Opportunistically streaming html output with Ruby lambdas
In my last post (Ruby, HTML, s-expressions, lambdas?) I said I would show how we can modify the html generation to allow streaming.
Of course I was making it out to be a lot more complicated in my head than it really is - all the hard work is done already by the lambdas enforcing correct execution order. All we need to do is add a few judicious STDOUT.flush calls before anything that might take some time (ie. evaluating the tag content which could include a database lookup etc.)
I've done that in the new htm.rb below, but first, here's some demo code with some delays:
engines = [["http://google.com/", "Google"], ["http://yahoo.com/", "Yahoo!"], ["http://webcrawler.com/", "Showing my age"]] Htm.htm(:ul, engines.map \ { |e| Htm.htm(:li, [:a, :href, e[0], [:b, lambda { sleep 3; Htm.str(e[1])}]])}).call
Notice also how we can use a lambda anywhere we can put content. Instead of a sleep, perhaps it's an external ImageMagick command. Or perhaps instead of the map iterator our loop is iterating over results being streamed from a slow database query.
Our output will look the same, but I've indicated with !!! where the output pauses for 3 seconds:
You can see that the http stream will receive all the output it possibly can while waiting for the expensive operation, and the tags are still automagically closed. Job done!
Here's the up to date htm.rb:
require 'cgi' module Htm def Htm.str(content) lambda { print CGI.escapeHTML(content.to_s) } end def Htm._build_tag(tag, args) # open the tag print '<' + tag.to_s tok = args.shift # find any tag attribute key/value pairs while tok.kind_of? Symbol print ' ' + tok.to_s + '="' + CGI.escapeHTML(args.shift.to_s) + '"' tok = args.shift end if tok.nil? # self-close tag if no content print ' />' else # finish open tag print '>' # output tag content Htm._eval_content( tok.kind_of?(Symbol) ? Htm.htm(tok, args) : tok ) # close tag print '</' + tag.to_s + '>' end end def Htm._eval_content(content) while content.kind_of? Proc STDOUT.flush content = content.call end if content.kind_of? Array Htm._eval_content(Htm.htm( *content )) elsif ! content.nil? print content end end def Htm.htm( *args ) lambda do tok = args.shift while ! tok.nil? if tok.kind_of? Symbol Htm._build_tag(tok, args) else Htm._eval_content(tok) end tok = args.shift end end end end
10:02 AM, 09 May 2009 by Mark Aufflick Permalink | Short Link







