Sep 09, 2015
Table of contents:
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.
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.
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.
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.
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.