#151
Mar 02, 2009

Rack Middleware

Rack middleware is a way to filter a request and response coming into your application. In this episode I show how to modify the response body using middleware.
Download (25.2 MB, 12:42)
alternative download for iPod & Apple TV (15.9 MB, 12:42)

Resources

Special thanks to Josh Peek for answering some questions I had on this topic.

Update: making the code below thread safe by duplicating self on call.

Note: as wldock and remi pointed out in the comments, it is important to change the headers["Content-Length"] value to reflect the new body length. See their comments for details.

rake middleware
# lib/response_timer.rb
class ResponseTimer
  def initialize(app, message = "Response Time")
    @app = app
    @message = message
  end
  
  def call(env)
    dup._call(env)
  end
  
  def _call(env)
    @start = Time.now
    @status, @headers, @response = @app.call(env)
    @stop = Time.now
    [@status, @headers, self]
  end
  
  def each(&block)
    block.call("<!-- #{@message}: #{@stop - @start} -->\n") if @headers["Content-Type"].include? "text/html"
    @response.each(&block)
  end
end

# config/environment.rb
config.middleware.use "ResponseTimer", "Load Time"

RSS Feed for Episode Comments 57 comments

1. Dougal MacPherson Mar 02, 2009 at 01:00

I'm really enjoying your Rails 2.3 screencasts.
Thanks again.


2. Dave Hollingworth Mar 02, 2009 at 01:01

Hi Ryan,

another great screencast as usual - thanks! A quick question - you mention that it wouldn't be a good idea to use your example in a production environment - why not? I'd liker to display the response time on a page (like Google does on a search results page for example), and this looks like a good way to do it. Is there a performance hit by using this perhaps?


3. Deger Mar 02, 2009 at 01:15

Interesting screencast as usual, thank you!


4. RailsCasts Fan Mar 02, 2009 at 01:38

Ah, the #151 is here ! Very good Ryan, as always ! ;-) I hope a #1000 and even a #2000. ^^


5. Graham Mar 02, 2009 at 05:13

Sounds useful, but I have to admit you lost me on this one Ryan. Lots of unfamiliar territory.


6. Joshua Peek Mar 02, 2009 at 07:08

Awesome!

Maybe not a huge concern for your example, but it is not threadsafe. You should never modify instance variables in the call method.

However, there is a quick trick to get around this: http://pastie.org/404695


7. remi Mar 02, 2009 at 08:14

If anyone wants a more in-depth look at how to write Rack Middleware (and Rack apps), you might like my Rack Middleware screencast:

http://remi.org/2009/02/28/rack-part-3-middleware.html

Thanks for showing off all of the sweet things that we get with Rails 2.3, now that Rails has such awesome Rack support :)


8. Stephen Celis Mar 02, 2009 at 08:20

A really nice overview, but with a potential gotcha for those that that use the example in a real app:

Nothing should ever appear before the DOCTYPE.

You'll start seeing some definite quirks in different browsers if anything not-DOCTYPE comes first, including comments.


9. Shreyans Mar 02, 2009 at 08:21

Nice episode. Keep it going.
Ryan, can you give me some more specific areas where it can be used in production environment? I mean more scenarios where it makes sense to use RACK middleware!


10. wlodek Mar 02, 2009 at 08:29

I think, that @headers['Content-Length'] should by updated to reflect the change in the response length. Otherwise Rack::Lint will complain.


11. Fredrik Mar 02, 2009 at 08:46

Middleware seems quite powerfull. I am currently using Sven Fuchs routing-filter plugin so I can support more search friendly urls. I am using it so my cms can respond to resource path's stored in the database (like: host/research/forums). Maybe it could be solved with a middleware instead?


12. remi Mar 02, 2009 at 11:08

@wlodek yep, if you do this for real, you absolutely need to update the Content-Length, and not just for Rack::Lint.

If you add text to the response body without updating the Content-Length, the HTML you see in the browser may be truncated (because the browser is only displaying the *original* number of characters, before you added more text to the response body).

Ofcourse, you can use another middleware to ensure that Content-Length is always set to the length of the response body. Rack comes out of the box with Rack::ContentLength for doing something like this, but I think it only works under certain conditions. It would be easy to write your own middleware to accomplish this, if need be, but it's a good idea for your middleware to always make sure to update the Content-Length, itself.


13. David Knorr Mar 02, 2009 at 11:58

@Dave Hollingworth: I'm pretty sure Ryan ment that adding a _comment_ before the DTD would not be really useful in production environment. If you want the user to see the response time it might be useful.

Cheers,
David Knorr.


14. Roman Le Négrate Mar 02, 2009 at 14:16

@Ryan: Nice screencast, as usual.
@Joshua Peek: +1, except I would rename '_call' to the more conventional 'call!'.


15. Shreyans Mar 02, 2009 at 14:35

Hi Ryan, thanks for this nice episode. One thing I need to request you. It would be too nice of you if you could dedicate one episode on GSA (Google Search Appliance) search. I know many people are using it and it would really help people learn it further if you produce one episode on how to implement GSA search in Rails App.


16. Rob Anderton Mar 03, 2009 at 01:00

