Off the rails
A journey to explore the alternative options when it comes to building web services
@marjaimate - BP.RB 2016 March
We help forging your software, product and company
Rails all the things?
Rails is the standout option when people start to write a new application. But should we start here always?
Sinatra
Sinatra
- Supports other engines (like thin) not just WEBrick
- Built in support for templating languages
- Error handling
- Helpers and callbacks
- Comprehensive guides
Sinatra
require "sinatra"
require 'sinatra/base'
class Libre < Sinatra::Base
get "/" do
redirect "/ciudades"
end
get "/ciudades" do
ciudades = YAML.load_file("ciudades.yml")
ciudades.to_json
end
get "/ciudades/:ciudad" do |ciudad|
ciudades = YAML.load_file("ciudades.yml")
ciudades[ciudad].to_json
end
end
Sinatra
get '/' do
.. show something ..
end
post '/' do
.. create something ..
end
put '/' do
.. replace something ..
end
patch '/' do
.. modify something ..
end
delete '/' do
.. annihilate something ..
end
options '/' do
.. appease something ..
end
# Request param binding
get '/hello/:name' do
# matches "GET /hello/foo" and "GET /hello/bar"
"Hello #{params['name']}!"
end
# Splats
get '/download/*.*' do
# matches /download/path/to/file.xml
params['splat'] # => ["path/to/file", "xml"]
end
# Regular expressions
get %r{/hello/([\w]+)} do |c|
# Matches "GET /meta/hello/world", etc.
"Hello, #{c}!"
end
Grape
Grape
- REST-like API micro-framework
- Can be used alongside Sinatra or Rails
- Geared towards APIs (routing engine, versioning)
- Requirement definition per param, per resource
- Exception and error handling
- Comprehensive guides
Grape
require "grape"
class Libre < Grape::API
format :json
helpers do
def ciudades
YAML.load_file("ciudades.yml")
end
end
desc "Ciudades"
resource :ciudades do
get do
ciudades.to_json
end
route_param :ciudad do
get do
ciudades[params[:ciudad]].to_json
end
end
end
end
Grape
# Describing methods
desc 'Returns your public timeline.' do
detail 'more details'
params API::Entities::Status.documentation
success API::Entities::Entity
failure [[401, 'Unauthorized', 'Entities::Error']]
named 'My named route'
headers XAuthToken: {
description: 'Validates your identity',
required: true
},
XOptionalHeader: {
description: 'Not really needed',
required: false
}
end
get :public_timeline do
Status.limit(20)
end
# Parameter validation
params do
requires :id, type: Integer
optional :text, type: String, regexp: /\A[a-z]+\z/
group :media do
requires :url
end
optional :audio do
requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3
end
mutually_exclusive :media, :audio
end
put ':id' do
# params[:id] is an Integer
end
Webmachine
Webmachine
- Port of webmachine, an Erlang library
- More of a toolkit for building HTTP-friendly applications
- Streaming/chunked response bodies
- Implemented as a Finite State Machine - It uses the facts about your resource to determine the flow though the FSM in order to produce a response.
- Comprehensive guides
Webmachine
class CiudadesResource < Webmachine::Resource
def allowed_methods
['GET','HEAD']
end
def content_types_provided
[['application/json', :to_json]]
end
# Return a Truthy or Falsey value
def resource_exists?
ciudades
end
def ciudades
@ciudades = YAML.load_file("ciudades.yml")
end
def to_json
ciudades.to_json
end
end
Webmachine
# Routing
MyApp = Webmachine::Application.new do |app|
# Configure your app like this:
app.configure do |config|
config.port = 9292
config.adapter = :WEBrick
end
# And add routes like this:
app.add_route ['ciudades'], CiudadesResource
app.add_route ['ciudades', :ciudad], CiudadResource
end
# Authentication using HTTP Auth
class MySecureResource < Webmachine::Resource
include Webmachine::Resource::Authentication
def is_authorized?(authorization_header)
basic_auth(authorization_header, "My Application") do |username, password|
@user = User.find_by_username(username)
!@user.nil? && @user.auth?(password)
end
end
end
Cuba
Cuba
- Real micro framework - less than 200 lines of code
- Cuba does one thing and does it well, handle HTTP requests
- Easy to mount other engines for RACK (thin), or templates (jbuilder, haml)
- Supports plugins and helpers
- Comprehensive guides
Cuba
Cuba.define do
on get do
on "ciudades" do
ciudades = YAML.load_file("ciudades.yml")
# Index Cities
on root do
res.write ciudades.to_json
end
# Show City
on ":ciudad" do |ciudad|
res.write ciudades[ciudad].to_json
end
end
on root do
res.redirect "/ciudades"
end
end
end
Cuba
# CSRF
Cuba.use Rack::Session::Cookie, :secret => "s3cr3t"
Cuba.plugin Cuba::Safe
Cuba.define do
on csrf.unsafe? do
csrf.reset!
res.status = 403
res.write("Not authorized")
halt(res.finish)
end
# Here comes the rest of your application
# ...
end
Cuba.define do
on get do
on "hello" do
on root do
res.write "hello world"
end
end
end
end
# Requests:
#
# GET / # 404
# GET /hello # 200
# GET /hello/world # 404
Comparison
Total Time | Req / sec | Longest Req | KB / sec | |
---|---|---|---|---|
Rails | 6.444 | 155.19 | 151 ms | 128.52 |
Sinatra | 4.325 | 231.20 | 147 ms | 151.95 |
Grape | 4.092 | 244.41 | 104 ms | 151.08 |
Webmachine | 4.310 | 232.00 | 118 ms | 134.35 |
Cuba | 4.068 | 245.82 | 94 ms | 196.13 |
- 1000 requests concurrency of 10
- ruby 2.2.0p0 [x86_64-darwin13]
Links to examples
- https://github.com/marjaimate/libre-sinatra
- https://github.com/marjaimate/libre-grape
- https://github.com/marjaimate/libre-webmachine
- https://github.com/marjaimate/libre-cuba
- https://github.com/marjaimate/libre-rails
Off the rails
By Máté Marjai
Off the rails
Exploring alternative options on ruby frameworks
- 668