Sprockets Dissected: Rack and Middleware

Web Developer, Ruby on Rails and JavaScript enthusiast

sprocketsmag

In a previous article I explain how asset tags are inserted into HTML by Rails and Sprockets. Inserting asset tags into the HTML is the first step towards serving assets. In this post, I continue the journey, answering the remaining questions:

  • What is /assets and it’s purpose?
  • What is ActionDispatch::Static middleware used for?

What is /assets?

By default, Rails applications using Sprockets prepend /assets on every asset’s path. /assets is not, however, only a prefix for assets; it is also a rack endpoint.

Basically, when generating the response, Sprockets prepends every asset with /assets, causing each browser requests for those assets to send calls to http(s)://your_domain/assets/asset_name. /assets/asset_name is the name of the asset we want browser to request.

Whenever a call is made to /assets/asset_name, the Rails application needs to respond by serving that asset. As I mentioned, /assets is a rack endpoint, so is it handled by our Rails routes? Every endpoint or path in a Rails application is defined in config/routes.rb, but when you view its content, you will not find any route for /assets. Yet, our assets are getting served. Let’s see how.

Building a rack endpoint in any existing Rails application is quite easy. There are some strict guidelines that every rack endpoint must follow and these are:

  • It must respond to the call method
  • It must return an array with three pieces of information: the HTTP status code, the response headers, and the response itself

For example, place the following code in our sample Rails application (created in first article) config/routes.rb file:

mount Proc.new {|env| [200, {'Content-Type' => 'text/html'}, ["SitePoint rocks!"]]} => "/sitepoint"

mount defines /sitepoint as a rack endpoint. Proc.new will return a Ruby block, which responds to the call method. The block returns an array containing status code, status headers, and the response.

Run your Rails application (rails server) and navigate to http://localhost:3000/sitepoint. You will see SitePoint rocks! displayed in browser. That was a quick and dirty introduction to rack endpoints in Rails. Let’s get back to Sprockets and see how Sprockets pulls this off.

Sprockets uses the following code to mount a rack endpoint at /assets which is defined here:

app.routes.prepend do
  mount app.assets => config.assets.prefix
end

app.assets is Rails.application.assets which is an instance of Sprockets::Environment, discussed in the previous article. config.assets.prefix is /assets. Sprockets is prepending /assets to the Rails routes because rack will match every requests in the first matching route definition. So, by prepending, Sprockets is making sure that /assets takes priority over other rack endpoints.

app.assets must respond to call in order to be processed as a rack endpoint. Looking at the code for Sprockets::Environment in sprockets/lib/sprockets/environment.rb, call is inherited from Sprockets::Base. Exploring sprockets/lib/sprockets/base.rb further, it is including some modules, one of which is Sprockets::Server. Code spelunking into Sprockets::Server and sprockets/lib/sprockets/server.rb, there is a call method defined. Therefore, app.assets is getting a call method from Sprockets::Server, which is called each time there is request to /assets. This call method returns the appropriate asset.

find_asset, defined at sprockets/lib/sprockets/server.rb:42, returns an instance of Sprockets::BundledAsset, provided it is JavaScript or CSS. If it is an image, a Sprockets::StaticAsset is returned. When the browser calls /assets/application.js, then find_asset will return an instance of Sprockets::ProcessedAsset.

At line:62 there is call to ok_response. ok_response is defined on line:187 and returns an array per the rack specification, by placing the asset into the response. This array is returned from ok_response, which is returned from call.

We have seen that an asset instance is presented as the response for /assets/application.js. But is this sufficient to send back to the client? The answer is “it depends”.

According to rack, the response being sent from a rack endpoint must respond to each. Both Sprockets::BundledAsset and Sprockets::StaticAsset inherit from the Sprockets::Asset class. sprockets/lib/sprockets/asset.rb:82 defines each, which means our asset instance responds to each and is rack compatible.

