Slide 1

Slide 1 text

& Rack Middleware

Slide 2

Slide 2 text

What is Rack?

Slide 3

Slide 3 text

Rack is both a protocol and a gem

Slide 4

Slide 4 text

Created in 2007 By Christian Neukirchen (for reference, Rails was created in 2004)

Slide 5

Slide 5 text

MyApp Thin Unicorn Puma Passenger WEBrick Internets Rails Sinatra Lotus Server Framework Network Application RACK

Slide 6

Slide 6 text

assert app.respond_to? :call status, headers, body = app.call(env) assert status.to_i >= 100 assert headers.is_a? Hash assert body.respond_to? :each

Slide 7

Slide 7 text

class MyApp def call(env) [200, {}, ['Hello world']] end end

Slide 8

Slide 8 text

Proc.new do |env| [200,{},['Hello world']] end

Slide 9

Slide 9 text

->(env) { [200,{},['Hello world']] }

Slide 10

Slide 10 text

env and CGI

Slide 11

Slide 11 text

curl localhost:8080/hello/world?foo=bar

Slide 12

Slide 12 text

GATEWAY_INTERFACE: "CGI/1.1" HTTP_ACCEPT: "*/*" HTTP_HOST: "localhost:8080" HTTP_USER_AGENT: "curl/7.37.1" HTTP_VERSION: "HTTP/1.1" PATH_INFO: "/hello/world" QUERY_STRING: "foo=bar" REMOTE_ADDR: "::1" REMOTE_HOST: "localhost" REQUEST_METHOD: "GET" REQUEST_PATH: "/hello/world" REQUEST_URI: "http://localhost:8080/hello/world?foo=bar" SCRIPT_NAME: "" SERVER_NAME: "localhost" SERVER_PORT: "8080" SERVER_PROTOCOL: "HTTP/1.1" SERVER_SOFTWARE: "WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)"

Slide 13

Slide 13 text

GATEWAY_INTERFACE: "CGI/1.1" HTTP_ACCEPT: "*/*" HTTP_HOST: "localhost:8080" HTTP_USER_AGENT: "curl/7.37.1" HTTP_VERSION: "HTTP/1.1" PATH_INFO: "/hello/world" QUERY_STRING: "foo=bar" REMOTE_ADDR: "::1" REMOTE_HOST: "localhost" REQUEST_METHOD: "GET" REQUEST_PATH: "/hello/world" REQUEST_URI: "http://localhost:8080/hello/world?foo=bar" SCRIPT_NAME: "" SERVER_NAME: "localhost" SERVER_PORT: "8080" SERVER_PROTOCOL: "HTTP/1.1" SERVER_SOFTWARE: "WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)"

Slide 14

Slide 14 text

GATEWAY_INTERFACE: "CGI/1.1" HTTP_ACCEPT: "*/*" HTTP_HOST: "localhost:8080" HTTP_USER_AGENT: "curl/7.37.1" HTTP_VERSION: "HTTP/1.1" PATH_INFO: "/hello/world" QUERY_STRING: "foo=bar" REMOTE_ADDR: "::1" REMOTE_HOST: "localhost" REQUEST_METHOD: "GET" REQUEST_PATH: "/hello/world" REQUEST_URI: "http://localhost:8080/hello/world?foo=bar" SCRIPT_NAME: "" SERVER_NAME: "localhost" SERVER_PORT: "8080" SERVER_PROTOCOL: "HTTP/1.1" SERVER_SOFTWARE: "WEBrick/1.3.1 (Ruby/2.1.3/2014-09-19)"

Slide 15

Slide 15 text

rack.errors: #> rack.hijack: # rack.hijack?: true rack.hijack_io: nil rack.input: # rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]

Slide 16

Slide 16 text

rack.errors: #> rack.hijack: # rack.hijack?: true rack.hijack_io: nil rack.input: # rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]

Slide 17

Slide 17 text

rack.errors: #> rack.hijack: # rack.hijack?: true rack.hijack_io: nil rack.input: # rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]

Slide 18

Slide 18 text

middleware

Slide 19

Slide 19 text

Handler Middleware Middleware App Internets

Slide 20

Slide 20 text

Handler Middleware Middleware Internets Request Request Request Request Request Internets App

Slide 21

Slide 21 text

Handler Middleware Middleware Request Response Response Response Response Internets App

Slide 22

Slide 22 text

Handler Middleware Internets Request Request Request Request Internets Response Response Response Middleware App

Slide 23

Slide 23 text

qux = ->(env){ [200,{},['Hello world']] } bar = ->(env){ qux.call(env) } foo = ->(env){ bar.call(env) } run foo

Slide 24

Slide 24 text

class Middleware def initialize(app) @app = app end def call(env) @app.call(env) end end

Slide 25

Slide 25 text

class Reverse < Middleware def call(env) status, headers, body = super body.each(&:reverse!) [status, headers, body] end end use Reverse use Reverse run ->(env){ [200,{},['Hello world']] }

Slide 26

Slide 26 text

Rack::Builder and config.ru

Slide 27

Slide 27 text

$ rackup config.ru

Slide 28

Slide 28 text

if config.end_with?('ru') Rack::Builder.new { }.to_app end

Slide 29

Slide 29 text

require_relative 'lib/my_app' use Rack::CommonLogger use Rack::Session::Cookie run MyApp.new

Slide 30

Slide 30 text

map '/admin' do use Authorization::Basic run Admin::App.new end map '/api' do use Authorization::Token run Api::App.new end

Slide 31

Slide 31 text

warmup do |app| client = Rack::MockRequest.new(app) client.get '/cached_resource' end

Slide 32

