-
Notifications
You must be signed in to change notification settings - Fork 217
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.
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
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.
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 # deprecated: required by other 2 in older versions of Goliath to 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.
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.