RubyGuides
Share this post!

A Quick Analysis of How Sinatra Works

What happens when you require sinatra into your project?

How does route matching work?

How are requests & responses processed?

So many questions, but so little time…

No problem!

I did the hard work for you & put together this article where I answer these questions & more!

Sinatra Initialization

It all starts with one file: sinatra.rb.

All this file does is to require main.rb, not very exciting right?

But here is where it gets more interesting.

Inside main.rb we will find a require for base.rb & we will also find the code for option parsing (port, environment, quiet mode, etc.).

Sinatra uses optparse, from Ruby’s standard library.

Another important thing happening here is the at_exit block:

This is a bit of code that will execute when the program ends.

What happens is that all your code will be read by Ruby & since you don’t have any loops, sleeps or anything like that your program will end naturally, but just before it ends the at_exit block will trigger.

When that happens Sinatra will take over & start a web server so it can handle requests:

Oh and another important thing happens here:

This defines the Sinatra DSL methods like get, post & set.

That’s why you can do this:

Request & Response Processing

Ok, so at this point we have a running server ready to accept new connections.

But what happens when a new connection is received?

Well Sinatra, just like Rails & other Ruby web frameworks, uses the Rack gem to handle all the lower level stuff.

Rack expects a call method to be available on your application. That’s just an object that you give to Rack when you initialize it.

In the case of Sinatra this object is the Sinatra::Base class.

Here’s the method:

It seems like we need to investigate the dispatch! method next to show how a request is handled.

Here’s that method:

The request in broken into 4 steps:

  1. Static files are checked first. These are files like css, js & images. This setting is enabled by default if a directory named “public” exists
  2. The before filter is run
  3. Route matching
  4. The after filter is run

Now we can dig into each step to see what happens in more detail.

Serving Static Files

The static! method is pretty simple:

This code checks if the requested file exists, then it sets the “Cache Control” HTTP header.

On the last line it calls send_file & it does just what the name says 🙂

Before Filter

A before filter allows you to run code before trying to find a matching route.

This is how a filter is added:

As you can see filters is just a hash with two keys, one for each filter type.

But what is compile!?

This method returns an array with 3 elements: a pattern, an array of conditions & a wrapper.

The same method is used for generating routes (when you use a get or post block):

From this we can learn that Sinatra filters behave & work the same way as routes.

Route Matching

The next step in the request processing cycle is route matching:

This code goes over every single route that matches the request method (get, post, etc).

Route matching happens inside the process_route method:

Where pattern is a regular expression.

If a route matches both the path & the conditions then route_eval will be called, which evaluates the block (the body of your get / post route) & ends the route matching process.

This uses the unusual catch / throw mechanism for flow control.

I would recommend against it because it can be very confusing to follow the flow of code, but it’s interesting to see a real-world example of this feature in use.

Response Building

The last step of the request cycle is to prepare the response.

So where does the response go?

The invoke method gathers the response like this:

This result is assigned to the response body using the body method:

Now if we look back where we started, the call method, we will find this line of code:

This calls the finish method on @response, which is a Rack::Response object.

In other words, this will actually trigger the response to be sent to the client.

Bonus: How The Set Method Works

The set method is part of Sinatra’s DSL (Domain-Specific Language) & it lets you set configuration options anywhere in your Sinatra application.

Example:

Every time you use set Sinatra creates 3 methods (via metaprogramming):

The 3 methods are (with public_folder as example):

  • public_folder
  • public_folder=
  • public_folder?

This method will also call the setter method (public_folder=) if it already exists:

Remember that metaprogramming is not free, so I would just stick with an options hash. You don’t need those fancy methods.

Summary

You learned how Sinatra gets initialized, how it handles a request & the different steps it takes until a response can be produced.

Don’t forget to share this post with other Ruby developers so they can learn from it too 🙂

2 comments
mike says last month

Good post. It makes me learn about more about sinatra.

    Jesus Castello says last month

    Thanks for reading! 🙂

Comments are closed