Find out Rack Apps and Middleware in ROR

Tram Ho

Many web developers work at the highest level of abstraction when we code. And sometimes it’s easy to take things for granted. Especially when we are using Rails.

Have you ever delved into how the request / response cycle works in Rails? Recently I realized that I knew almost nothing about how Rack or middleware works – so I spent a little bit of time figuring it out. Here are my findings.

What is Rack?

Did you know that Rails is a Rack application? So is Sinatra. What is Rack? I’m glad you asked. Rack is a Ruby package that provides an easy to use interface between a web server and web framework.

You can quickly create simple web applications with just Rack.

To start, you need an object that responds to a call, uses an Environment Hash and returns an array with the HTTP response code , header and response body . Once you’ve written the server code, all you have to do is launch it using a Ruby server such as Rack::Handler::WEBrick or put it in the config.ru file and run it from the command line with rackup config.ru .

Ok, great. But what does Rack actually do?

How does Rack work?

Rack is really just a way for a developer to create a server application while avoiding pre-compiled code to support different Ruby web servers. If you’ve written some code that meets Rack’s specifications, you can upload it to a Ruby server like WEBrick, Mongrel, or Thin – and you’ll be more than willing to accept requests and respond to them.

There are several methods you should know which are provided for you. You can call them directly from within your config.ru file.

trembling

Take an application – the call response object – as an argument. The following code snippet from the Rack website shows this way:

map

Get a string specifying the path to be processed and a block containing the Rack application code to be run when a request for that path is received.

Here is an example:

use

This indicates that Rack uses certain middleware.

So what else do you need to know? Let’s take a closer look at environment hash function and response array.

Environment Hash

Rack server objects receive in a hash environment. What’s in that hash? Here are some more interesting parts:

  • REQUEST_METHOD : The HTTP verb of the request. This is required.
  • PATH_INFO : The request URL path, relative to the root of the application.
  • QUERY_STRING : Anything next? in the request URL string.
  • SERVER_NAME and SERVER_PORT : Server address and port.
  • rack.version : The version of the rack in use.
  • rack.url_scheme : Is it http or https?
  • rack.input : An IO-like object that holds raw HTTP POST data.
  • rack.errors : A feedback object for placing, writing, and discharging.
  • rack.session : The key value store to store the requested session data.
  • rack.logger : An object can write interfaces. It must implement informational, debugging, warning, error and critical methods.

A lot of frameworks built on Rack wrap env hash in a Rack::Request object. This object provides a lot of convenience methods. For example, request_method, query_string, session, and logger return the values ​​from the keys described above. It also allows you to test things like parameters, HTTP schema or if you are using ssl.

Response

When your Rack object returns a response, it must contain three parts:

  • Status
  • Header
  • Body

Like the request, there is a Rack :: Response object that gives you convenient methods like write, set_cookie, and finish. Alternatively, you can return an array containing three elements.

Status

It’s an HTTP Status like 200 or 400

Header

Something responds to each and generates key-value pairs. The keys should be string and conform to the RFC7230 token specification. This is where you can set Content-Type and Content-Length if it fits your answer.

Body

The body is the data that the server sends back to the requestor. It must satisfy each and every string value brought.

What is Middleware?

One of the things that makes Rack so great is how easy it is to add chain middleware components between the web server and the app to customize how your request / response works.

But what is the middleware component?

An middleware component resides between the client and the server, handling both inbound and outbound responses. Why do you want to do that? There are plenty of middleware components available to Rack to eliminate guesswork from problems like caching activation, authentication, and spam traps.

Use Middleware in the Rack application

To add middleware to the Rack application, all you have to do is ask Rack to use it. You can use many middleware components and they will change request or response before passing it on to the next component. This component chain is called the middleware stack .

Warden

We’ll look at how you add the Warden to a project. The Warden has to come after some sort of session middleware in the stack, so we will also use Rack::Session::Cookie .

First, add it to your Gemfile project with the “warden” gem and install it with the package installed.

