Rack runs an instance of a class that implements the #call
method, but it can also use several other "stand-alone" Rack apps before returning the response to your web-browser by passing the status, headers, and body to each new app, we can stack middleware between the server and the browser to manipulate the details of the request/response cycle. Think of it like a stack.
-
Create a directory called rack_middleware
-
cd
into that directory and create the file app.rb -
We're going to define our base Rack app in app.rb. We just need a class that implements a call method. Add a
MyApp
class that says hello, but doesn't set any headers. We'll add those later.
# app.rb
class MyApp
def call(env)
[200, {}, ["Hello"]]
end
end
Just for the sake of seeing what's going on, let's also have it puts
a message to the console as well.
# app.rb
class MyApp
def call(env)
puts "Hello from MyApp!"
[200, {}, ["Hello"]]
end
end
-
In the same directory, create the file config.ru
-
In config.ru, load the code you just wrote in app.rb with:
# config.ru
require './app.rb'
-
In config.ru add the run command to mount a new instance of MyApp
-
From the console run the
rackup
command and open your browser to http://localhost:9292. Note the formatting of the text. We didn't set any headers in MyApp, and so the content-type is being automatically set to text! -
Let's write some middleware to add the content-type headers to the response from our app. Let's first tell Rack we are going to use this piece of middleware, which we'll call ContentType. This is actually going to break the app because we haven't defined a Rack app with that name, but we'll fix it in a moment. The
use
method simply specifies some middleware to use in the stack. http://rack.rubyforge.org/doc/Rack/Builder.html#method-i-use
# config.ru
require './app.rb'
use ContentType
run MyApp.new
The use
method tells Rack to run the request/response through this piece of middleware before returning it to the browser.
Now we'll create the middleware that we just told Rack to use when processing requests. In config.ru create a class named ContentType
.
# config.ru
require './app.rb'
class ContentType
end
use ContentType
run MyApp.new
This class is going to implement two methods. The #call
method, the only method required of a Rack application, and the #initialize
method, which is Ruby method called each time a new instance of a class is created.
#config.ru
require './app.rb'
class ContentType
def intialize
end
def call(env)
end
end
use ContentType
run MyApp.new
We need to manipulate the headers returned from our app, so we're going to use the initialize method to get that response from MyApp and allow us to manipulate it "on the way out," if you will. The app
argument is passed to this method by the use
method.
#config.ru
require './app.rb'
class ContentType
def initialize(app)
@app = app
end
def call(env)
end
end
use ContentType
run MyApp.new
Next, we're going to use that @app
instance variable to pass the status, headers and body to ContentType's #call
method.
First, lets do a puts
on that apps call method, and see what we get back. We'll need to pass it the env since we're not using Rack's run
method on it.
#config.ru
require './app.rb'
class ContentType
def initialize(app)
@app = app
end
def call(env)
puts "app.call(env) returns: #{@app.call(env)}"
end
end
use ContentType
run MyApp.new
Run rackup
and visit http://localhost:9292.
Take a look in console, what got returned? Where did it come from? Based on that, what is @app
an instance of in this case?
Now lets set our status, headers and body variables to match those values.
#config.ru
require './app.rb'
class ContentType
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
end
end
use ContentType
run MyApp.new
Run rackup
and visit http://localhost:9292.
Looks the same as before the middleware? Ok now lets make some changes to the headers hash so that the browser will interpret the response as html instead of plain old text. We're going to access the "Content-Type" key in the hash, and set its value equal to "text/html"
#config.ru
require './app.rb'
class ContentType
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers["Content-Type"] = "text/html"
end
end
use ContentType
run MyApp.new
and then we need our call method to return the new status, headers, body values as an array:
#config.ru
require './app.rb'
class ContentType
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers["Content-Type"] = "text/html"
[status, headers, body]
end
end
use ContentType
run MyApp.new
Re-run rackup
and visit http://localhost:9292.
The text changed! The browser interpreted the response as HTML and applied its built in default HTML style. Nice job, you just wrote your first piece of Rack middleware! Not much of a change, but now that you've got the basic concept down, time for you to cut loose and change that body response.
- Using our existing app, write another piece of middleware called FinishSentence that will take the body of the response from MyApp and add the word "World!" so that when you run rackup and visit http://localhost:9292 the text displayed reads "Hello World!"