What is a server and how does it relate to my Rails app?
I learned the basics of Ruby on Rails web application development a few years ago. At that time, my understanding how that system works was as follows
- Find articles describing things Rails allows you to do easily
- Decide on an app that requires only those things
- Follow the instructions in those articles to implement your app idea
- Push the app to Heroku
- Now the app is accessible to anyone on the World Wide Web
The fact that it is so easy to do such a thing is nothing short of magical. Especially while you have no idea how any of it works. Now that I am slightly more knowledgeable about how software systems are put together and deployed, I’d like to take a slightly more nuanced stab at what really was going on when the above 5 steps were executed.
How Rails makes things so easy
Ruby on Rails was created out of frustration with “heavyweight” web application development frameworks, such as ones from the late 90’s that use Java. It gives you base-classes for all the important parts of your program so that you only need to write (and even see) code for the pieces that are unique to your system. It generates a base project with everything you need already included in a well-structured layout that helps you make sense of what is going on. Through some clever usage of the Ruby programming language, it creates methods named after the components you implemented, and makes them available in the places you may need them. This has led to a culture of natural-language inspired community-built APIs that enable you to easily do things only the most modern websites have.
How does traffic get into and out of Rails?
Maybe there is another way to do it, but at least by default Rails uses Rack.
Rack is a “webserver interface”. A Rack application has a Ruby object that has
a method that receives parsed HTTP requests in a well-specified format, and
then calls a method (called call
) which receives the parsed HTTP request as
a Ruby object, and returns a parsed HTTP response in a well-specified format
(specifically a Ruby array structured like [response_code, {headers_map},
[body_lines]]
).
A Rails application implements Rack’s call
method. What that call
method
actually returns for any given HTTP request argument is defined by all your
Rails code, e.g. your router, controllers, models, views, etc.
What about Rack then?
Fine, now we understand that Rack passes the HTTP request to Rails, and Rails returns the HTTP response to Rack. But how did Rack get the HTTP request in the first place? Answer: from a Web Server.
A web server suitable for Rack would be a program that
- listens on a TCP port
- connects to clients on that port
- receives the HTTP requests from those clients
- parses them into the format that Rack expects
- Calls the Rack method
call
using that format - Receives the HTTP response from Rack
- Converts that into the raw HTTP response format
- Sends the response to the client
There are many such suitable web servers, such as Unicorn and Thin.
So what is nginx?
It seems to me that one does not typically use nginx to call Rack’s call
method (though this might be possible to do). One typically uses nginx as a
waypoint of traffic bound for the application, but in need of some sort of
pre-processing. That pre-processing could take multiple forms. Maybe there is
a cache sitting outside of Rails itself, and nginx can be setup to return
results from the cache instead of proxying requests into the server which
interacts with Rails itself. This will improve performance for each client who
hits such a cache, and will increase the load the system is capable of
supporting overall. Nginx might know of multiple servers connected to
different instances of the Rails app running concurrently, and will choose
which server to send each request to in such a way as to distribute load
across the Rails applications (“load balancing”). Nginx might “terminate SSL”,
meaning that clients connect to the address on which nginx is listening, and
will send their request to nginx over the TLS protocol, but nginx will decrypt
that request before passing it wherever it will go next; then nginx will
encrypt the response according to the TLS state and the client will know how
to decrypt the response (and ideally no one else will know how to decrypt it).
What Heroku does
Using the simple git push heroku
workflow, Heroku takes care of all the
aspects of managing the deployment of your Rails server. It will provide a
virtual server to run your Rails code. It will provide a server which speaks
Rack to receive requests, invoke your Rails code, and return the response to
the client. It has DNS entries installed in the relevant places on the
internet so that you can choose a name for your app to be accessed by your
friends in their homes using their browsers. It will do a whole lot more, such
as autoscaling, caching, and other things beyond the scope of this post
because I don’t know about them.