Rack Middleware

  • 4,009 views
Uploaded on

 

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
4,009
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
1
Comments
0
Likes
5

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

  • To start off, we should take a brief look at Rack itself.

  • This is a valid Rack application.
  • lambda takes a block and creates a Proc object.
    Proc objects are invoked using the call method.
  • This proc object takes one argument, the environment.
  • And returns three values, the status code...
  • The response headers (here we’re setting the Content Type to text/plain) ...
  • and the response body (here the string “OK”)
  • A Ruby Hash responds to each and returns key/value pairs
  • This array responds to each and returns Strings
  • Rack was introduced to Rails in version 2.3.
  • Before Rack, a typical Rails stack in production would look like...
  • A Web Server
  • Some kind of application server (Mongrel, Thin, etc)..
  • Your application.
  • Now that we have Rack, this stack looks like...
  • A web server...
  • An application server...
  • And your application. No difference.
    From a Rails developer’s perspective, not much has changed at first glance.
  • The magic happens here.
    Rails is now Rack compatible, and this interconnect now takes the form of the Rack API.
  • The Rack API is encapsulated in Rails by ActionController::Base...
  • Which has a call method...
  • That takes the environment as a parameter...
  • And returns a status code...
  • The response headers...
  • And the response body.
  • There are many reasons this change is important, but the one I’m going to talk about is Middleware.
    What is Rack Middleware?
  • Because we know what the API between the app server and the Rails stack looks like now.
    And Because we know what inputs it is expecting.
    And because we know what the return will look like.
  • We can now insert code in between the app server and the application, that can speak this Rack API. We call this code Middleware.
  • A middleware application takes the form of a class...
  • Its constructor...
  • ...takes the next piece of the chain as a parameter, which allows you to keep the chain going.
  • In this middleware, which has a call method...
  • ...and takes the environment as a parameter...
  • We’re going to check a value in the environment, the requested path,
    and see if it starts with /api
  • If it does, we’re going to return an array of three values,
    A status code, the headers, and the body
  • Otherwise, we’re going to add some data to the environment.
    We can put any ruby objects we like in here for the benefit of applications further down the chain.
  • And we’re going to pass the call through to the next item in the chain.



  • Rack::Cache sits in the middle of your application stack and monitors requests.
    It understands HTTP standards for both freshness (Expires, Cache-Control headers)
    and validation (Last-Modified, ETag headers)
  • It can intercept requests and serve them from its own storage. (Disk, memcached, memory)
    Cache expiration and invalidation is handled automatically.

  • Rack::Bug adds a toolbar to the top of your app to give you some insight into the internals.
  • You can look at things like the Rails Environment
  • You can look at which queries ran on the page and how long they took.
    You can run an EXPLAIN on them right from the page.
  • Rack::OpenID takes care of OpenID authentication for you
  • In your SessionsController which controls user authentication...
  • ...you have a “new” method which renders a form that contains an identity textbox for the user to type their OpenID URL
  • In the create action, which processes the form
  • We check the environment to see if Rack::OpenID has injected credentials
    On the first pass through this will be missing, so...
  • We build a header that Rack::OpenID will be able to process...
  • ...that includes the OpenID identity provided by the user
  • which in combination with a 401 status code lets Rack::OpenID know to take over
  • After Rack::OpenID does its thing it injects ‘rack.openid.response’ into the environment.
    So when we come back into the controller and the OpenID credentials are present in the environment...
  • We can check the status of the OpenID response and take the appropriate action.

  • Rack::Debug gives an easy interface to ruby-debug inside your application
  • Add debugger statements to your code
  • At the console, run the debug rake task
  • The next time you refresh a page in your browser, your app stops at debugger statements and drops you into an interactive shell.
  • How do I use Middleware in my applications.
  • If I have a middleware called ExampleMiddleware
  • In Rails, I’m going to go to my config/environment.rb
  • And add a line, config.middleware.use ‘ExampleMiddleware’
  • Rack also gives us a class called Rack::Builder
  • We tell it to use our Middleware

