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?
Key Takeaways
- The /assets path in Rails applications using Sprockets is not just a prefix for assets, but a rack endpoint. Sprockets prepends every asset with /assets, causing each browser request for those assets to send calls to http(s)://your_domain/assets/asset_name.
- Building a rack endpoint in a Rails application involves following strict guidelines, including that it must respond to the call method, and it must return an array with three pieces of information: the HTTP status code, the response headers, and the response itself.
- Sprockets uses a rack endpoint at /assets to ensure that /assets takes priority over other rack endpoints. It does this by prepending /assets to the Rails routes, as rack matches every request in the first matching route definition.
- ActionDispatch::Static is a middleware in the default Rails middleware stack that serves static assets, which are assets that don’t require any preprocessing before serving them, like images and fonts. In the development environment, all assets are handled by Sprockets, whereas in production, static assets in the public directory exist and are served by ActionDispatch::Static.
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!
Frequently Asked Questions (FAQs) about Rack Middleware and Sprockets
What is the primary function of Rack Middleware in Ruby on Rails?
Rack Middleware in Ruby on Rails is a component that sits between the web server and the web framework. It’s responsible for processing HTTP requests and responses. It can perform a variety of functions such as logging, setting up sessions, managing cookies, and handling errors. Middleware components are reusable and can be used in different applications or in different parts of the same application.
How does Sprockets work in conjunction with Rack Middleware?
Sprockets is a Ruby library that aids in the generation of asset files. It works with Rack Middleware to serve these assets in a Rails application. When a request for an asset is made, Sprockets compiles the asset if necessary and then the request is passed to Rack, which then serves the asset to the client.
How can I add a new middleware to my Rails application?
You can add a new middleware to your Rails application by using the config.middleware.use
command in the application configuration file. You need to specify the class of the middleware and any arguments that should be passed to it. Once added, the middleware will be included in the processing of every request.
Can I remove or disable a middleware in Rails?
Yes, you can remove or disable a middleware in Rails. This can be done by using the config.middleware.delete
command in the application configuration file. You need to specify the class of the middleware that you want to remove.
What is the order of execution of middleware in Rails?
The order of execution of middleware in Rails is determined by the order in which they are defined in the application configuration file. Middleware defined earlier in the file are executed before those defined later. However, the response is processed in the reverse order.
How can I debug middleware in Rails?
You can debug middleware in Rails by using the rake middleware
command. This will display a list of all the middleware in your application, in the order they are executed. You can also add logging statements in your middleware code to track its execution.
How does Sprockets handle asset compilation?
Sprockets handles asset compilation by concatenating all the required files into a single file. It also minifies the assets by removing unnecessary characters, and can compile assets from a higher-level language like CoffeeScript or Sass to a lower-level language like JavaScript or CSS.
Can I use Sprockets with other Ruby frameworks?
Yes, Sprockets can be used with other Ruby frameworks. While it is commonly used with Rails, it can also be used with Sinatra or any other Rack-based framework.
How can I customize the behavior of Sprockets?
You can customize the behavior of Sprockets by creating a custom initializer. This allows you to set various configuration options, such as the paths that Sprockets should search for assets, the cache directory, and the compression level for assets.
What are some common issues that can occur with Rack Middleware and Sprockets?
Some common issues that can occur with Rack Middleware and Sprockets include conflicts between middleware, issues with the order of middleware execution, problems with asset compilation or serving, and performance issues. These can often be resolved by debugging the middleware stack, adjusting the middleware order, or tweaking the Sprockets configuration.
Imran Latif is a Web Developer and Ruby on Rails and JavaScript enthusiast from Pakistan. He is a passionate programmer and always keeps on learning new tools and technologies. During his free time he watches tech videos and reads tech articles to increase his knowledge.