module Roda::RodaPlugins::ExceptionPage::InstanceMethods

  1. lib/roda/plugins/exception_page.rb

Public Instance methods

exception_page(exception, opts=OPTS)

Return a HTML page showing the exception, allowing a developer more information for debugging. Designed to be called inside an exception handler, passing in the received exception. Sets the Content-Type header in the response, and returns the string used for the body. If the Accept request header is present and text/html is accepted, return an HTML page with the backtrace with the ability to see the context for each backtrace line, as well as the GET, POST, cookie, and rack environment data. If text/html is not accepted, then just show a plain text page with the exception class, message, and backtrace.

Options:

:assets

If true, sets :css_file to /exception_page.css and :js_file to /exception_page.js, assuming that r.exception_page_assets is called in the route block to serve the exception page assets. If a String, uses the string as a prefix, assuming that r.exception_page_assets is called in a nested block inside the route block. If false, doesn’t use any CSS or JS.

:context

The number of context lines before and after each line in the backtrace (default: 7).

:css_file

A path to the external CSS file for the HTML exception page. If false, doesn’t use any CSS.

:js_file

A path to the external javascript file for the HTML exception page. If false, doesn’t use any JS.

:json

Return a hash of exception information. The hash will have a single key, “exception”, with a value being a hash with three keys, “class”, “message”, and “backtrace”, which contain information derived from the given exception. Designed to be used with the json exception, which will automatically convert the hash to JSON format.

[show source]
    # File lib/roda/plugins/exception_page.rb
