What Is Rack Middleware

What is Rack middleware?

Rack as Design

Rack middleware is more than "a way to filter a request and response" - it's an implementation of the pipeline design pattern for web servers using Rack.

It very cleanly separates out the different stages of processing a request - separation of concerns being a key goal of all well designed software products.

For example with Rack I can have separate stages of the pipeline doing:

  • Authentication: when the request arrives, are the users logon details correct? How do I validate this OAuth, HTTP Basic Authentication, name/password?

  • Authorization: "is the user authorised to perform this particular task?", i.e. role-based security.

  • Caching: have I processed this request already, can I return a cached result?

  • Decoration: how can I enhance the request to make downstream processing better?

  • Performance & Usage Monitoring: what stats can I get from the request and response?

  • Execution: actually handle the request and provide a response.

Being able to separate the different stages (and optionally include them) is a great help in developing well structured applications.

Community

There's also a great eco-system developing around Rack Middleware - you should be able to find pre-built rack components to do all of the steps above and more. See the Rack GitHub wiki for a list of middleware.

What's Middleware?

Middleware is a dreadful term which refers to any software component/library which assists with but is not directly involved in the execution of some task. Very common examples are logging, authentication and the other common, horizontal processing components. These tend to be the things that everyone needs across multiple applications but not too many people are interested (or should be) in building themselves.

More Information

  • The comment about it being a way to filter requests probably comes from the RailsCast episode 151: Rack Middleware screen cast.

  • Rack middleware evolved out of Rack and there is a great intro at Introduction to Rack middleware.

  • There's an intro to middleware on Wikipedia here.

What is middleware when referenced in the context of Ruby on Rails?

Middleware is related to Rack, the standard Ruby API for web applications. Since Rails applications are Rack applications these days, they apply to both.

Rack middleware is everything between application servers (Webrick, Thin, Unicorn, Passenger, ...) and the actual application, such as your Rails application. It is the pipeline between the web application server and the application itself.

The input to a Rack application is an "environment" which contains all the HTTP request details (and more). The output is a HTTP response. Middleware layers are like filters which can modify the input, the output or both. Rails uses middleware to implement some of its features (query caching, cookie stores, http method expansion), but you can add your own.

Rack middleware is an effective way to reuse simple web-related behavior across web applications that use Rack, regardless of the underlying framework. If a part of your application adds functionality, but is not responsible for a HTTP response, it qualifies as Rack middleware.

Some examples of things you could implement as Rack middleware include:

  • HTTP caching (server side and client side)
  • Logging
  • Authentication
  • Monitoring
  • HTTP header filtering

See also this SO question.

Rack Middleware: use and run constructs

The article you have linked to mentions that Rack comes with a sample application Lobster so yes, for a start, that is an app.

The Shrimp class is just a Ruby class which is how a middleware should be written.

In the config.ru file, you require the modules, register the middleware, and then run the application.

use adds Shrimp to the middleware stack and run passes Lobster
instance into the Shrimp constructor?

You are right when you say use adds Shrimp to the middleware stack but run does not necessarily pass a Lobster instance into the Shrimp constructor. run just executes the Rack application and passing the app instance is how Rack works internally. The real thing happens in the use part where you register the middleware.

For example:

# config.ru
require 'rack'
require 'rack/lobster'
require 'shrimp_1'
require 'shrimp_2'
require 'shrimp_3'

use Shrimp_1
use Shrimp_2
use Shrimp_3
run Rack::Lobster.new </pre>

All three Shrimp classes will be passed the app(Lobster) instance in that order and this will be handled by Rack itself.

Rack Middleware in Rack Middleware?

Right, I'm not entirely sure what you're trying to do. But you can do this

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

def call(env)
cors = Rack::Cors.new(@app, {}) do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
cors.call(env)
end
end

Your config.ru should have use CorsWired though, not use CorsWired.new

This is I think what you were asking but I think you're missing the point of middleware. You should just change your config.ru to use rack-cors before/after your middleware depending on what you want to do.

require 'rack'
require 'rack/cors'
require './cors_wired'

app = Rack::Builder.new do
use Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :put, :options, :delete], :credentials => false
end
end
use CorsWired
run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
end
run app

RoR - Which is preferred - Rack Middleware or Active Controller Filters?

Rack Middleware and ActionController Filters are really quite different.

Rack is the standard Ruby webserver interface. It is designed to work in such a way that Rack applications or "Middlewares" can be chained together, each transforming the request / response in a particular way. If you create/use a Rack Middleware you are getting a chance to transform the request prior to it actually reaching the Rails app.

ActionController Filters are simply before/after hooks that execute before or after your immediate controller methods in Rails. These will be invoked immediately before or after your controller method, but after the entire remainder of the Rails stack.

Therefore there are significant differences in what is possible via a Rack Middleware and an ActionController filter, namely, because a Rack Middleware is executing prior to your application code it will not be executed in the same scope as your application code -- e.g. you won't be able to use your app models etc. unless you explicitly require them and perform the necessary initialization (like establishing a database connection).

If you're looking for rules of thumb, off the top of my head here's what I would tell you:

  1. If you want to do something with the request before methods only in a specific controller, use a before filter in that controller.

  2. If you want to do something with the request before all controller methods in your app, and what you want to do is very specific to your application or relies on your application code, use a filter on your ApplicationController.

  3. If you want to do something generic with the request, not tied to your application code at all, and you imagine it would be nice to be able to re-use that in another app, a Rack Middleware would be a better fit.

Hope that helps.

Where do you put your Rack middleware files and requires?

As of Rails 3.2, Rack middleware belongs in the app/middleware directory.

It works "out-of-the-box" without any explicit require statements.

Quick example:

I'm using a middleware class called CanonicalHost which is implemented in app/middleware/canonical_host.rb. I've added the following line to production.rb (note that the middleware class is explicitly given, rather than as a quoted string, which works for any environment-specific config files):

config.middleware.use CanonicalHost, "example.com"

If you're adding middleware to application.rb, you'll need to include quotes, as per @mltsy's comment.

config.middleware.use "CanonicalHost", "example.com"


Related Topics



Leave a reply



Submit