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.
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 ExampleYou 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.
config.middleware.insert_before Rails::Rack::Lint, Example config.middleware.insert_after Rails::Rack::Lint, ExampleSo, 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
Hello Anjana, I know this is outside the scope of the article but by chance do you know how to create a middleware while using rails 5 in api-mode?
This post shows how to do it in rails 4:
https://stackoverflow.com/questions/26922343/omniauthnosessionerror-you-must-provide-a-session-to-use-omniauth-configur
I’m trying to create a middleware like the accepted answer in the above post but when adding the
config.middleware.insert_before ActionDispatch::ParamsParser, “SelectiveStack”
it throws an error saying it can’t find SelectiveStack.
It also won’t let me add a require or includes in the application.rb to access it, so I was wondering if you’ve come across this or had any ideas?
Can you post exact error message with traceback, I think issue is because “SelectiveStack” isn’t properly autoloaded, make sure that you are using exact file location as in the that SO answer
We ended up just dropping API mode because it was causing issues with other gems as well.
The error was something along the lines of no SelectiveStack to access or something of the sort.