198         def exception_page(exception, opts=OPTS)
199           message = exception_page_exception_message(exception)
200           if opts[:json]
201             @_response[RodaResponseHeaders::CONTENT_TYPE] = "application/json"
202             {
203               "exception"=>{
204                 "class"=>exception.class.to_s,
205                 "message"=>message,
206                 "backtrace"=>exception.backtrace.map(&:to_s)
207               }
208             }
209           elsif env['HTTP_ACCEPT'] =~ /text\/html/
210             @_response[RodaResponseHeaders::CONTENT_TYPE] = "text/html"
211 
212             context = opts[:context] || 7
213             css_file = opts[:css_file]
214             js_file = opts[:js_file]
215 
216             case prefix = opts[:assets]
217             when false
218               css_file = false if css_file.nil?
219               js_file = false if js_file.nil?
220             when nil
221               # nothing
222             else
223               prefix = '' if prefix == true
224               css_file ||= "#{prefix}/exception_page.css"
225               js_file ||= "#{prefix}/exception_page.js"
226             end
227 
228             css = case css_file
229             when nil
230               "<style type=\"text/css\">#{exception_page_css}</style>"
231             when false
232               # :nothing
233             else
234               "<link rel=\"stylesheet\" href=\"#{h css_file}\" />"
235             end
236 
237             js = case js_file
238             when nil
239               "<script type=\"text/javascript\">\n//<!--\n#{exception_page_js}\n//-->\n</script>"
240             when false
241               # :nothing
242             else
243               "<script type=\"text/javascript\" src=\"#{h js_file}\"></script>"
244             end
245 
246             frames = exception.backtrace.map.with_index do |line, i|
247               frame = {:id=>i}
248               if line =~ /\A(.*?):(\d+)(?::in [`'](.*)')?\Z/
249                 filename = frame[:filename] = $1
250                 lineno = frame[:lineno] = $2.to_i
251                 frame[:function] = $3
252 
253                 begin
254                   lineno -= 1
255                   lines = ::File.readlines(filename)
256                   if line = lines[lineno]
257                     pre_lineno = [lineno-context, 0].max
258                     if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
259                       frame[:pre_context_lineno] = pre_lineno
260                       frame[:pre_context] = pre_context
261                     end
262 
263                     post_lineno = [lineno+context, lines.size].min
264                     if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
265                       frame[:post_context_lineno] = post_lineno
266                       frame[:post_context] = post_context
267                     end
268 
269                     frame[:context_line] = line.chomp
270                   end
271                 rescue
272                 end
273 
274                 frame
275               end
276             end.compact
277 
278             r = @_request
279             begin 
280               post_data = r.POST
281               missing_post = "No POST data"
282             rescue
283               missing_post = "Invalid POST data"
284             end
285             info = lambda do |title, id, var, none|
286               <<END
287   <h3 id="#{id}">#{title}</h3>
288   #{(var && !var.empty?) ? (<<END1) : "<p>#{none}</p>"
289     <table class="req">
290       <thead>
291         <tr>
292           <th>Variable</th>
293           <th>Value</th>
294         </tr>
295       </thead>
296       <tbody>
297           #{var.sort_by{|k, _| k.to_s}.map{|key, val| (<<END2)}.join
298           <tr>
299             <td>#{h key}</td>
300             <td class="code"><div>#{h val.inspect}</div></td>
301           </tr>
302 END2
303 }
304       </tbody>
305     </table>
306 END1
307 }
308 END
309             end
310 
311             <<END
312 <!DOCTYPE html>
313 <html lang="en">
314 <head>
315   <meta http-equiv="content-type" content="text/html; charset=utf-8" />
316   <title>#{h exception.class} at #{h r.path}</title>
317   #{css}
318 </head>
319 <body>
320 
321 <div id="summary">
322   <h1>#{h exception.class} at #{h r.path}</h1>
323   <h2>#{h message}</h2>
324   <table><tr>
325     <th>Ruby</th>
326     <td>
327 #{(first = frames.first) ? "<code>#{h first[:filename]}</code>: in <code>#{h first[:function]}</code>, line #{first[:lineno]}" : "unknown location"}
328     </td>
329   </tr><tr>
330     <th>Web</th>
331     <td><code>#{r.request_method} #{h r.host}#{h r.path}</code></td>
332   </tr></table>
333 
334   <h3>Jump to:</h3>
335   <ul id="quicklinks">
336     <li><a href="#get-info">GET</a></li>
337     <li><a href="#post-info">POST</a></li>
338     <li><a href="#cookie-info">Cookies</a></li>
339     <li><a href="#env-info">ENV</a></li>
340   </ul>
341 </div>
342 
343 <div id="traceback">
344   <h2>Traceback <span>(innermost first)</span></h2>
345   <ul class="traceback">
346 #{frames.map{|frame| id = frame[:id]; (<<END1)}.join
347       <li class="frame">
348         <code>#{h frame[:filename]}:#{frame[:lineno]}</code> in <code>#{h frame[:function]}</code>
349 
350           #{frame[:context_line] ? (<<END2) : '</li>'
351           <div class="context" id="c#{id}">
352             #{frame[:pre_context] ? (<<END3) : ''
353             <ol start="#{frame[:pre_context_lineno]+1}" id="bc#{id}">
354               #{frame[:pre_context].map{|line| "<li>#{h line}</li>"}.join}
355             </ol>
356 END3
357 }
358 
359             <ol start="#{frame[:lineno]}" class="context-line">
360               <li>#{h frame[:context_line]}<span>...</span></li>
361             </ol>
362 
363             #{frame[:post_context] ? (<<END4) : ''
364             <ol start='#{frame[:lineno]+1}' id="ac#{id}">
365               #{frame[:post_context].map{|line| "<li>#{h line}</li>"}.join}
366             </ol>
367 END4
368 }
369           </div>
370 END2
371 }
372 END1
373 }
374   </ul>
375 </div>
376 
377 <div id="requestinfo">
378   <h2>Request information</h2>
379 
380   #{info.call('GET', 'get-info', r.GET, 'No GET data')}
381   #{info.call('POST', 'post-info', post_data, missing_post)}
382   #{info.call('Cookies', 'cookie-info', r.cookies, 'No cookie data')}
383   #{info.call('Rack ENV', 'env-info', r.env, 'No Rack env?')}
384 </div>
385 
386 <div id="explanation">
387   <p>
388     You're seeing this error because you use the Roda exception_page plugin.
389   </p>
390 </div>
391 
392 #{js}
393 </body>
394 </html>
395 END
396           else
397             @_response[RodaResponseHeaders::CONTENT_TYPE] = "text/plain"
398             "#{exception.class}: #{message}\n#{exception.backtrace.map{|l| "\t#{l}"}.join("\n")}"
399           end
400         end
exception_page_css()

The CSS to use on the exception page

[show source]
    # File lib/roda/plugins/exception_page.rb
403 def exception_page_css
404   ExceptionPage.css
405 end
exception_page_js()

The JavaScript to use on the exception page

[show source]
    # File lib/roda/plugins/exception_page.rb
408 def exception_page_js
409   ExceptionPage.js
410 end