Configure Cloudfront CDN for Rails application in Heroku

December 6, 2013
admin

CloudFront is a Content Distribution Network (CDN), powered by Amazon. CloudFront is used for hosting assets over a different region and serving these assets from the nearest server for any particular request.

While deploying application all the files including Javascript, CSS, Fonts and other files reside on one server. If we want to reduce the load time for any page and improve overall response time, we need to think about how to reduce the number of requests to our application server. To tackle this issue, we can forward asset retrieval requests to a separate asset hosting server. In that way, we are segregating our static assets from the application. Also, we are going to host our images to a file storage like amazon s3 as a content delivery network or content distribution network (CDN). In that case, our resources come from amazon s3 and all the asset retrieval requests will go to amazon s3. 

Now, we are happy for this famous third-party service but one question may arise where our storage server is located and when the request comes from the different region, does this storage service meet our expectation in terms of the minimum load time of resources? So we consider again how we can achieve this and how we are going to use Amazon CloudFront service to minimize the load time of resources along with resource distribution.

But we can make it work by following CORS standards. We will see an example of CORS implementation for Amazon S3 and CloudFront as our assets distribution service.

When a cross-origin request is initiated, it includes the request origin in its header. The value of this header is the domain that serves the page. For example, your request for www.nascenia.com and it’s all assets are hosted at assets.nascenia.com. If we want the browser to implement CORS between www.nascenia.com and assets.nascenia.com, following request header needs to be sent to www.nascenia.com:

Origin: http://assets.nascenia.com

If the www.nascenia.com allows the request, it sends an Access-Control-Allow-Origin  key in its response header and the value will be:

Access-Control-Allow-Origin: http://assets.nascenia.com

If the server does not allow the cross-origin request, the browser will return an error. The value of the header indicates which origin sites are allowed. To allow access from all domains, a server can send the following response header:

Access-Control-Allow-Origin: *

In this case, across request was initiated from assets.nascenia.com to www.nascenia.com and www.nascenia.com allowed the access for all domains.

The steps to set up CloudFront distribution will be as follows:

Step-1: Login to your Amazon console and go CloudFront service, which will be like below: 

Step1

And click to Create Distribution for going to the next step.

Step-2: In this step, you need to select the type of delivery method. In our case, of course, it is web type and it remains selected by default.

Cloudfront CDN | Nascenia

Step-3: In this step, we set up our origin which is responsible to store our assets. Before going any further from this, I assume that you already have Amazon s3 account and you stored your assets on that storage service.

If you have a bucket at amazon s3, then you will see that there is a text filed named Origin Domain Name with some bucket names and you are able to see these names when you click on this box and select your intended bucket like xyz.s3.amazonaws.com as a dropdown list if you have multiple buckets. This step will be like below:

Cloudfront CDN for Rails application in Heroku | Nascenia

Also, other fields will remain blank as the default value and you are able to edit it later if necessary. It takes an hour for this distribution to come online.

Now, we configure our application to serve assets from this distribution. We use asset_sync gem to synchronize our precompiled assets during slug compilation with amazon s3. For this, we need to follow instructions from this gem asset_sync.

Here are the steps:

Step-1:  Enable the user-env-compile functionality by executing the following command in terminal

heroku labs:enable user-env-compile -a myapp

Step-2:  Add the gem in: assets group in your Gemfile

group :assets do
gem 'asset_sync'
end

Step-3: Configure config/environments/production.rb to use Amazon S3 as the asset host and ensure precompiling is enabled.

#config/environments/production.rb
config.action_controller.asset_host = "//#{ENV['FOG_DIRECTORY']}.s3.amazonaws.com"

Also, ensure the following are set (in production.rb or application.rb)

config.assets.digest = true.
config.assets.enabled = true.

Additionally, if you depend on any configuration that is set up in your initializers you will need to ensure that you set the following option (in production.rb or application.rb):

config.assets.initialize_on_precompile = true

Step-4: Now, we set the environment variables that will help us in asset synchronization process. Add your configuration details to Heroku.

heroku config:add AWS_ACCESS_KEY_ID=xxxx
heroku config:add AWS_SECRET_ACCESS_KEY=xxxx
heroku config:add FOG_DIRECTORY=xxxx
heroku config:add FOG_PROVIDER=AWS

You may also define different keys depending upon your context. To do so, we can create a yaml file by running the following command:

rails g asset_sync:install --use-yml --provider=AWS

The generator will create a YAML file at config/asset_sync.yml and modify it as your own requirements if needed.

Now, we have completed all the process except one configuration. We have not added any CloudFront link that will use for asset distribution. You will be able to collect it from CloudFront using this AWS console with proper credentials. Then select your distribution -> Distribution Settings -> General tab -> Domain Name, copy domain name from here and put it to production.rb as an asset_host.

So, go to config/environments/production.rb file and modify the following settings once again:

#config/environments/production.rb
config.action_controller.asset_host = "//xyz.cloudfront.net"

After completing the process, browse your site and observe the loading speed of your application.

But you may notice that some items are not loading properly like font awesome or any other custom fonts! It is due to CORS and you need to permit these resources to get access via CloudFront.

So we need to allow our amazon s3 assets to access via CloudFront. To do this, we need to add the filter to set Access-Control-Allow-Origin in application controller.

For Rails 2.3.8

class ApplicationController < ActionController::Base
protect_from_forgery
after_filter :allow_cross_domain_access
def allow_cross_domain_access
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "*"
end
end

For Rails 3.1

class ApplicationController < ActionController::Base
protect_from_forgery
after_filter :allow_cross_domain_access
def allow_cross_domain_access
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Request-Method'] = '*'
end
end

For Rails 4

                   In config/application.rb:

config.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => '*'
}

Also, you need to allow via amazon s3. To do go to:

amazon s3 console -> select your intended s3 bucket -> properties -> Add more permissions -> Add bucket policy

A popup window will open to give bucket CORS permissions. There will be a link to see sample permissions or you may put this below configuration:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Now all the setup is completed and you are ready for CloudFront distribution without any issue. That’s it!

Contributor: Naim Rajib, Software Engineer, Nascenia

No comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.