Module: Roda::RodaPlugins::ExceptionPage::InstanceMethods

Defined in:
lib/roda/plugins/exception_page.rb

Instance Method Summary collapse

Instance Method Details

#exception_page(exception, opts = OPTS) ⇒ Object

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.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/roda/plugins/exception_page.rb', line 198

def exception_page(exception, opts=OPTS)
  message = exception_page_exception_message(exception)
  if opts[:json]
    @_response[RodaResponseHeaders::CONTENT_TYPE] = "application/json"
    {
      "exception"=>{
        "class"=>exception.class.to_s,
        "message"=>message,
        "backtrace"=>exception.backtrace.map(&:to_s)
      }
    }
  elsif env['HTTP_ACCEPT'] =~ /text\/html/
    @_response[RodaResponseHeaders::CONTENT_TYPE] = "text/html"

    context = opts[:context] || 7
    css_file = opts[:css_file]
    js_file = opts[:js_file]

    case prefix = opts[:assets]
    when false
      css_file = false if css_file.nil?
      js_file = false if js_file.nil?
    when nil
      # nothing
    else
      prefix = '' if prefix == true
      css_file ||= "#{prefix}/exception_page.css"
      js_file ||= "#{prefix}/exception_page.js"
    end

    css = case css_file
    when nil
      "<style type=\"text/css\">#{exception_page_css}</style>"
    when false
      # :nothing
    else
      "<link rel=\"stylesheet\" href=\"#{h css_file}\" />"
    end

    js = case js_file
    when nil
      "<script type=\"text/javascript\">\n//<!--\n#{exception_page_js}\n//-->\n</script>"
    when false
      # :nothing
    else
      "<script type=\"text/javascript\" src=\"#{h js_file}\"></script>"
    end

    frames = exception.backtrace.map.with_index do |line, i|
      frame = {:id=>i}
      if line =~ /\A(.*?):(\d+)(?::in `(.*)')?\Z/
        filename = frame[:filename] = $1
        lineno = frame[:lineno] = $2.to_i
        frame[:function] = $3

        begin
          lineno -= 1
          lines = ::File.readlines(filename)
          if line = lines[lineno]
            pre_lineno = [lineno-context, 0].max
            if (pre_context = lines[pre_lineno...lineno]) && !pre_context.empty?
              frame[:pre_context_lineno] = pre_lineno
              frame[:pre_context] = pre_context
            end

            post_lineno = [lineno+context, lines.size].min
            if (post_context = lines[lineno+1..post_lineno]) && !post_context.empty?
              frame[:post_context_lineno] = post_lineno
              frame[:post_context] = post_context
            end

            frame[:context_line] = line.chomp
          end
        rescue
        end

        frame
      end
    end.compact

    r = @_request
    begin 
      post_data = r.POST
      missing_post = "No POST data"
    rescue
      missing_post = "Invalid POST data"
    end
    info = lambda do |title, id, var, none|
      <<END
  <h3 id="#{id}">#{title}</h3>
  #{(var && !var.empty?) ? (<<END1) : "<p>#{none}</p>"
    <table class="req">
      <thead>
<tr>
  <th>Variable</th>
  <th>Value</th>
</tr>
      </thead>
      <tbody>
  #{var.sort_by{|k, _| k.to_s}.map{|key, val| (<<END2)}.join
  <tr>
    <td>#{h key}</td>
    <td class="code"><div>#{h val.inspect}</div></td>
  </tr>
END2
}
      </tbody>
    </table>
END1
}
END
    end

    <<END
<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>#{h exception.class} at #{h r.path}</title>
  #{css}
</head>
<body>

<div id="summary">
  <h1>#{h exception.class} at #{h r.path}</h1>
  <h2>#{h message}</h2>
  <table><tr>
    <th>Ruby</th>
    <td>
#{(first = frames.first) ? "<code>#{h first[:filename]}</code>: in <code>#{h first[:function]}</code>, line #{first[:lineno]}" : "unknown location"}
    </td>
  </tr><tr>
    <th>Web</th>
    <td><code>#{r.request_method} #{h r.host}#{h r.path}</code></td>
  </tr></table>

  <h3>Jump to:</h3>
  <ul id="quicklinks">
    <li><a href="#get-info">GET</a></li>
    <li><a href="#post-info">POST</a></li>
    <li><a href="#cookie-info">Cookies</a></li>
    <li><a href="#env-info">ENV</a></li>
  </ul>
</div>

<div id="traceback">
  <h2>Traceback <span>(innermost first)</span></h2>
  <ul class="traceback">
#{frames.map{|frame| id = frame[:id]; (<<END1)}.join
      <li class="frame">
<code>#{h frame[:filename]}:#{frame[:lineno]}</code> in <code>#{h frame[:function]}</code>

  #{frame[:context_line] ? (<<END2) : '</li>'
  <div class="context" id="c#{id}">
    #{frame[:pre_context] ? (<<END3) : ''
    <ol start="#{frame[:pre_context_lineno]+1}" id="bc#{id}">
      #{frame[:pre_context].map{|line| "<li>#{h line}</li>"}.join}
    </ol>
END3
}

    <ol start="#{frame[:lineno]}" class="context-line">
      <li>#{h frame[:context_line]}<span>...</span></li>
    </ol>

    #{frame[:post_context] ? (<<END4) : ''
    <ol start='#{frame[:lineno]+1}' id="ac#{id}">
      #{frame[:post_context].map{|line| "<li>#{h line}</li>"}.join}
    </ol>
END4
}
  </div>
END2
}
END1
}
  </ul>
</div>

<div id="requestinfo">
  <h2>Request information</h2>

  #{info.call('GET', 'get-info', r.GET, 'No GET data')}
  #{info.call('POST', 'post-info', post_data, missing_post)}
  #{info.call('Cookies', 'cookie-info', r.cookies, 'No cookie data')}
  #{info.call('Rack ENV', 'env-info', r.env, 'No Rack env?')}
</div>

<div id="explanation">
  <p>
    You're seeing this error because you use the Roda exception_page plugin.
  </p>
</div>

#{js}
</body>
</html>
END
  else
    @_response[RodaResponseHeaders::CONTENT_TYPE] = "text/plain"
    "#{exception.class}: #{message}\n#{exception.backtrace.map{|l| "\t#{l}"}.join("\n")}"
  end
end

#exception_page_cssObject

The CSS to use on the exception page



403
404
405
# File 'lib/roda/plugins/exception_page.rb', line 403

def exception_page_css
  ExceptionPage.css
end

#exception_page_jsObject

The JavaScript to use on the exception page



408
409
410
# File 'lib/roda/plugins/exception_page.rb', line 408

def exception_page_js
  ExceptionPage.js
end