Slide 32 text

rackup greeter.rb

Slide 33

Slide 33 text

if config.end_with?('rb') require config app_name = File.basename(config, '.rb') app = Object.const_get(app_name.capitalize) run app end

Slide 34

Slide 34 text

class Greeter def self.call(env) params = Rack::Request.new(env).params name = params.fetch('name', 'unknown') [200, {}, ["Greetings, #{name}"]] end end

Slide 35

Slide 35 text

# greeter.rb -> Greeter # my_app.rb -> My_app

Slide 36

Slide 36 text

Request, Response, BodyProxy, and Utils

Slide 37

Slide 37 text

def call(env) request = Rack::Request.new(env) request.request_method? request.post? request.get? request.params['query'] request.update_params['query', 'foobar'] request.ip end

Slide 38

Slide 38 text

def call(env) response = @app.call(env) response = Rack::Response.new(response) response['Etag'] = 'abc123' response.redirect('/faq') response.set_cookie('user_id', 123) response.write 'Hello world' end

Slide 39

Slide 39 text

def call(env) status, headers, body = @app.call body = BodyProxy.new(body) do # Ensure something happens after # body.close is called, eg. DB.connection.close end [status, headers, body] end

Slide 40

Slide 40 text

include Rack::Utils parse_query 'foo=bar&foo=qux' #=> {'foo' => ['bar','qux']} parse_nested_query 'foo[]=bar&foo[]=qux' #=> {'foo' => ['bar','qux']} parse_nested_query 'foo[x]=bar&foo[y]=qux' #=> {'foo' => {'x' => 'bar', 'y' => 'qux'}}

Slide 41

Slide 41 text

include Rack::Utils build_query {foo: ['bar','qux']} #=> 'foo=bar&foo=qux' build_nested_query {foo: ['bar', 'qux']} #=> 'foo[]=bar&foo[]=qux' build_nested_query {foo: {x: 'bar', y: 'qux'}} #=> 'foo[x]=bar&foo[y]=qux'

Slide 42

Slide 42 text

Callbacks Cascade Chunked CommonCookies CommonLogger ConditionalGet Config ContentLength ContentType Context CookieJar Cookies DeflateStream Deflater Deflater Deflect Directory ETag ErrorWrapper Evil ExpectationCascade File ForwardRequest GarbageCollector GzipStream HeaderHash HijackWrapper HostMeta InputWrapper InvalidParameterError JSONP KeySpaceConstrainedParams LighttpdScriptNameFix Lint LintError Lobster Locale Lock Logger MD5 MailExceptions MethodOverride NullLogger ParameterTypeError PostBodyContentTypeParser Printout ProcTitle Profiler Recursive Reloader Request ResponseHeaders RouteExceptions Runtime Sendfile SessionHash rack-contrib https://github.com/rack/rack-contrib

Slide 43

Slide 43 text

Rails

Slide 44

Slide 44 text

use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run Rails.application.routes $ rake middleware

Slide 45

Slide 45 text

use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run Rails.application.routes $ rake middleware

Slide 46

Slide 46 text

Rails.application.routes #=> ActionDispatch::Routing::RouteSet def initialize @set = Journey::Routes.new @router = Journey::Router.new @set end def call(env) req = ActionDispatch::Request.new(env) @router.serve(req) end

Slide 47

Slide 47 text

module ActionDispatch class Request < Rack::Request def initialize(env) super # ... end end end

Slide 48

Slide 48 text

use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run Rails.application.routes $ rake middleware

Slide 49

Slide 49 text

use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run Rails.application.routes $ rake middleware

Slide 50

Slide 50 text

use Rack::Sendfile use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag run Rails.application.routes $ rake middleware

Slide 51

Slide 51 text

More details about Rails on Rack: http://guides.rubyonrails.org/rails_on_rack.html

Slide 52

Slide 52 text

rack.hijack

Slide 53

Slide 53 text

env['rack.hijack?'] #=> true/false env['rack.hijack'] #=> .call env['rack.hijack_io'] #=> IO/Socket

Slide 54

Slide 54 text

app = Proc.new do |env| io = env['rack.hijack'].call begin io.write("Status: 200\r\n") io.write("Connection: close\r\n") io.write("Content-Type: text/plain\r\n") io.write("\r\n") 10.times do |i| io.write("Line #{i + 1}!\n") io.flush sleep 0.1 end ensure io.close end end

Slide 55

Slide 55 text

app = Proc.new do |env| io = env['rack.hijack'].call begin io.write("Status: 200\r\n") io.write("Connection: close\r\n") io.write("Content-Type: text/plain\r\n") io.write("\r\n") 10.times do |i| io.write("Line #{i + 1}!\n") io.flush sleep 0.1 end ensure io.close end end

Slide 56

Slide 56 text

app = Proc.new do |env| hijack = ->(io) { 10.times do |n| io.write("Line #{n + 1}\n") io.flush sleep 0.1 end io.close } headers = {'rack.hijack' => hijack} [200, headers, nil] }

Slide 57

Slide 57 text

• Supported by Passenger • Supported by Unicorn (for responses < 30s) • Supported by Puma • WEBrick only supports partial hijacking • Not supported by Thin (maybe?)

Slide 58

Slide 58 text

MyApp Server Internets Framework Server Framework Network Application HIJACK Rack

Slide 59

Slide 59 text

The future of Rack? https://github.com/tenderlove/the_metal https://gist.github.com/raggi/11c3491561802e573a47 https://github.com/Wardrop/Rack-Next

Slide 60

Slide 60 text

Exercise https://github.com/yycruby/middleware_exercise [email protected]:yycruby/middleware_exercise.git