Today I’m reading the code for Rack::GoogleAnalytics. It’s rack middleware I found on coderack.org that will automatically insert the Google Analytics tracking code into every page.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
module Rack #:nodoc: class GoogleAnalytics < Struct.new :app, :options def call(env) status, headers, response = app.call(env) if headers["Content-Type"] =~ /text\/html|application\/xhtml\+xml/ body = "" response.each { |part| body << part } index = body.rindex("") if index body.insert(index, tracking_code(options[:web_property_id])) headers["Content-Length"] = body.length.to_s response = [body] end end [status, headers, response] end protected # Returns JS to be embeded. This takes one argument, a Web Property ID # (aka UA number). def tracking_code(web_property_id) returning_value = <<-EOF if (typeof gaJsHost == 'undefined') { var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); } try { var #{options[:prefix]}pageTracker = _gat._getTracker("#{web_property_id}"); EOF if options[:multiple_top_level_domains] returning_value << <<-EOF #{options[:prefix]}pageTracker._setDomainName("none"); #{options[:prefix]}pageTracker._setAllowLinker(true); EOF elsif options[:domain_name] returning_value << <<-EOF #{options[:prefix]}pageTracker._setDomainName("#{options[:domain_name]}"); EOF end returning_value << <<-EOF #{options[:prefix]}pageTracker._trackPageview(); } catch(err) {} EOF returning_value end end end |
Review
Rack::GoogleAnalytics does a few interesting things:
Struct
Instead of using an initialize
method like Rack::MemoryBloat did yesterday, it’s using a new Struct
class.
Generating tracking code
The protected #tracking_code
method is responsible for generating the Google Analytics Javascript. There isn’t much going on here, just some string substitution and an if statement for the different domain options.
Inserting tracking code
How Rack::GoogleAnalytics inserts the tracking code is interesting. It’s running the rest of the application stack and then if the content type is HTML, it adds the tracking code to the string right before the closing body
(using String#rindex
). This line looks odd to me though:
1 |
response.each { |part| body << part } |
I think this is collecting all of the response’s parts into a single string (body
). I’m not sure how Rack works internally, but if it tries to send data to the client in chunks this code might block. At the very least, it will be creating a new string for every response that is the same size as the response body. I wonder if it’s possible to just do a string replacement in place.