An Interest In:
Web News this Week
- April 29, 2024
- April 28, 2024
- April 27, 2024
- April 26, 2024
- April 25, 2024
- April 24, 2024
- April 23, 2024
Exploring Rack
If you’re a Ruby programmer who has done any kind of web development, you’ve almost certainly used Rack, whether you know it or not, as it’s the foundation which most Ruby web frameworks (Rails, Sinatra, etc.) are built upon. Let’s dig into some of the basic concepts of Rack and even build a small app or two.
What Is Rack, Exactly?
Rack is several things, actually:
- a web server interface
- a protocol for building and composing web applications
- a collection of middleware utilities
A Web Server Interface
Part of what’s nice about Rack, is that it provides a standardized way for Ruby applications to talk to web servers, abstracting away the server stuff (listening on a port, accepting connections, parsing HTTP requests and responses, etc.) so that you can focus on what your application does.
The Protocol
The Rack protocol is very simple: a Rack application is simply a Ruby object with a call
method. That method should accept an environment hash describing the incoming request and return a three-element array in the form of: [status, headers, body]
, where:
status
is the HTTP status code.headers
is a hash of HTTP headers for the response.body
is the actual body of the response (e.g. the HTML you want to
display). The body must also respond toeach
.
The easiest way to understand Rack’s protocol, is by taking a look at some code.
First, get the rack gem and set up a directory:
$ gem install rack$ mkdir hellorack$ cd hellorack
Now create a file named config.ru
and fill it in with the following:
class Hello def self.call(env) [ 200, # 200 indicates success {"Content-Type" => "text/plain"}, # the hash of headers ["Hello from Rack!"] # we wrap the body in an Array so # that it responds to `each` ] endend# Tell Rack what to run our apprun Hello
Save the file, open up your terminal and run the rackup
command:
$ rackup[2012-12-21 17:48:38] INFO WEBrick 1.3.1[2012-12-21 17:48:38] INFO ruby 1.9.2 (2011-07-09) [x86_64-darwin11.0.1][2012-12-21 17:48:38] INFO WEBrick::HTTPServer#start: pid=1597 port=9292
The bottom few lines of output, in the above code, let you know that Rack is running your app at port 9292 using Ruby’s built-in WEBrick web server. Point your browser to https://localhost:9292 to see a happy welcome message from Rack.
Kill the app (ctrl-c) and let’s talk about what is going on here.
When you run the rackup
command, Rack looks for a rackup
config file (conventionally named config.ru
, but you can name it whatever you want). It then starts a web server (WEBrick by default) and runs your app.
This protocol is the foundation on which popular frameworks like Rails and Sinatra are built. What they do is layer functionality like template rendering, route dispatching, managing database connections, content negotiation, etc. on top of this fundamental abstraction.
How they do this, is what brings us to the concept of middleware.
Middleware
Middleware gives you a way to compose Rack applications together.
A middleware (yes, it’s both singular and plural) is simply a Rack application that gets initialized with another Rack application. You can define different middleware to do different jobs and then stack them together to do useful things.
For example, if you have a Rails app lying around (chances are, if you’re a Ruby developer, that you do), you can cd
into the app and run the command rake middleware
to see what middleware Rails is using:
$ cd my-rails-app$ rake middlewareuse ActionDispatch::Staticuse Rack::Lockuse #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fcc4481ae08>use Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActiveRecord::ConnectionAdapters::ConnectionManagementuse ActiveRecord::QueryCacheuse ActionDispatch::Cookiesuse ActionDispatch::Session::CookieStoreuse ActionDispatch::Flashuse ActionDispatch::ParamsParseruse ActionDispatch::Headuse Rack::ConditionalGetuse Rack::ETaguse ActionDispatch::BestStandardsSupportrun MyRailsApp::Application.routes
Every request that comes into this app starts at the top of this stack, bubbles its way down, hits the router at the bottom, which dispatches to a controller that generates some kind of response (usually some HTML), which then bubbles its way back up through the stack before being sent back to the browser.
A Middleware Example
Nothing fosters understanding a new concept like coding does, so let’s build a very simple middleware that just converts the response body to uppercase. Open up our config.ru
file from before and add the following:
class ToUpper # Our class will be initialize with another Rack app def initialize(app) @app = app end def call(env) # First, call `@app` status, headers, body = @app.call(env) # Iterate through the body, upcasing each chunk upcased_body = body.map { |chunk| chunk.upcase } # Pass our new body on through [status, headers, upcased_body] endend# This is the same Hello app from before, just without all the commentsclass Hello def self.call(env) [ 200, {"Content-Type" => "text/plain"}, ["Hello from Rack!"] ] endenduse ToUpper # Tell Rack to use our newly-minted middlewarerun Hello
Run the rackup
command again and visit https://localhost:9292 to see our new middleware in action.
What Rack did here was build a Rack application that was the composition of the ToUpper
and Hello
applications. Internal to Rack, there’s a Builder
class that effectively constructed a new app by doing the equivalent of:
app = ToUpper.new(Hello)run app
If there were more middleware present (like in the Rails stack), it would just nest them all the way down:
use Middleware1use Middleware2use Middleware3run MyApp#=> Boils down to Middleware1.new(Middleware2.new(Middleware3.new(MyApp)))
Request and Response Classes
When you start writing Rack applications and middleware, manipulating the [status, headers, body]
array quickly becomes tedious.
Rack provides a couple of convenience classes, Rack::Request
and Rack::Response
, to make life a little bit easier.
Rack::Request
wraps an env
hash and provides you with convenience methods for pulling out the information you might need:
def call(env) req = Rack::Request.new(env) req.request_method #=> GET, POST, PUT, etc. req.get? # is this a GET requestion req.path_info # the path this request came in on req.session # access to the session object, if using the # Rack::Session middleware req.params # a hash of merged GET and POST params, useful for # pulling values out of a query string # ... and many moreend
Rack::Response
is complementary to Rack::Request
, and gives you a more convenient way to construct a response. For example, our Hello
app could be rewritten as follows:
class Hello def self.call(env) res = Rack::Response.new # This will automatically set the Content-Length header for you res.write "Hello from Rack!" # returns the standard [status, headers, body] array res.finish # You can get/set headers with square bracket syntax: # res["Content-Type"] = "text/plain" # You can set and delete cookies # res.set_cookie("user_id", 1) # res.delete_cookie("user_id") endend
Conclusion
In this article, we’ve covered the basic concepts of Rack, which should be enough for you to get a better understanding of what’s under the hood of the many popular frameworks out there and also help you get your feet wet if you’re interested in developing directly with Rack.
Code is an excellent teacher, and so if you’re interested in Rack, I highly recommend looking at its source. It comes with a lot of very useful baked-in utilities and middleware (and plenty more at rack-contrib) that you can use and learn from.
Original Link: http://feedproxy.google.com/~r/nettuts/~3/em6Y2H_sl2g/
TutsPlus - Code
Tuts+ is a site aimed at web developers and designers offering tutorials and articles on technologies, skills and techniques to improve how you design and build websites.More About this Source Visit TutsPlus - Code