I remember when I first learned how to host a PHP site. The stunning realization that Apache was just a way to expose files to the internet. All of the sudden websites didn’t feel so complex at all. They were just documents that had some extra functionality to them.

The simplicity of hosting PHP applications is possibly the biggest reason why it continues to power most of the web today.

Now, 3rd party hosting has made it easier than ever to host applications of any language - including Ruby. Heroku and other companies that specialize in Ruby hosting have abstracted away much of the internals of how to actually hook up a Ruby application to the web.

Most Ruby developers will use Ruby on Rails primarily, and with that choice you have the ability to not think about how your web server interacts with Ruby - because the hosting takes care of it for you.

However, coming from a PHP background like you - it wasn’t all that difficult to host PHP applications yourself. It’s reassuring to know how it works under the hood incase your application falls outside of the 80% use case for these 3rd party hosting providers.

Knowing how Ruby connects with the web won’t be the most commonly used knowledge for day to day Ruby development - especially if you’re using already great Ruby frameworks and hosting providers (which I totally recommend). However, I believe professionals of their crafts should at least have a basic understanding of what’s going on under the hood, and be empowered to fix it with their own hands instead of opening a support ticket.

What the heck are Passenger, Puma and Unicorn for?

As you know, all web applications respond to the HTTP protocol. An HTTP request is send to your web server, the server routes the request to the right PHP file, the PHP file interprets the request, processes the output and the web server sends back the response in HTML format via HTTP. Simple.

When it comes to Web Ruby applications that first step is the same - your web server - Apache or Nginx - will receive the HTTP request.

However, there’s an intermediary between Nginx and your Ruby Application. In fact, PHP also requires a layer between the application and the HTTP server, for Nginx you’ll need php-fpm to process the PHP files hosted by Nginx. For those who use Apache mod-php may sound familiar, both of these components help the web server execute PHP files and spit out HTML.

Just like php-fpm and mod-php, Ruby requires a component between itself and the webserver in order to speak HTTP. This component is called an Application Server.

Where the Web Server is responsible for routing requests and delivering static HTML pages, and Application Server is responsible for interpreting the request, processing it in the server side of choice to produce HTML and then giving it back to the Web Server.

In Ruby you have several different options: Puma, Passenger and Unicorn. Essentially, they are all HTTP servers, but they can do some thing that Nginx or Apache cannot and that is execute Ruby.

Here’s an example of a Passenger hosted Ruby application in an Nginx virtual host file:

server {
    listen 80;
    server_name phptoruby.io;

    # Let Nginx and Passenger know where your app's 'public' directory is
    root /home/pierce/php_to_ruby/public;

    # Turn on Passenger
    passenger_enabled on;
    # Let Passenger know where Ruby is installed so it can interpret the .rb files
    passenger_ruby /usr/local/bin/ruby;
}

For basic hosting, that’s about it. There are a plethora of options you can get into and customize your Application Server of choice with. That dives into more advanced topics like threads, but for deploying your own Ruby application from a blank VPS (Virtual Private Server) - this is all you need to know.

Ruby’s Rack & You

PHP is a language tailored for web application development. In no other language do you have so many super global variables that are specialized for HTTP. For example you have $_GET and $_POST at your disposal at any time in a PHP file. Either one of those variables contains each query string or POST argument.

However, Ruby is a general purpose language. It wasn’t born for the web specifically like PHP is. It just so happens that Rails has become the de-facto standard for R.A.D. (Rapid Application Development) application development.

So know you how how Nginx connects with a Ruby application through an Application Server like Passenger. But how does Ruby access HTTP related things, like the HTTP request? Does Passenger “create” super globals like PHP?

Not really. The component that connects Ruby to the HTTP request and response at the application level is actually this pattern called Rack.

Rack is a standardization of Ruby Web Applications. Sinatra and Ruby on Rails utilize this pattern to integrate with the request and deliver a reponse that your Application Server can understand.

It sounds mighty complicated but it’s actually not. If you’ve ever used something with the Middleware pattern - much like Laravel’s Middleware or GuzzleHttp’s Middleware you’ll instantly recognize how Rack works.

You can think of Rack as your index.php file for a PHP application. It’s the gateway to your application.

A Rack application only has to abide by a few simple rules:

  1. The Rack application must respond to .call - so any Lamdba or Proc will work or a class implementing the .call function
  2. The Rack application’s .call method must return something that responds to .each - a.k.a. an Array

This .call method will accept a single argument - env. env is shortened for environment and it is a Hash that contains of the variables pertaining to the HTTP request and the server itself. Essentially it’s Ruby’s equivalent to $_SERVER, $_GET, $_POST, etc.

Here’s an example of a simple Rack application:


  require 'rack'
  
  ruby_web_app = Proc.new do |env|
      ['200', {'Content-Type' => 'text/html'}, ['We just made a Ruby Web Application without any Framework.']]
  end
   
  # Now let's use Rack's Handler to execute our first Ruby Web Application:
  Rack::Handler::WEBrick.run(ruby_web_app)

As you can probably guess - the Array returned by ruby_web_app.call is essentially our HTTP response. We returned a 200 HTTP status code, a header of Content-Type: text/html and an HTML page with the content of We just made a Ruby Web Application without any Framework..

But this is Ruby, shouldn’t we do this in OOP style?

Absolutely. So let’s rewrite our Rack application in a class:


  require 'rack'
  
  class RubyWebApp
    def call(env)
      ['200', {'Content-Type' => 'text/html'}, ['We just made a Ruby Web Application without any Framework.']]
    end
  end

  # Now let's use Rack's Handler to execute our OOP style Ruby Web Application:
  Rack::Handler::WEBrick.run(RubyWebApp.new)

It’s that simple.

If you’re still not understanding it - think of it in terms of a PHP Interface:

/**
 * Rack PHPified
 */
interface Rack {

  /**
   * Calling the web application
   * 
   * @param Array $env
   * @return Array $response
   */
  public function call(Array $env) : Array;
}

Rack’s call() method accepts an associative array of variables - things like the headers, the query string, POST variables, etc. Just like a normal request.

use Rack\WEBrick;

class RackPHPified implements Rack {
  /**
   * Calling the web application
   *
   * @param Array $env
   * @return Array
   */
   public function call(Array $env) {
      return [
        '200',
        ['Content-Type' => 'text/html'],
        'This is what a Rack application would look like in PHP.'
      ];
   }
}

// WEBrick for PHP isn't a real thing, but you get the idea.
WEBrick::run(new RackPHPified);