Rack Middlewares in Ruby on Rails

In this post, I will describe what Rack is and how to write middlewares in Ruby on Rails. I will use common production examples.

Imagine that we want to take request samples in our App and send the metrics to a Service to analyze (request processing time, http_status, etc.). At the same time, we want to add an ID to every single request for debugging purposes. Before jumping into how to solve it, we need to understand a few concepts.

What is a Middleware?

Flow diagram for Client, Server and its middleware. On the left there is the client and on the right the server. There are directional arrows indicating the sender and receiver. Each arrow is tagged with a number.
Flow diagram for Client, Server and its middleware. On the left there is the client and on the right the server. There are directional arrows indicating the sender and receiver. Each arrow is tagged with a number.
Figure 1. Basic Client-server communication diagram.

The term middleware is used in many different forms and has different meanings depending on the context.

A middleware is a layer that lives between the application and the server. It acts as the middleman between them. The communication flow between a client, a server and its middleware, is the following:

The middleware receives a request from a client (1) and sends it to the application (2). The application returns a response (3). The middleware grabs that response and sends it to the client (4). See figure 1 above.

As you might think from the definition, middlewares have a lot of power!. We can change the request before passing it to the application and after it is processed before sending it to the client!.

There are many different usages for middlewares:

  • Routing: We can redirect a request depending on its content.
  • Performance: We can modify the request before passing it to the application to improve performance (e.g. compressing responses).
  • Logging: We can log the request stats before sending it to the client (e.g. log the response status or time to process the request).
  • Security: We can verify that the request was made by a client that we trust.
  • Profiling: (e.g. the gem rack-mini-profiler)
  • And many more!

Rack

What is Rack? Do I use it? If you’re working on Rails, you likely use it!

First of all, Rack is a gem. It’s is included in Rails by default (via ActionPack). It’s also part of other popular Web Ruby frameworks like Sinatra or Hanami.

It allows you to write middlewares for Ruby apps easily! Rack creates an interface that wraps HTTP requests and responses. A Rack application is a Ruby object (not a class) that responds to the call method. The application is called when the method call is invoked.

Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby. By wrapping HTTP requests and responses in the simplest way possible, it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call. Rack source code.

A Rack application needs to be Rack compliant. It means that it needs to respect the Rack spec. The application needs to respond to call that receives one argument: the environmentand returns an Array of exactly three values: status, headers, and body.

Basic middleware example.

The enviroment is a hash that contains CGI-like headers such as http_method, query_string etc., and Rack specific variables. We can modify the environment before calling the App, such as adding a header with extra information or return early if a particular condition happens. You can see here the list of values an environment can have.

Pro tip: To see the list of middleware your Rails app has, run the command: bin/rails middleware.

From the code above, you probably got a good idea of how to write Rack middleware. It’s straightforward!. First, we need to add it to your App’s middleware stack. To do that, we need to use config.middleware in the application.rb file to tell in which class our middleware lives and the order it’s in the stack (More details).

Let’s see two examples.

Example 1: Log Request and Response metrics

We want to print, in the console, metrics of the requests and responses, such as the controller used, time to process the request, etc.

MeasureLogs middleware code.

The constructor of our Rack middleware will receive the application and store it in a variable.

Example middleware call function that tracks request process time.

We will store the current time in a variable and tell the App to execute (by calling app.call). We will calculate the time and print the metrics in the console.

Figure 2: Console for the example Logger middleware.

A similar middleware Rails::Rack::Logger is implemented in Rails.

Example 2: Add a unique identifier to each request

LogRequestId middleware code.

We want to add a unique identifier to each request, tell the application which ID the request has, and add it to the response.

To do that, we’ll generate a random ID and add it to the request’ headers before passing the request to the App. We have access to the headers via the environment.

Figure 3. Example of a request that contains X-Request-Id.

Great! We have added a request-ID identifier to every request!

You might have seen this in Rails applications before. It is because ActionDisptach has a middleware called RequestId that implements the same functionality.

I encourage you to take a look at the Rails source code to check the middlewares included. You can see the complete list on this file. You can also check the examples used here on this Github repository.

Conclusion

We learned the basics of Middlewares, what Rack is, and how easy it’s to write them in Rails.

Middlewares are very powerful. There are thousands of use cases for them. We covered two examples, which are examples of existing middleware that are part of Rails.

Thanks for reading!

Staff Software Engineer at Shopify. Opinions are my own. ignaciochiazzo.com