Slide 1

Slide 1 text

An Introduction to Rack Benjamin Curtis / [email protected] / @stympy

Slide 2

Slide 2 text

A minimal API for connecting web servers and web frameworks What is Rack?

Slide 3

Slide 3 text

You get an environment hash, and you return an array of status code, headers hash, and response body Just how minimal?

Slide 4

Slide 4 text

Or, in other words… # my_rack_app.rb
 
 require 'rack'
 
 app = Proc.new do |env|
 ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']]
 end
 
 Rack::Handler::WEBrick.run app

Slide 5

Slide 5 text

The Internet talks to your web server that talks to a Ruby web server that serves your application How does it work?

Slide 6

Slide 6 text

That’s a lot of web servers! o.O

Slide 7

Slide 7 text

Middleware But, Rails! Why do I care?

Slide 8

Slide 8 text

It’s a Freedom Patch for the Interwebs! Middlewhat?

Slide 9

Slide 9 text

The refresh button, it does nothing! Rack::Etag

Slide 10

Slide 10 text

Please Hammer, don’t hurt ‘em Rack::Attack

Slide 11

Slide 11 text

Bit conservation to fight global warming! Rack::Deflater

Slide 12

Slide 12 text

github.com/rack/rack-contrib And many more…

Slide 13

Slide 13 text

You can build an entire web app* with Rack But it’s not just middleware *For certain values of app

Slide 14

Slide 14 text

Rack it up require './compressed_requests'
 require './key_checker'
 require './collector'
 
 use CompressedRequests
 use KeyChecker
 
 run Collector


Slide 15

Slide 15 text

Decompress class CompressedRequests
 ValidMethods = { 'POST' => true, 'PUT' => true }.freeze
 ValidEncodings = { 'gzip' => true, 'deflate' => true }.freeze
 
 def initialize(app)
 @app = app
 end
 
 def call(env)
 if ValidMethods[env['REQUEST_METHOD']] && ValidEncodings[env['HTTP_CONTENT_ENCODING']]
 extracted = case env['HTTP_CONTENT_ENCODING']
 when 'gzip' then Zlib::GzipReader.new(env['rack.input']).read
 when 'deflate' then Zlib::Inflate.inflate(env['rack.input'].read)
 end
 
 env.delete('HTTP_CONTENT_ENCODING')
 env['CONTENT_LENGTH'] = extracted.length
 env['rack.input'] = StringIO.new(extracted)
 end
 
 @app.call(env)
 end
 end


Slide 16

Slide 16 text

Authenticate class KeyChecker
 def initialize(app)
 @app = app
 end
 
 def call(env)
 @req = Rack::Request.new(env)
 env['hb.api_key'] = @api_key = @req.env['HTTP_X_API_KEY'] || @req.params['api_key']
 
 if @api_key.nil? || @api_key == ""
 return bail_with_message("Invalid API key")
 end
 @app.call(env)
 end
 
 def bail_with_message(message, code = 403)
 if @req.env['HTTP_ACCEPT'].to_s.match(%r{text/html})
 [ code, { 'Content-Type' => 'text/html' }, [ message ] ]
 else
 [ code, { 'Content-Type' => 'application/json' }, [ %Q{{"error":"#{message}"}} ] ]
 end
 end
 end


Slide 17

Slide 17 text

Do something class Collector
 Headers = {
 html: { "Content-Type" => "text/html" },
 json: { "Content-Type" => "application/json" },
 }.freeze
 def self.call(env)
 req = Rack::Request.new(env)
 api_key = env['hb.api_key']
 
 case req.path_info
 when “/v1/notices" id = do_some_work(api_key, req)
 render(%Q{{"id":"#{id}"}})
 end end
 
 def self.render(body, headers = Headers[:json], status = 201)
 [ status, headers, [ body ] ]
 end
 
 end

Slide 18

Slide 18 text

Check out a microframework… mount a Rack endpoint in your Rails app… go wild! Now what?

Slide 19

Slide 19 text

Thanks! Benjamin Curtis / [email protected] / @stympy speakerdeck.com/stympy