Skip to content
Ilya Grigorik edited this page Mar 3, 2011 · 12 revisions

Middleware

Within Goliath, all request and response processing can be done in fully asynchronous fashion. Hence, you will have to use Rack middleware that is async-aware.

Using Rack Middleware in Goliath

Goliath ships with a number of common middleware files, which you can load into your application through the familiar Rack builder interface: use MiddlewareClass, options. Let’s take a look at a simple example:

class Echo < Goliath::API

  # reload code on every request in dev environment
  use ::Rack::Reloader, 0 if Goliath.dev?

  use Goliath::Rack::Params             # parse & merge query and body parameters
  use Goliath::Rack::DefaultMimeType    # cleanup accepted media types
  use Goliath::Rack::Formatters::JSON   # JSON output formatter
  use Goliath::Rack::Render             # auto-negotiate response format
  use Goliath::Rack::Heartbeat          # respond to /status with 200, OK (monitoring, etc)

  def response(env)
    [200, {}, {response: params['echo']}]
  end
end

Above, we are loading the Rack::Reloader plugin in our development environment, which will reload the code on each request to help us avoid having to start and stop the API after each edit. Then, we initialize Params middleware which will parse the body and query-string params, and so on. Finally, in our response we return a Ruby hash, which is then converted by our formatter middleware (JSON) into a well-formed JSON response.

Validations

A common use case for all web-services is to accept some number of query or POST body parameters and validate their formatting, presence, etc. Goliath ships with a set of Validation middleware helpers which will help simplify this process:

class Echo < Goliath::API

  use Goliath::Rack::ValidationError    # catch and render validation errors

  use Goliath::Rack::Validation::RequestMethod, %w(GET)           # allow GET requests only
  use Goliath::Rack::Validation::RequiredParam, {:key => 'echo'}  # must provide ?echo= query or body param

  def response(env)
    [200, {}, 'Hello World']
  end
end

In the example above, we specify two validators: only GET requests are accepted, and we set key to be a required parameter. If either of these validations are not met, then Goliath will return an error to the client with a simple explanation of what criteria must be met. For full list of other supported validations such as numeric ranges, etc, check the source.

Writing your own middleware

Unlike other Rack powered app servers, Goliath creates a single instance of the middleware chain at startup, and reuses it for all incoming requests. Since everything is asynchronous, you can have multiple requests using the middleware chain at the same time. If your middleware tries to store any instance or class level variables they’ll end up getting stomped all over by the next request. Everything that you need to store needs to be stored in local variables.

Hence to make your custom middleware work with Goliath, you will have to (a) handle the asynchronous response case, and (b) make sure that the middleware is safe to reuse between multiple requests. As an example, let’s look at a simple JSON formatter middleware:

require 'yajl'

module Goliath
  module Rack

    module Formatters

      class JSON
        def initialize(app)
          @app = app
        end

        def call(env)
          async_cb = env['async.callback']
          env['async.callback'] = Proc.new do |status, headers, body|
            async_cb.call(post_process(status, headers, body))
          end

          status, headers, body = @app.call(env)
          post_process(status, headers, body)
        end

        def post_process(status, headers, body)
          if json_response?(headers)
            body = Yajl::Encoder.encode(body, :pretty => true, :indent => "\t")
          end
          [status, headers, body]
        end

        def json_response?(headers)
          headers['Content-Type'] =~ %r{^application/(json|javascript)}
        end
      end
    end
  end
end

Within call(env) we store the previous async.callback into async_cb and redefine it to be our own – this way, when the asynchronous response is done, Goliath can “unwind” the request by walking up the callback chain.

However, you will notice that we execute the post_process method in the default return case. If the validations fail later in the middleware chain before your classes response(env) method is executed the response will come back up through the chain normally and be returned.