Transcript

  • 1. Rack Middleware David Dollar
  • 2. What is Rack?
  • 3. A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 4. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 5. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 6. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 7. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 8. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 9. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 10. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 11. lambda do |environment| [ 200, { 'Content-Type' => 'text/plain' }, [ 'OK' ] ] end A Rack application is a Ruby object that responds to call. It takes exactly one argument, the environment. It returns an Array of exactly three values: The status, the headers, and the body. The headers should respond to each and yield key/value pairs. The body should respond to each and yield String objects.
  • 12. Rack and Rails
  • 13. Before Rails 2.3
  • 14. Before Rails 2.3 Apache
  • 15. Before Rails 2.3 Apache Passenger
  • 16. Before Rails 2.3 Apache Passenger Rails
  • 17. Before Rails 2.3 Apache Passenger Rails After Rails 2.3
  • 18. Before Rails 2.3 Apache Passenger Rails After Rails 2.3 Apache
  • 19. Before Rails 2.3 Apache Passenger Rails After Rails 2.3 Apache Passenger
  • 20. Before Rails 2.3 Apache Passenger Rails After Rails 2.3 Apache Passenger Rails
  • 21. Before Rails 2.3 Apache Passenger Rails After Rails 2.3 Apache Passenger Rails
  • 22. Passenger ActionController::Base#call(env) Rails
  • 23. What is Rack Middleware?
  • 24. Passenger ActionController::Base#call(env) Rails
  • 25. Passenger Middleware Rails
  • 26. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 27. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 28. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 29. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 30. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 31. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 32. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 33. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 34. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, do_api_call(env) ] else env['mydata'] = 'test' @next_in_chain.call(env) end end end
  • 35. Example
  • 36. Existing Middleware
  • 37. Rack::Cache http://github.com/rtomayko/rack-cache
  • 38. App Server Rack::Cache Rails Storage
  • 39. App Server Rack::Cache Rails Storage
  • 40. Rack::Bug http://github.com/brynary/rack-bug
  • 41. Rack::OpenID http://github.com/josh/rack-openid
  • 42. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 43. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 44. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 45. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 46. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 47. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 48. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 49. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 50. class SessionsController def new render :new # contains a textbox called quot;identityquot; end def create if openid = request.env['rack.openid.response'] case openid.status when :success # ... when :failure # ... end else response.headers['WWW-Authenticate'] = Rack::OpenID.build_header( :identifier => params[:identity] ) render :text => '', :status => 401 end end end
  • 51. Rack::Debug http://github.com/ddollar/rack-debug
  • 52. # app/controllers/users_controller.rb @user = User.find(params[:id]) debugger render :show # run the rake task, $ rake debug Connected. # refresh a page in your browser, your app will break at debugger statements (rdb:1) p @user <User id: 1, name: quot;David Dollarquot;, email: quot;ddollar@gmail.comquot;>
  • 53. # app/controllers/users_controller.rb @user = User.find(params[:id]) debugger render :show # run the rake task $ rake debug Connected. (rdb:1) p @user <User id: 1, name: quot;David Dollarquot;, email: quot;ddollar@gmail.comquot;>
  • 54. # app/controllers/users_controller.rb @user = User.find(params[:id]) debugger render :show # run the rake task $ rake debug Connected. (rdb:1) p @user <User id: 1, name: quot;David Dollarquot;, email: quot;ddollar@gmail.comquot;>
  • 55. # app/controllers/users_controller.rb @user = User.find(params[:id]) debugger render :show # run the rake task $ rake debug Connected. (rdb:1) p @user <User id: 1, name: quot;David Dollarquot;, email: quot;ddollar@gmail.comquot;>
  • 56. Using Middleware
  • 57. Passenger Middleware Rails class ExampleMiddleware def initialize(next_in_chain) @next_in_chain = next_in_chain end def call(env) if env['PATH_INFO'] =~ Regexp.new('^/api') [ 200, { 'Content-Type' => 'text/xml' }, '<?xml?>' ] else @next_in_chain.call(env) end end end
  • 58. Passenger Middleware Rails class ExampleMiddleware # ... end # config/environment.rb config.middleware.use 'ExampleMiddleware'
  • 59. Passenger Middleware Rails class ExampleMiddleware # ... end # config/environment.rb config.middleware.use 'ExampleMiddleware'
  • 60. Passenger Middleware Sinatra class ExampleMiddleware # ... end app = Rack::Builder.new do use ExampleMiddleware run MySinatraApp.new end
  • 61. Passenger Middleware Sinatra class ExampleMiddleware # ... end app = Rack::Builder.new do use ExampleMiddleware run MySinatraApp.new end
  • 62. Passenger Middleware Sinatra class ExampleMiddleware # ... end app = Rack::Builder.new do use ExampleMiddleware run MySinatraApp.new end
  • 63. class ExampleMiddleware # ... end # Rails config.middleware.use 'ExampleMiddleware' # Rack::Builder app = Rack::Builder.new do use ExampleMiddleware run MySinatraApp.new end
  • 64. class ExampleMiddleware # ... end # Rails config.middleware.use 'ExampleMiddleware' config.middleware.use 'ExampleMiddlewareTwo' # Rack::Builder app = Rack::Builder.new do use ExampleMiddleware use ExampleMiddlewareTwo run MySinatraApp.new end
  • 65. class ExampleMiddleware # ... end # Rails config.middleware.use 'ExampleMiddleware' config.middleware.use 'ExampleMiddlewareTwo' config.middleware.use 'ExampleMiddlewareThree' # Rack::Builder app = Rack::Builder.new do use ExampleMiddleware use ExampleMiddlewareTwo use ExampleMiddlewareThree run MySinatraApp.new end
  • 66. Questions?