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.
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. 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
52. 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
53. 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
55. # 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;>
56. # 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;>
57. # 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;>
58. # 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;>
60. 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
61. Passenger Middleware Rails
class ExampleMiddleware
# ...
end
# config/environment.rb
config.middleware.use 'ExampleMiddleware'
62. Passenger Middleware Rails
class ExampleMiddleware
# ...
end
# config/environment.rb
config.middleware.use 'ExampleMiddleware'
63. Passenger Middleware Sinatra
class ExampleMiddleware
# ...
end
app = Rack::Builder.new do
use ExampleMiddleware
run MySinatraApp.new
end
64. Passenger Middleware Sinatra
class ExampleMiddleware
# ...
end
app = Rack::Builder.new do
use ExampleMiddleware
run MySinatraApp.new
end
65. Passenger Middleware Sinatra
class ExampleMiddleware
# ...
end
app = Rack::Builder.new do
use ExampleMiddleware
run MySinatraApp.new
end
66. class ExampleMiddleware
# ...
end
# Rails
config.middleware.use 'ExampleMiddleware'
# Rack::Builder
app = Rack::Builder.new do
use ExampleMiddleware
run MySinatraApp.new
end
67. 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
68. 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
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’