You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
If you're writing web applications with Ruby there comes a time when you might need something a lot simpler, or even faster, than Ruby on Rails or the Sinatra micro-framework. Enter Rack.
Rack describes itself as follows:
> Rack provides a minimal interface between webservers supporting Ruby and Ruby frameworks.
Before Rack came along Ruby web frameworks all implemented their own interfaces, which made it incredibly difficult to write web servers for them, or to share code between two different frameworks. Now almost all Ruby web frameworks implement Rack, including Rails and Sinatra, meaning that these applications can now behave in a similar fashion to one another.
At it's core Rack provides a great set of tools to allow you to build the most simple web application or interface you can. Rack applications can be written in a single line of code. But we're getting ahead of ourselves a bit.
Installing Rack is simple. You just need to install the `rack` gem.
```
gem install rack
```
I would also recommend installing a Rack compatible server, such as `thin`, but `Webrick` should work just as well.
```
gem install thin
```
With Rack installed let's create our first Rack application. Like I said Rack applications can be written in just one line of code, should we desire.
```ruby
run ->(env) { [200, {"Content-Type" => "text/html"}, ["Hello World!"]] }
```
We can run this application like such:
```
rackup lambda_example.ru
```
If we now visit `http://localhost:9292` we should be greeted with `Hello World!` as we expect.
So how did this work? Rack has two simple requirements. The first is that whatever you ask it to run must respond to a `call` method, which is why a `lambda` or `Proc` works here. This `call` method will be called with a `Hash`-like object that contains the request, as well as other environmental data.
The `call` method must return an `Array` that contains three elements.
The first element is an integer that represents the status of the request, so in our example we return a status of `200`.
The second element is a `Hash` that contains any headers you want to return with the response.
The last element is the body of the response. Whatever object you return here must be an enumerable object, such as an `Array` or an `IO` object.
Since Rack will accept any object that responds to a `call` method we can enhance our example further by using a full Ruby class instead of just a `lambda`.
We're now doing the same thing, but we are using a class instead of a `lambda`.
As I said earlier Rack provides a great toolkit for writing Ruby applications. So let's use a little bit of that in this application. Let's add a few routes.
First we create a new `Rack::Request` object using the `env` hash that was passed in from the request. We can then use the `path_info` attribute on the request and build a simple `case` statement to handling our "routing".
If the path matches `/hello/` we'll return a status of `200` and the body of `"Hello World!"`. If the path matches `/goodbye/` we'll return a status of `500` and the body of `"Goodbye Cruel World!"`. All other requests will get a `404` response.
Now if we open a browser and navigate to `http://localhost:9292` we should see the `404` page because none of our routes matched. We can confirm that status of `404` in the browsers inspector window.
Navigating to `http://localhost:9292/hello` and `http://localhost:9292/goodbye` give us the results we would expect.
Fantastic, we now have some simple routing in our application, however, this won't scale very well, and `case` statements aren't going to get us where we need to be.
Let's take this example one step further and build a simple web framework that will handle `GET` requests. I'll leave it up to you to support the other HTTP protocols.
Let's start with what we want the application to look like, and then we'll fill in the details when we write our simple framework.
```ruby
$:.unshift File.dirname(__FILE__)
require'simple_framework0'
route("/hello") do
"Hello #{params['name'] ||"World"}!"
end
route("/goodbye") do
status 500
"Goodbye Cruel World!"
end
run SimpleFramework.app
```
The first few lines are simply there to add the current directory to Ruby's load path and require, what will be, our framework.
Our framework, named `SimpleFramework`, gives us a `route` method that takes a path and a block. If the request matches the path then the block will be evaluated and the last line of the block will be the body of the response.
In the `hello` block you can see we are referencing a `params` hash, so we'll need to make sure that is available to the block. In the `goodbye` block we want to set the status of the response to `500`, so we'll need a `status` method that let's us do that.
Now that we know what we want the `SimpleFramework` to look like, let's actually implement it.
```ruby
require'action'
classSimpleFramework
defself.app
@app||=begin
Rack::Builder.newdo
map "/"do
run ->(env) {[404, {'Content-Type' => 'text/plain'}, ['Page Not Found!']] }
end
end
end
end
end
defroute(pattern, &block)
SimpleFramework.app.map(pattern) do
run Action.new(&block)
end
end
```
The first thing we are doing is requiring a filed named `action`, we'll look at that in just one second. Let's look at the `SimpleFramework` class first.
The `SimpleFramework` class is quite simple, thanks to the the `Rack::Builder` class that Rack offers us. The `app` method we've defined will return an instance of the `Rack::Builder` class. `Rack::Builder` let's us easily construct a Rack application by letting us chain together a bunch of smaller Rack applications.
In the `app` method when we create the instance of the `Rack::Builder` class we are going to map a default Rack application to run, should no other path match.
Finally we define a `route` method at the root object space to let us map our actions.
The real meat of `SimpleFramework` is the `Action` class, and even that is pretty simple and straight forward.
```ruby
classAction
attr_reader:headers, :body, :request
definitialize(&block)
@block= block
@status=200
@headers= {"Content-Type" => "text/html"}
@body=""
end
defstatus(value=nil)
value ?@status= value : @status
end
defparams
request.params
end
defcall(env)
@request=Rack::Request.new(env)
@body=self.instance_eval(&@block)
[status, headers, [body]]
end
end
```
When we initialize a new instance of the `Action` class we define a few `attr_reader` methods and set them to some basic defaults.
We implement the `status` and `params` methods so we have access to them as we saw earlier.
Finally, we implement a `call` method that takes in the `env` hash and creates a `Rack::Request` object from it. We then use `instance_eval` to evaluate the original block in the context of the `Action` object so it has access to all of the methods and goodies in the `Action` class.
All that is left to do is return the appropriate Rack response `Array`.
When we navigate to `http://localhost:9292/hello` in the browser we see `Hello World!` just as expected. If we pass `name=Mark` on the query string we should now see `Hello Mark!` proving that we do have access to the request parameters, just like we wanted.
A quick look at `http://localhost:9292/goodbye` confirms that we are getting a status of `500` and the text of `Goodbye Cruel World!`.
Well, that's a quick look at the basics of the Rack library. There is a lot more to it, and I highly suggest you read its very thorough documentation.
One word of caution though, from someone who's been there, it is very easy to go down this path and end up building your own fully featured framework. Before you do that, make sure that something like Sinatra, doesn't already float your boat.