Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Rack and Middleware

Tim Uruski
February 03, 2015

Rack and Middleware

Introduction to Rack and Middleware for YYC Ruby meetup, Feb 2015.

Tim Uruski

February 03, 2015
Tweet

More Decks by Tim Uruski

Other Decks in Programming

Transcript

  1. 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
  2. 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)"
  3. 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)"
  4. 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)"
  5. rack.errors: #<IO:<STDERR>> rack.hijack: #<Proc:0x007f8fab939e90 @lib/rack/handler/webrick.rb:77 (lambda)> rack.hijack?: true rack.hijack_io: nil

    rack.input: #<StringIO:0x007f8fab938298> rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]
  6. rack.errors: #<IO:<STDERR>> rack.hijack: #<Proc:0x007f8fab939e90 @lib/rack/handler/webrick.rb:77 (lambda)> rack.hijack?: true rack.hijack_io: nil

    rack.input: #<StringIO:0x007f8fab938298> rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]
  7. rack.errors: #<IO:<STDERR>> rack.hijack: #<Proc:0x007f8fab939e90 @lib/rack/handler/webrick.rb:77 (lambda)> rack.hijack?: true rack.hijack_io: nil

    rack.input: #<StringIO:0x007f8fab938298> rack.multiprocess: false rack.multithread: true rack.run_once: false rack.url_scheme: "http" rack.version: [1, 3]
  8. 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']] }
  9. map '/admin' do use Authorization::Basic run Admin::App.new end map '/api'

    do use Authorization::Token run Api::App.new end
  10. 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
  11. 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
  12. 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'}}
  13. 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'
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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] }
  24. • Supported by Passenger • Supported by Unicorn (for responses

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