In the each method, to_s is passed as argument to the block. At line:55 there is a method to_s which returns the source of the asset. Each asset instance has its source in a source instance variable which is used to get source code for the corresponding asset. We are doing same thing in the to_s method, fetching the source code and returning it to the appropriate method call. The source code of the requested asset is then transferred to the HTTP client by our Rails application.

This is how Sprockets serves our assets. The above procedure is the same for every type of asset call, JavaScript, stylesheet, image, font, etc.

What is ActionDispatch::Static?

ActionDispatch::Static is a middleware in the default Rails middleware stack. Its primarily purpose is to serve static assets. Static assets are assets which don’t require any preprocessing before serving them, like images and fonts. JavaScript and stylesheet files are not considered to be static assets because there is some preprocessing involved before serving them up. When we execute rake assets:precompile, however, those assets become static assets because they are preprocessed and saved as files so they can be served just like images. Let’s see how this middleware works.

ActionDispatch::Static middleware is configured and added to the middleware stack here:

middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control

ActionDispatch::Static is defined in actionpack/lib/action_dispatch/middleware/static.rb:47. There are three parameters in its constructor: app, path, and cache_control. Providing the app parameter is the responsibility of rack. Rails is passing paths["public"].first for the second argument. paths["public"].first is the absolute path of the public directory in the Rails application. If we have enabled config.serve_static_assets in the relevant environment file under config/environments, then ActionDispatch::Static is the first middleware in the list when rake middleware is executed. It will serve as the first execution point for our Rails application.

Looking at actionpack/lib/action_dispatch/middleware/static.rb:53 there is a call method, which is used by rack. In this method, at line:54, there is a case statement on the HTTP method being used. If the request’s HTTP method is GET or HEAD, this middleware is evaluating the request path to see if it is a static asset that needs to be served. line:50 of the constructor for ActionDispatch::Static shows the following:

@file_handler = FileHandler.new(path, cache_control)

The FileHandler class is defined in same file, just above ActionDispatch::Static. At line:57, there is a call to match? on this @file_handler instance. At line:12, you can see the definition of match?. This method will evaluate the provided path to see whether it is a static asset. If this method returns true, ActionDispatch::Static will serve the asset and control will not be passed to other middlewares in the stack. If match? returns false, control will be passed to subsequent middleware.

line:15 of match? concatenates the provided path with @root. @root is ithe absolute path of hte public directory of our Rails application.

For example, a request for /assets/application.js is concatenated with the public directory path to become /Users/imran/work/rails_asset_serve/public/assets/application.js (your’s will be different). The code on line:19 tries to determine whether this file exists. If it does, then this file will be handled by ActionDispatch::Static, otherwise it will be handled by Sprockets.

In the development environment there are no assets in the public directory, so they are all handled by Sprockets. In production, however, static assets in the public directory exist because we usually compile assets by using rake assets:precompile. Here, match? returns true and assets are served by ActionDispatch::Static.

On line:59, a call is made to call on the @file_handler instance. call is defined on line:26 which is, in turn, calling call on @file_server, an instance of Rack::File.

Rack::File is defined in rack/lib/rack/file.rb. In the call method on line:30, dup is called which creates a shallow copy of the current instance. _call (defined on line:36) is called on that newly created copy, performing different checks and then calling serving on line:53.

In serving, on line:73, the array for the rack response is created, where self is presented as the response object. At the end of serving, this response array is returned.

I asked previously if self is enough to send a response to client? The answer is yes. Because self responds to each which is defined on line:104. In each, a file is opened for reading and read in 8KB chunks to the passed in block, which is transferred to the browser by our Rails application.

This is how ActionDispatch::Static serves static assets.

Conclusion

Rails is an incredible codebase written by the brightest minds in the Ruby community. By reading the source code, you not only improve your skills but you also get a chance to learn new things that you might not find elsewhere. For example, I discovered that, by using ActionDispatch::Static, we can serve portions of our assets using Range headers, which I was not aware of before.

I hope these articles helped you learn more about how Rails and Sprockets serve assets. Please let me know what you think!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.