Now add it to your config.ru file:

Finally, run the app with the rackup. It will find config.ru and boot on port 9292.

Note that there are many settings regarding having the Warden actually authenticate with your application. This is just one example of how to load it into the middleware stack. For a more robust example of Warden integration, check out this gist.

There is another way to define middleware stack. Instead of calling use directly in config.ru , you can use Rack::Builder to wrap some middleware and application (s) in a large application.

For example:

Rack Basic Auth

One useful middleware is Rack::Auth::Basic , which can be used to secure any Rack application with HTTP basic authentication. It is really lightweight and useful for protecting small bits of the application. For example, Ryan Bates uses it to protect the Resque server in a Rails application in this Railscasts article.

Here’s how to install it:

Use Middleware in Rails

So what? Rack is pretty cool. And we know that Rails is built on top of it. But just because we understand what it is, it’s not really that useful when working with a production application.

How Rails uses Rack

Have you ever noticed that there is a config.ru file in the root directory of every Rails project generated? Have you ever looked inside? Here’s what it contains:

This is quite simple. It finds the config/environment file, then starts Rails.application .

Wait, what is that? Looking at config / environment, we can see that it’s defined in config / application.rb. The config / environment is just the initial call! up there.

So what’s in config / application.rb? If we pay attention, we will see that it loads in the associated gems from config/boot.rb , requests rails/all , uploads the environment (development, test, production, etc.) and specifies the namespace version. of our application.

It looks like this:

This means that Rails::Application is a Rack application. Certainly, if we check the source code, it will respond to a call

But which middleware is it using? If we look closely, we see it’s automatically loading rails / application / default_middleware_stack – and checked, it looks like it was defined in ActionDispatch .

Where does ActionDispatch come from? ActionPack .

Action Dispatch

ActionPack is the framework of Rails for handling web requests and responses. ActionPack is home to pretty much all the good features you find in Rails, such as routing, the abstract controls you inherit from, and view rendering.

The most relevant part of ActionPack for our discussion here is Action Dispatch. It offers several middleware components handling ssl, cookies, debugging, static files and more.

If you look at each component of the ActionDispatch middleware, you’ll notice they all conform to the Rack specification: They all respond to the call, use the application and return the status, title and content. Many of them also use Rack :: Request and Rack :: Response objects.

Reading through the code in these components has helped you uncover many of the mysteries of what’s going on behind the scenes when making a request for a Rails application. When I realized that it was just a bunch of Ruby objects conforming to the Rack specification – passing requests and responses to each other – that made this whole part of Rails a lot less mysterious.

Now that we understand a bit of what’s going on, let’s take a look at how to actually bring some custom middleware into the Rails application.

Manually add Middleware

Imagine you are hosting an application on Engine Yard. You have a Rails API running on one server and a client-side JavaScript application running on another server. The API has a url of https://api.example.com and the client-side application is available at https://app.example.com .

You will quickly run into a problem: You cannot access the resource at api.example.com from your JavaScript application due to the same origin policy. As you probably know, the solution to this problem is to enable cross-origin resource sharing (CORS). There are many ways to enable CORS on your server – but one of the easiest is to use the middleware Rack :: Cors gem.

Start by asking for it in Gemfile:

Like many other things, Rails provides a very easy way to download middleware. While we can certainly add it to the Rack :: Builder block in config.ru – as we did above – the convention of Rails is to put it in config / application.rb, using the following syntax:

Note that we are using insert_before here to make sure that Rack :: Cors comes before the rest of the middleware brought into the stack by ActionPack (and any other middleware you may be doing. use).

If you restart the server, you should get started! Your client-side application can access api.example.com without encountering a native policy JavaScript error.

Conclude

In this post, we will take a deep look at how Rack works and request / response loops for several Ruby web frameworks, including Ruby on Rails.

I hope that better understanding the process of a request to access your server and your application returning the response will help make things clear. And when we run into magic problems, we can easily judge it through how Rack works.

Share the news now

Source : Viblo