codereading / sinatra

Classy web-development dressed in a DSL (official / canonical repo)
http://www.sinatrarb.com/
MIT License
12 stars 2 forks source link

What are various options for error handling in Sinatra? #8

Open ericgj opened 12 years ago

ericgj commented 12 years ago

@codereading/readers

ericgj commented 12 years ago

This is a somewhat complex topic, to begin with there is a distinction between

  1. You have an error condition that you want to trap and return an http error code and response for, within your app, and
  2. Your code raises an error that you don't handle, how do you want Sinatra to handle it and/or how is it handled back down the stack ?

The app-level error handling (1) is well-documented here. Here's the method for defining an error handler:

      # Define a custom error handler. Optionally takes either an Exception
      # class, or an HTTP status code to specify which errors should be
      # handled.
      def error(*codes, &block)
        args  = compile! "ERROR", //, block
        codes = codes.map { |c| Array(c) }.flatten
        codes << Exception if codes.empty?
        codes.each { |c| @errors[c] = args }
      end

It's interesting that error handlers are defined as a special kind of route. Anyone want to take a stab at exegesis ?


As for framework-level error handling (2), the relevant code is here (called from the rescue Exception in dispatch!):

    # Error handling during requests.
    def handle_exception!(boom)
      @env['sinatra.error'] = boom
      status boom.respond_to?(:code) ? Integer(boom.code) : 500

      if server_error?
        dump_errors! boom if settings.dump_errors?
        raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
      end

      if not_found?
        headers['X-Cascade'] = 'pass'
        body '<h1>Not Found</h1>'
      end

      res = error_block!(boom.class, boom) || error_block!(status, boom)
      return res if res or not server_error?
      raise boom if settings.raise_errors? or settings.show_exceptions?
      error_block! Exception, boom
    end

    # Find an custom error block for the key(s) specified.
    def error_block!(key, *block_params)
      base = settings
      while base.respond_to?(:errors)
        next base = base.superclass unless args = base.errors[key]
        args += [block_params]
        return process_route(*args)
      end
      return false unless key.respond_to? :superclass and key.superclass < Exception
      error_block!(key.superclass, *block_params)
    end

    def dump_errors!(boom)
      msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
      @env['rack.errors'].puts(msg)
    end