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

Rack and Middleware

F78e9e14b7a79742c7109b6d36e3fbd5?s=47 Tim Uruski
February 03, 2015

Rack and Middleware

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

F78e9e14b7a79742c7109b6d36e3fbd5?s=128

Tim Uruski

February 03, 2015
Tweet

Transcript

  1. & Rack Middleware

  2. What is Rack?

  3. Rack is both a protocol and a gem

  4. Created in 2007 By Christian Neukirchen (for reference, Rails was

    created in 2004)
  5. MyApp Thin Unicorn Puma Passenger WEBrick Internets Rails Sinatra Lotus

    Server Framework Network Application RACK
  6. 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
  7. class MyApp def call(env) [200, {}, ['Hello world']] end end

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

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

  10. env and CGI

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

  12. 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)"
  13. 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)"
  14. 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)"
  15. 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]
  16. 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]
  17. 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]
  18. middleware

  19. Handler Middleware Middleware App Internets

  20. Handler Middleware Middleware Internets Request Request Request Request Request Internets

    App
  21. Handler Middleware Middleware Request Response Response Response Response Internets App

  22. Handler Middleware Internets Request Request Request Request Internets Response Response

    Response Middleware App
  23. qux = ->(env){ [200,{},['Hello world']] } bar = ->(env){ qux.call(env)

    } foo = ->(env){ bar.call(env) } run foo
  24. class Middleware def initialize(app) @app = app end def call(env)

    @app.call(env) end end
  25. 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']] }
  26. Rack::Builder and config.ru

  27. $ rackup config.ru

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

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

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

    do use Authorization::Token run Api::App.new end
  31. warmup do |app| client = Rack::MockRequest.new(app) client.get '/cached_resource' end

  32. rackup greeter.rb

  33. if config.end_with?('rb') require config app_name = File.basename(config, '.rb') app =

    Object.const_get(app_name.capitalize) run app end
  34. class Greeter def self.call(env) params = Rack::Request.new(env).params name = params.fetch('name',

    'unknown') [200, {}, ["Greetings, #{name}"]] end end
  35. # greeter.rb -> Greeter # my_app.rb -> My_app

  36. Request, Response, BodyProxy, and Utils

  37. 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
  38. 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
  39. 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
  40. 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'}}
  41. 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'
  42. 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
  43. Rails

  44. 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
  45. 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
  46. 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
  47. module ActionDispatch class Request < Rack::Request def initialize(env) super #

    ... end end end
  48. 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
  49. 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
  50. 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
  51. More details about Rails on Rack: http://guides.rubyonrails.org/rails_on_rack.html

  52. rack.hijack

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

  54. 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
  55. 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
  56. 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] }
  57. • Supported by Passenger • Supported by Unicorn (for responses

    < 30s) • Supported by Puma • WEBrick only supports partial hijacking • Not supported by Thin (maybe?)
  58. MyApp Server Internets Framework Server Framework Network Application HIJACK Rack

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

  60. Exercise https://github.com/yycruby/middleware_exercise git@github.com:yycruby/middleware_exercise.git