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.
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!
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, 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, 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 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 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.
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, 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)
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
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)