LUIS VASCONCELLOS

Rackless Ruby Servers


Rackless Ruby Servers

In this article we are going to GET a Hello, World! text response from localhost:3000 using four different Ruby servers, both with and without the Rack gem. The goal is to make it clearer why Rack was created in the first place, and why to this day it’s still so relevant.

Getting Ready

The examples to come will help to illustrate the role that Rack, specially the Rack::Handler module, plays on being that minimal interface between Ruby servers and Ruby web frameworks, as stated by Rack documentation. To run each one of the examples in this article:

$ ruby server.rb

To verify that example works as expected:

$ curl localhost:3000

Which should output a simple:

> Hello, World!

Webrick

Off Rack

Since Webrick comes with Ruby, one can directly require, mount and run the server, without needing to install any third party libraries:

require 'webrick'

server = WEBrick::HTTPServer.new(:Port => 3000)

server.mount_proc '/' do |req, res|
  res.body = 'Hello, World!'
end

server.start

On Rack

On Rack, using its famous minimal interface, the same behaviour could be achieved with:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'rack'
end

class WebrickRackApp
  def call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']]
  end
end

Rack::Handler::WEBrick.run(WebrickRackApp.new, Port: 3000)

Mongrel

Off Rack

Mongrel, Thin and Puma will require a gem installation. For sake of simplicity, in this article each gem will be installed using an embedded Gemfile definition. So the Mongrel example would look like this:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'mongrel', '1.2.0.pre2'
end

class BasicHttpHandler < Mongrel::HttpHandler
  def process(request, response)
    response.start(200) do |head, output|
      head["Content-Type"] = "text/plain"
      output.write("Hello, World!")
    end
  end
end

mongrel = Mongrel::HttpServer.new("0.0.0.0", "3000")
mongrel.register("/", BasicHttpHandler.new).run.join

On Rack

On Rack it will be necessary to use a specific rack version, since Mongrel support was removed on one of Rack’s last releases, but otherwise the interface remains the same:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'rack', '1.6.10'
  gem 'mongrel', '1.2.0.pre2'
end

class MongrelRackApp
  def call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']]
  end
end

Rack::Handler::Mongrel.run(MongrelRackApp.new, Port: 3000)

Thin

Thin and Puma examples are going to be a bit closer to the Rack specification even for a simple Hello, World!, which was actually a bit surprising to be honest.

That is because, different from the previously shown Webrick and Mongrel, they both expect an object with a Rack app interface, even when directly launched without their respective Rack handlers.

Off Rack

Thin is definitely the one that requires the least amount of code to launch the simple Hello, World! output. This how one can achieve it:


require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'thin'
end

# literally a rack app
app = ->(env) { ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']] }
Thin::Server.new('localhost', 3000, app, {}).start

On Rack

On Rack, the interface remains the same from the previous examples:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'rack'
  gem 'thin'
end

class ThinRackApp
  def call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']]
  end
end

Rack::Handler::Thin.run(ThinRackApp.new, Port: 3000)

Puma

Off Rack

This is how a Hello, World! with Puma could be achieved:


require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'puma'
end

require 'puma/configuration'

# literally a rack app
app = ->(env) { ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']] }

config = Puma::Configuration.new({}) do |user_config, _|
  user_config.app(app)
  user_config.port(3000)
end

Puma::Launcher.new(config).run

On Rack

In Puma case, the rack gem doesn’t come with the Rack::Handler::Puma class, as one would expect, since puma comes with its own rack handler.

And as expected, once again, the interface remains the same:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'rack'
  gem 'puma'
end

require 'rack/handler/puma'

class PumaRackApp
  def call(env)
    ['200', {'Content-Type' => 'text/html'}, ['Hello, World!']]
  end
end

Rack::Handler::Puma.run(PumaRackApp.new, Port: 3000)

back up | home connect on twitter or github