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

"Golfing" Rack Middleware on Rails

"Golfing" Rack Middleware on Rails

Simple introduction to Rack Middleware on Rails, and how Rails use Rack Middleware for most of its features.

Event page: https://www.meetup.com/id-ruby/events/235569544/

Muhammad Mufid Afif

December 01, 2016
Tweet

More Decks by Muhammad Mufid Afif

Other Decks in Technology

Transcript

  1. “Golfing” Rack Middleware on Rails Muhammad Mufid Afif Was presented

    on Jakarta.rb Meetup @ Jenius Office Bank BTPN December 1st, 2016
  2. In this session - Introduction - What is Rack? -

    Rails and Rack (Middlewares) - Trivia
  3. What is Rack? Handler Middleware(s) Application Handle HTTP connection Unicorn,

    Puma, WEBRick, SendFile, Runtime, Attack, Method Override Filter request Rails, Sinatra, anything.
  4. What is Rack? - “Modular HTTP Web Server” - Has

    a “Handler”, “Middlewares”, and (an) Application - Handler: WEBRick, Puma, Thin, Unicorn - Middlewares: Rack::Attack, Rack::MethodOverride, ActionDispatch - “Application”: Sinatra, Rails - By default, Rack Handler is WEBrick. - You must specify Rack applications and middlewares in Rack Configuration “config.ru” → ru stands for “rackup” - Latest stable version on RubyGem: 2.0.1 - On this talk, let’s focus on Middleware and App *Hijacking and stream are not covered in this talk
  5. How to make Rails App/Middleware - Duck typing! No need

    to extend any class - Rack will call you will give you Request ENV - Your App/Middleware should response [], with - HTTP Status code - Response header - Enumerable of body - For middleware: you will be constructed - For app: you must be constructed
  6. Let’s write a simple Rack app! $ cat Gemfile source

    "https://rubygems.org" gem "rack", '~> 2.0.1' $ cat config.ru class Meow def call(env) [200, {}, ['Miaw']] end end run Meow.new * We don’t really need Bundler. Just make sure we don’t mess up your file system
  7. Let’s write a simple Rack app! $ cat config.ru class

    Meow def call(env) [200, {}, ['Miaw']] end end run Meow.new Request ENV Response Status Response Header Response Body
  8. Let’s write a simple middleware! class CatId def initialize(app) @app

    = app end def call(env) result = @app.call(env) result[1]['X-Cat-Name'] = %w{ Angora Persia Bobtail }.sample result end end class CatStartTime def initialize(app, ct) @app = app @cat_time = ct end def call(env) result = @app.call(env) result[1]['X-Cat-Start-Time'] = @cat_time.to_s result end end Pass parameters into middleware!
  9. Let’s write a simple middleware! use CatId use CatStartTime, Time.now

    run Meow.new Construct Meow Construct CatStartTime, with parameter (<Meow>, Time.now) Construct CatId, with parameter (<CatStartTime>
  10. How Rails use Rack? - Rails is Rack-based application -

    Uses A LOT of Rack middleware before request hit application
  11. $ rails middleware use Rack::Sendfile use ActionDispatch::Static use ActionDispatch::Executor use

    ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use Rack::Head use Rack::ConditionalGet use Rack::ETag use Warden::Manager use OmniAuth::Strategies::GoogleOauth2 run SinapsWeb::Application.routes
  12. $ rails middleware use Rack::Sendfile use ActionDispatch::Static use ActionDispatch::Executor use

    ActiveSupport::Cache::Strategy::LocalCache::Middleware use Rack::Runtime use Rack::MethodOverride ←←←←←←←←←←←←←←←←←←←←←←←←←←←←←← use ActionDispatch::RequestId use Rails::Rack::Logger use ActionDispatch::ShowExceptions use WebConsole::Middleware use ActionDispatch::DebugExceptions use ActionDispatch::RemoteIp use ActionDispatch::Reloader use ActionDispatch::Callbacks use ActiveRecord::Migration::CheckPending use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use Rack::Head use Rack::ConditionalGet use Rack::ETag use Warden::Manager use OmniAuth::Strategies::GoogleOauth2 run SinapsWeb::Application.routes
  13. Example: Rack::MethodOverride - Browser only able to POST and GET

    - This middleware allows changing Request Verb (GET, POST, DELETE) with _method params - ???? - Only works for POST request, don’t worry
  14. class MethodOverride HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS

    PATCH LINK UNLINK] METHOD_OVERRIDE_PARAM_KEY = "_method".freeze HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze ALLOWED_METHODS = %w[POST] def call(env) if allowed_methods.include?(env[REQUEST_METHOD]) method = method_override(env) if HTTP_METHODS.include?(method) env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD] env[REQUEST_METHOD] = method end end @app.call(env) end def method_override(env) req = Request.new(env) method = method_override_param(req) || env[HTTP_METHOD_OVERRIDE_HEADER] method.to_s.upcase end def method_override_param(req) req.POST[METHOD_OVERRIDE_PARAM_KEY] end end
  15. Some “Interesting” Rack-based Middleware - Logster - Override Rails’ default

    middleware logging - Put logging into Redis instead and show it
  16. Some “Interesting” Rack-based Middleware - Rack::Attack - Rate limit and

    blacklisting, prevent brute-force attempts on login, etc.
  17. Summing Up - Use middleware to modify requests or response

    before hitting application - Rack Middlewares are universal: It should be plug and play to any Rack-based app, not limited to Rails - (Yes yes. As long as the middleware is not tightly-coupled to Rails) - All demo code available on https://github.com/mufid/rack-talk