@Shreyans:

I blogged a little while back about how I used middleware to help with a flash file uploader:

http://tinyurl.com/desshu

While the middleware doesn't look like it does much, it replaces a much more hacky and lengthy solution that had to be used in the 'olden days' :D


17. Ryan Bates Mar 03, 2009 at 08:40

@Dave, I would not use this specific middleware in production because it is not the best way to measure performance. Instead you should use something like NewRelic's RPM. It is also bad to add an HTML comment to the very top of the page before the doctype.

I think Google's response time display is primarily the time the search took to process, I would do that within the Rails app itself and not though middleware.

@Graham, sorry to lose you. This is a fairly advanced topic. Expect some more intermediate content soon.

@Joshua, thanks for pointing this out! I'll update the code so it is thread safe.

@Stephen, right, placing a comment above the doctype is not ideal. This is one reason why I mentioned to not do this in production.

@Shreyans, check out the rack-contrib project linked to in the show notes. That has a lot of production ready middleware examples.

@wlodek, @remi good point! I'll add a note about this.


18. Amit Levy Mar 03, 2009 at 12:55

Ryan,
I've been a big fan for some time now, however one comment:

It seems to me that your essentially using instance variables instead of parameter passing. With the addition to what @Joshua pointed out, this makes this code very hard to test. This example is maybe too simple to illustrate this, but more complex certainly would need some unit tests.
This structure means you would have to either invoke call before invoking each in your tests, or use Object#instance_variable_get, which is ugly at best.
Perhaps a better way might be to create a business object that wrapped response with all the other variables you need:
http://pastie.org/406393

This is both thread safe *and* testable


19. Ryan Bates Mar 03, 2009 at 15:34

@Amit, good suggestion. That does make things easier to test and elegantly handles the thread-safety issue. One might also consider nesting the body class within the timer. I feel better about keeping them in the same file this way.
http://pastie.org/406597


20. Jaime Iniesta Mar 05, 2009 at 04:24

Thanks Ryan, a very interesting railscast, and the link to rack-contrib helps newbies like me understand what all this rack middleware stuff is good for. :)


21. David Beckwith Mar 06, 2009 at 03:28

Hello Ryan,

  Great screencast as usual. Can you explain a little about

block.call( "... stuff .... ")
@response.each(&block)

  How is it that "...stuff..." gets inserted into the response.body by calling the block? And how is that equivalent to "...stuff...." + response.body?

Thank you,
David :)


22. Raj Mar 06, 2009 at 23:26

Ryan,

I've been watching your railscasts and liked them very much.

In one of them you are watching the contents of development.log on OS X terminal as it changes.

Please let me know the command/tool to watch the runtime changes to the files.

Thanks in advance.

Regards,
Rajan


23. remi Mar 06, 2009 at 23:40

@DavidBeckwith: the answers to your questions can be found in the Rack interface specification:

http://rack.rubyforge.org/doc/files/SPEC.html

Note that the body of a Rack app (and middleware are Rack apps) is anything that responds to #each and returns String values. Ryan could have built the body of the response and then prepended it with "..." but, instead, he basically says "the first time #each is called, i'll return my custom string, then i'll just pass the block along to the response's #each".

For more, definitely check out the Rack interface specification or my Rack screencasts @ http://remi.org


24. Melvin Ram - BrainBank Mar 08, 2009 at 17:00

Thanks Ryan! Here's my notes for this screencast (also available at:
http://railsnotes.wordpress.com/2009/03/08/rails-rack-middleware-screencast-notes )

RailsCasts #151 Rack Middleware Notes

* Rack Middleware = filter which you can use to intercept a request and handle the behavior a little differently as it goes to and from the application

* Different between middleware & metal = Metal is an endpoint & middleware is designed to be more of a filter (changes behavior if it needs to.)

* Goes into /lib directory as a .rb file?

* All middleware takes a initialize(app) method. The app variable will hold the rails application

* Adding a call(env) method will make it override rails. This needs to return a array with the same 3 elements as a metal.

* to make rails use your middleware, add this to your environment.rb file inside the Initializer block: config.middleware.use "NameOfClass"

* 'rake middleware' will list out the middlewares used by your app

* You'll need to restart the server every time you create/change a middleware

* To execute your rails app from your middleware, just run .call on the rails app, ex: @app.call(env)

* you can assign parts of the response from a rails app to variables using something like this:
  status, headers, response = @app.call(env)
  
* and then you can call return those back to the real response using something like this:
  [status, headers, "Add stuff" + response.body]
  
* response.body assumes that the app is a rails app. this is not best practice.

