How to write your own Rack middleware

How to write your own Rack middleware Rack is a Ruby package which provides an interface for a web server to communicate with the application. It is very easy to add middleware components between the web server and the app to customize the way your request/response behaves. The middleware component sits between the client and the server, processing inbound requests and outbound responses. Rack Middleware is an implementation of the pipeline design pattern for web servers using Rack. For example with Rack, we can have separate stages of the pipeline:

  • Authentication: Checks whether the login details are correct or not when the request arrives.
  • Authorization:  It performs role-based security. i.e. checks whether the user is authorized to perform the particular task.
  • Caching: Return a cached result if the request is already processed.
  • Decoration: Enhance the request to make downstream processing better.
  • Performance & Usage Monitoring: Status get from the request and response.
  • Execution: actually handle the request and provide a response.
Next, we will see how to build our own rack middleware.

Building your own Rack middleware

To add middleware to a Rack application, all you have to do is tell Rack to use it. You can use multiple middleware components, and they will change the request or response before passing it on to the next component. It uses the configuration file with extension .ru, that instructs Rack::Builder what middleware should it use and in which order. Now, let’s have a look at how to add our own rack middleware to a project. For that, add the following code in config.ru file.: Eg:
use Rack::Lint # gives more descriptive error messages when responses aren't valid
class Example
  def initialize(app)
    @app = app
  end
  def call(env)
    status, headers, body = @app.call(env)
    body.map { |msg| p "Example: #{msg}" }
    [status, headers, body]
  end
end
use Example # Does nothing with uppercase'd response, just logs it to stdout
run -> env {[200, {"Content-Type" => "text/html"}, ["<h1>Hello Redpanthers</h1>"]]}
In our example, here the response from Example is then passed into the 3rd party Rack middleware Rack::Lint which will let us know if our final response is valid or not. And the first argument of the initialize method is the application or the request handler.Another rule that applies to a middleware class is the call method. The call method executes the application which returns the status, the headers and the body of the response.

Finally, run the server with rackup. It will find config.ru and boot up on the specified port.

$ rackup -s Puma -p 9293
Puma starting in single mode...
Version 3.6.2 (ruby 2.3.1-p112), codename: Sleepy Sunday Serenity
Min threads: 5, max threads: 5
Environment: development
Listening on tcp://localhost:9293
Use Ctrl-C to stop
"Example: <h1>Hello Redpanthers</h1>"
127.0.0.1 - - [05/Jan/2017:17:48:43 +0530] "GET / HTTP/1.1" 200 - 0.0035
"Example: <h1>Hello Redpanthers</h1>"
127.0.0.1 - - [05/Jan/2017:17:48:43 +0530] "GET /favicon.ico HTTP/1.1" 200 - 0.0022

Adding it to Rails

We can add the middleware to our Rails app by placing middleware module to lib/<file_name.rb> and configure it by using config.middleware in environments/<environment>.rb file. As per the above example, we can add the middleware Example in lib/example.rb configure it by using:
config.middleware.use Example
You can add your middleware using any of the following methods:
  • config.middleware.use(new_middleware, args) – Adds the new middleware at the bottom of the middleware stack.
  • config.middleware.insert_before(existing_middleware, new_middleware, args) – Adds the new middleware before the specified existing middleware in the middleware stack.
  • config.middleware.insert_after(existing_middleware, new_middleware, args) – Adds the new middleware after the specified existing middleware in the middleware stack.
Eg:
config.middleware.insert_before Rails::Rack::Lint, Example
config.middleware.insert_after Rails::Rack::Lint, Example
So, by doing the above steps we can make our own rack middleware. Hope it helps you in some way to know about the basics of rack middleware and how to create our own middleware.

 References

  1. https://www.amberbit.com/blog/2011/07/13/introduction-to-rack-middleware/
  2. http://www.integralist.co.uk/posts/rack-middleware.html
  3. http://ieftimov.com/writing-rack-middleware
  4. https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
  5. http://www.cise.ufl.edu/research/ParallelPatterns/PatternLanguage/AlgorithmStructure/Pipeline.htm
]]>