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.
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?
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!
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?
@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.
@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.
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.
@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.
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
@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
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. :)
@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
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) `
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.
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 :)
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
This example is not working in Rails3. At least for me.
The problem is that response is ActionDispatch object (code 200OK) the first time a route is accessed, and an empty array[] (code 304) for all requests.
Can you update this for Rails3 and explain this change a little further?
I tried the code example but I get the following error when a request hits the server:
[2011-09-16 15:04:36] ERROR NoMethodError: undefined method new' for "ResponseTimer":String
C:/project/vendor/rails/actionpack/lib/action_controller/middleware_stack.rb:72:inbuild'
C:/project/vendor/rails/actionpack/lib/action_controller/middleware_stack.rb:116:in build'
C:/project/vendor/rails/activesupport/lib/active_support/dependencies.rb:443:ininject'
I'm really enjoying your Rails 2.3 screencasts.
Thanks again.
Helpful for all learners Thanks.
asd
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?
Interesting screencast as usual, thank you!
Ah, the #151 is here ! Very good Ryan, as always ! ;-) I hope a #1000 and even a #2000. ^^
Sounds useful, but I have to admit you lost me on this one Ryan. Lots of unfamiliar territory.
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
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 :)
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.
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!
I think, that @headers['Content-Length'] should by updated to reflect the change in the response length. Otherwise Rack::Lint will complain.
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?
@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.
@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.
@Ryan: Nice screencast, as usual.
@Joshua Peek: +1, except I would rename '_call' to the more conventional 'call!'.
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.
@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
@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.
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
@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
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. :)
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 :)
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
@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
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) `
Rajan,
You can watch live changes to your development.log file by doing "tail -f log/development.log"
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.
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 :)
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?
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.
could this be used to create an upload status bar?
Good post.It is very usefu.I like to see it because I am learn it now.Thank you.
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
What a post, thanks for your code
thanks for this nice code & template
This is really fabulous!
I already have some of them but I am still going to save all of them.
Thanks for your wonderful sharing.
Cracking screencast, thanks. I just used metal to load Nesta (a Sinatra CMS) into the same process as a Rails 2.3 app.
I'm sure I wouldn't have found out how to do it so smoothly if I hadn't watched this.
Hi Ryan,
This example is not working in Rails3. At least for me.
The problem is that response is ActionDispatch object (code 200OK) the first time a route is accessed, and an empty array[] (code 304) for all requests.
Can you update this for Rails3 and explain this change a little further?
Respect!
I tried the code example but I get the following error when a request hits the server:
[2011-09-16 15:04:36] ERROR NoMethodError: undefined method
new' for "ResponseTimer":String
build'C:/project/vendor/rails/actionpack/lib/action_controller/middleware_stack.rb:72:in
C:/project/vendor/rails/actionpack/lib/action_controller/middleware_stack.rb:116:in
build'
inject'C:/project/vendor/rails/activesupport/lib/active_support/dependencies.rb:443:in
I am using Rails 2.3.5/ Ruby 1.8.7.
Years later, this is still very helpful, great ep !
Years later, this is still very helpful, Thanks!
For Rails 3.2 >=, you can create an
app/middleware
dir for response_timer.rb and just addconfig.middleware.use "ResponseTimer"
to the Rails.application.configure block in config/environments/development.rb