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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ 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:
1 |
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:
1 2 |
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
- https://www.amberbit.com/blog/2011/07/13/introduction-to-rack-middleware/
- http://www.integralist.co.uk/posts/rack-middleware.html
- http://ieftimov.com/writing-rack-middleware
- https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
- http://www.cise.ufl.edu/research/ParallelPatterns/PatternLanguage/AlgorithmStructure/Pipeline.htm