Skip to content

Instantly share code, notes, and snippets.

@ashleygwilliams
Last active December 18, 2015 18:29
Show Gist options
  • Save ashleygwilliams/5825650 to your computer and use it in GitHub Desktop.
Save ashleygwilliams/5825650 to your computer and use it in GitHub Desktop.
rack lab

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.

  1. Create a directory called rack_middleware

  2. cd into that directory and create the file app.rb

  3. 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
  1. In the same directory, create the file config.ru

  2. In config.ru, load the code you just wrote in app.rb with:

# config.ru

require './app.rb'
  1. In config.ru add the run command to mount a new instance of MyApp

  2. 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!

  3. 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.

  1. 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!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment