This post is the first part in a four part series on how to build your own web framework with Rack. The 4-part series will cover what rack is, how to use it to handle incoming requests, how to use templating techniques to return a response, and finally, how to extract common code to plant the seeds for your very own web application development framework. Here in part 1, we’ll give a short introduction on what Rack is and how to use it. We’ll end things by building a simple Rack application.
What is Rack?
Rack is a web server interface that provides a fluid API for creating web applications. You may know of several popular frameworks that are “rack based”, such as Sinatra or Ruby on Rails. We call those frameworks rack based because they adhere to the Rack interface to easily communicate between the server and the client. In some ways, you can think of Rack as a protocol or specification (though it’s slightly more than that).
Before Rack existed, web developers would have to write code to connect their application to work with different web servers and they had to specify and work with several different types of connection mechanisms. Every web server required a different connection and communication mechanism, making supporting multiple web servers difficult. Framework authors — the developers working on projects like Sinatra or Rails — had to continuously reinvent the wheel whenever they wanted to support a new web server. What was needed was a way to abstract away the mundane work of connecting and communicating with the web serving and content generating tiers of Ruby web applications. That is exactly what Rack provides for us. Rack gives developers a consistent interface when working with Rack compatible servers, effectively giving web server developers and application framework developers a common language.
This blog post will walk you through building a Ruby application that communicates with a web server through the Rack interface. The finished application will be a Ruby web application running on a Rack compatible web server.
Before starting this blog post, it’ll be helpful to have a solid understanding of both HTTP and Ruby. If you already have a good grasp of both, then it’s fine to proceed forward in this article. If you don’t have a good grasp of HTTP and Ruby, read through the two books below first:
Further, you’ll need to be aware of things like Bundler and Gemfiles. If you’re not sure what those are, read our book on the topic:
As we go through this blog post, we’ll be building up a simple web application. With that in mind, we’ll have to set some foundation so that we can proceed smoothly with this process.
First, create a new project. On your local machine, just create a new directory and call it
my_framework or another name of your choosing. Make sure this directory is not nested inside of any other projects or applications. From now on, this directory will be your project or application root directory.
Next, inside your project root directory, let’s make a
Gemfile for our application. This way, we can specify any dependencies needed before proceeding forward.
1 2 3 4 5
We’ll certainly need the
rack gem, since without it, we won’t be able to create our Rack application. The existence of a
Gemfile implies that we’ll use Bundler to handle dependencies in our application. If you don’t have Bundler, make sure to install it now:
After that, go ahead and install the dependencies specified in the
Gemfile, which is just the
rack gem. Issue the following command from your project root directory:
With that out of the way, we’re almost ready to get started. Let’s briefly go over the key components of a web application, and then we can get started on making one.
What Makes A Rack Application
We mentioned earlier what Rack is; it’s a specification for connecting our application code to the web server, and also our application to the client. It sets and allow us to utilize a standardized methodology for communicating HTTP requests and responses between the client and the server. To accommodate this standard, Rack has some very specific conventions in place. Here is what you need to make your Ruby code into a Rack application:
- Create a “rackup” file: this is a configuration file that specifies what to run and how to run it. A rackup file uses the file extension
- The rack application we use in the rackup file must be an Ruby object that responds to the method
call(env)method takes one argument, the environment variables for this application.
call method always returns an array, containing these 3 elements :
- Status Code: represented by a string or some other data type that responds to
- Headers: these will be in the form of key-value pairs inside a hash. The key will be a header name and the corresponding value will be the value for that header.
- Response Body: this object can be anything, as long as that object can respond to an
Enumerableobject would work, as would a
StringIOobject, or even a custom object with an
eachmethod would work. The response should never just be a String by itself, but it must
yielda String value.
Together, these three elements represent the information that will be used to put together the response that is sent back to the client. If we have all of the above within our application directory, then we have the start of a Rack application. Let’s follow these instructions and get started on making a Rack application now.
A Simple Rack Application - Hello World!
Ok, we’ve laid down some key facts about Rack and what we need to work with it. Let’s start using that information to make a web application. Remember from the previous section that we need a “rackup” configuration file to run our application. Let’s start with that; by default Rack will expect it to be called
config.ru, though any file ending in
.ru would work. Let’s stick to the expected convention and call it
config.ru. We’ll create this file within our application’s root directory. Within that file, we want to run a very simple application, called
HelloWorld which will return
"Hello World!" to the client. We’ll implement all of this in a bit, but first let’s write the code we want to use.
1 2 3 4
We have two things in our configuration file, a
require_relative that loads a file called
hello_world.rb, and a call to the
run method. Rack configuration files use
run to say what application we want to run on our server. In this case, we created a
HelloWorld class which will act as our web application and is where most of our application code will be. Note that we have to require the file where this
HelloWorld class is implemented, hence the
require_relative 'hello_world' at the top.
Just a quick note for those typing along that our entire application should now have 3 files:
1 2 3 4 5
Now, let’s implement the web application logic in our
1 2 3 4 5 6 7
And there you have it, we now have all the requirements for our Rack application. We have a configuration file that tells the server what to run (the
config.ru file). We also have the application itself, the
HelloWorld class in the
hello_world.rb file. We know it is a Rack application because it has the method
call(env), and that method returns a 3 element array containing the exact information needed for a proper Rack application: a status code (string), headers (hash), and a response body (responds to
Recall that earlier we said that the response body has to be an object that responds to the
each method. In this case, we opt for an
Array, since this represents the simplest implementation that allows us to get the response body to the client.
Now it’s time to test out our application. To start a Rack application, we’ll use the
rackup command; make sure to prepend the command
bundle exec when starting up the server in your terminal. The command you use to start our web application should look like:
-p flag allows us to specify which port we want our application to run on. We chose
9595 here, but it could be any other valid number. It will accept any valid port number between 0 and 65535. Typically, we’ll use a port number that is 4 digits in length. If you don’t specify a port number the application server will default to 9292 with
Ok, let’s try and ping our server to see that it’s working correctly. We should get back a status of 200 and a response body that says “Hello World!”. Make sure that you use a separate terminal tab or window to ping the server. You can’t ping in the tab where the server is running. To ping the server we’ll use the
1 2 3 4 5 6
Great! It looks like our application is working as expected. Let’s take a look from our browser. With the server still running, open your browser to: http://localhost:9595/ (this is just requesting the root path). You should see:
You can, in fact, add anything else after the last
/, and should see the same result. Try http://localhost:9595/whatever or http://localhost:9595/hello?name=joe. No matter the request path or query parameters, our application can only respond with “Hello World!”; it’s hard coded in our app.
Note that Rack doesn’t come with its own server, but it’s smart enough to automatically try to use a sever that’s already installed on your machine. If you didn’t install any server, like Puma or Thin, then Rack will just use the default server that comes with Ruby, Webrick.
We’ve gone over quite a bit so far. Rack, while a fairly slim system for HTTP communication, still requires certain technical knowledge to understand. We also have to make sure to follow its conventions if we want to take advantage of everything that Rack has to offer us.
We learned how to set up a simple rack application with the knowledge of what makes up a rack request and a response. Setting up that application was only the first step though, since we still need to start our server via a
Once our Rack application was running, we used the
curl command to act as a client and issued a request to the server. We then issued another request from a browser and saw the same response string.
Let’s continue on and develop our application a bit more. Ideally, we want to be able to send more complex information back to the client besides just a string. We’ll take a look at how to do that in a part 2 of this series.