cult3

What is Ruby Rack?

Sep 09, 2015

Table of contents:

  1. What is Rack?
  2. Rack as a Web Server Interface
  3. Rack as Middleware
  4. Conclusion

Something you will hear a lot about in the world of Ruby is “Rack”.

Rack is very nice convention of the Ruby world. It gives you (as a Ruby developer) the ability to use any web server with any framework (as long as both adhere to the Rack contract).

This is really good for a lot of reasons and so in today’s tutorial we’re going to be looking at using Rack.

What is Rack?

Rack is an interface that sits between your application and the web server. Rack provides a standard way for Ruby applications to talk to web servers so it doesn’t really matter what web server you use for your application. This makes it very easy to create HTTP facing web applications.

Rack is also an implementation of the Pipeline Design pattern that enables you to add Middleware to your application’s request lifecycle. Because Rack does not depend on any framework, you can reuse middleware across applications.

So Rack is basically a standardised contract for accepting a request and returning a response within web applications.

Rack as a Web Server Interface

First we will look at Rack as a Web Server Interface. The Rack contract specifies that there should be a call method that accepts an env variable and should return an array containing [status, headers, body].

If you remember back to What are Ruby Procs, we can easily create an object that has a call method by simply creating a new Proc!

Create a new config.ru file and copy the following code:

run Proc.new { |env|
      ['200', { 'Content-Type' => 'text/html' }, ['Hello world!']]
    }

Now if you run the following command and go to https://localhost:9292 you should see the correct response in the browser:

$ rackup

Congratulations! You just wrote your first Rack application!

As you can probably imagine, there is a lot more to Rack than this simple example. But hopefully you can see the power and simplicity of Rack and how it enables you to quickly write applications that can receive and respond to HTTP requests.

Rack as Middleware

The second important characteristic of Rack is that it can also act as Middleware. Defining Middleware is a good way to separate the different jobs that should be performed during the application’s request lifecycle.

For example, most applications require authentication, caching, setting cookies or potentially any number of other jobs.

By defining each task as a separate middleware layer, you adhere to the Single Responsibility Principle.

And because Rack is not dependant on any one particular framework, Middlewares can be reused between projects!

Middlewares are also very easy to create. For example, here is a Middleware that will reverse the body:

class Reverse
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)

    body = body.map(&:reverse)

    [status, headers, body]
  end
end

First we accept an instance of app through the initialize method.

Next we provide a call method that accepts an env variable just as we did before.

In the call method we first grab the status, headers and body.

Next we map over each line of the body and call the reverse method.

Finally we return an array containing [status, headers, body].

To use this Middleware, update your config.ru file to look like this:

app =
  Proc.new do |env|
    ['200', { 'Content-Type' => 'text/html' }, ['Hello world!']]
  end

class Reverse
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)

    body = body.map(&:reverse)

    [status, headers, body]
  end
end

use Reverse
run app

Now if you kill the current application, start it up again and then view it in the browser you should see the correct response.

Conclusion

Rack is a very powerful component of the Ruby ecosystem and it is widely adopted so you will see it used extensively in all major web frameworks or smaller HTTP based projects.

By defining the contract, Rack makes it really easy to make applications that can be accessed via HTTP requests.

And by having that standard contract, it makes it really easy to write Middleware that can be shared amongst different projects.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.