* a better approach is to use [status, headers, self] together with def each(&block) `


25. Melvin Ram - BrainBank Mar 08, 2009 at 17:02

Rajan,

You can watch live changes to your development.log file by doing "tail -f log/development.log"


26. Todd Tyree Mar 28, 2009 at 04:09

Ryan, thanks for that as always.

I've Been putting off moving over to 2.3 for a while now, but this convinced me that rack is the perfect solution for a problem I've been seeing with cookieless sessions whereby some phones won't send get and post parameters as part of the same request.


27. Dick Davies Apr 22, 2009 at 15:45

Great screencast as usual, thanks Ryan.

It's worth re-iterating that whenever you write middleware, it's always worth sticking Rack::Lint *on either side of it*, to check you don't inadvertantly break requests and responses.

Essentially, Rack::Lint *is* the Rack spec, so it saves you a lot of reading :)


28. Shih-gian Lee May 05, 2009 at 16:53

It is nice screencast for Rails 2.3. What about Rails 2.1? How do I use Rack in Rails 2.1? Any screencast for that?


29. Lazer May 05, 2009 at 22:25

 Hi Ryan,
Great screencast for rails 2.3.Take care of commenter Melvin Ram above,he also explains extras.

Please let me know the command/tool to watch the runtime changes to the file.


30. Gabe Nov 04, 2009 at 13:59

could this be used to create an upload status bar?


31. Double Metal Beds Nov 30, 2009 at 06:22

That's great post as usual :)))


32. grain grinder mill Dec 06, 2009 at 05:00

Thanks for this nice post


33. singapore startup Dec 08, 2009 at 08:18

Nice post, thanks for this info


34. New Handbags Dec 14, 2009 at 06:37

<a href=http://www.fit-2gether.com/Login.aspx>Fitness trainer</a> | <a href=http://www.fit-2gether.com/Login.aspx>Fitness Community</a> | <a href=http://www.fit-2gether.com/Login.aspx>Fitness Trainers</a> | <a href=http://www.fit-2gether.com/Login.aspx>Activity Groups</a>
 
<a href=http://www.contentarcade.com>Website Content Writing</a> | <a href=http://www.contentarcade.com>Website Content Writing services</a> | <a href=http://www.contentarcade.com>Web Content Writing</a> | <a href=http://www.contentarcade.com>Content Writing services</a>

<a href=http://www.slicklaptops.com>inexpensive laptops</a> | <a href=http://www.slicklaptops.com>cheap laptops computer</a> | <a href=http://www.slicklaptops.com>affordable laptops</a>

<a href=http://www.handbagsinfo.com>handbags collection</a> | <a href=http://www.handbagsinfo.com>Fashion handbags collection</a> | <a href=http://www.handbagsinfo.com>New handbags</a> | <a href=http://www.handbagsinfo.com>cheap handbags</a>


35. sportsjerseysales Dec 16, 2009 at 17:08

This is really a great stuff for sharing.keep it up .Thanks for sharing,ps:welcome to my site
<a href="http://www.sportsjerseysales.com/" target="_blank">NFL Jerseys</a>
<a href="http://www.vertuphonecom.com/" target="_blank">vertu phones</a>


36. hotel boutique singapore Dec 17, 2009 at 12:38

great post, thanks


37. penis büyütücü Dec 18, 2009 at 01:26

penis büyütücü penis büyütme hapı bitkisel penis büyütücü


38. cheap uggs online Dec 24, 2009 at 00:26

Yeah,It looks pretty cool.
Good sharing!


39. Shipbuilding Dec 25, 2009 at 09:04

thanks for this nice work


40. Harry Dec 27, 2009 at 00:57

Very good, thanks!


41. Darkfall gold Dec 28, 2009 at 18:07

The post is very good.I like to read it.Thank you for the share.


42. Penis enlargement Jan 07, 2010 at 15:56

Cool stuff, thanks for sharing!


43. feng shui geomancy Jan 10, 2010 at 11:25

Great Post, thanks for sharing these with us


44. buy skelaxin Jan 10, 2010 at 15:05

ef efesvcds fsdfs


45. skelaxin sale Jan 10, 2010 at 15:09

fef 3r3 f3f3fwf3wfw


46. shenxian's blog Jan 11, 2010 at 22:19

Good post.It is very usefu.I like to see it because I am learn it now.Thank you.


47. wholesale scarves Jan 13, 2010 at 22:56

Such a good article, caught my sympathy!
-


48. wholesale nike shoes Jan 13, 2010 at 22:58

A very good article, I will always come in.


49. Mark D Jan 20, 2010 at 01:48

Hey, just thought i'd say i had a problem running the 'hello world' bit, i was just getting a '#' on the screen. Sorted it by putting the 'Hello world' in square brackets.

def call(env)
  [200, {"Content-Type" => "text/html"}, ["Hello world"]]
end


50. {{!COL7)) Jan 23, 2010 at 19:04

{{!COL4))


51. {{!COL7)) Jan 23, 2010 at 19:53

{{!COL4))


52. buy augmentin Jan 23, 2010 at 20:43

love this info i will read this more often)


53. Plagiarism Checker Jan 24, 2010 at 04:03

What a post, thanks for your code


54. cheap adidas shoes Jan 31, 2010 at 19:06

Adidas Shoes Online Shop-Hot Selling Adidas Shoes & Cheap Adidas Shoes.
you


55. Nike Air Max 90 Feb 01, 2010 at 17:52

Nice post,nice sharing.Thanks!


56. Beauty Accessories Feb 02, 2010 at 05:47

good


57. internet branding Feb 06, 2010 at 12:06

Great article, thanks for sharing it with us

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source