Rails 5 cache control doesn't work in heroku - ruby-on-rails

I am setting up the CDN in my application, and by setting the cache-control according to the new standards in Rails 5:
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age = 31536000',
    'Expires' => "# {1.year.from_now.to_formatted_s (: rfc822)}"
}
But when I'm deploying the application to Heroku, it indicates that I am not using the new standards:
DEPRECATION WARNING: config.static_cache_control is deprecated and will be removed in Rails 5.1.
Please use config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=604800' } instead.
(called from at /app/config/application.rb:14)
And when I view the page response headers not being applied cache-control to set

Is that comma between the hash values only missing in this question, or also in your configuration? Try this:
config.public_file_server.headers = {
'Cache-Control' => 'public, max-age = 31536000',
'Expires' => "# {1.year.from_now.to_formatted_s (: rfc822)}"
}

I found the solution on this issue https://github.com/romanbsd/heroku-deflater/issues/26, the problem was with the gem heroku deflater

Related

Setting public_file_server.headers except for some files

I use this in production.rb :
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, maxage=31536000',
'Expires' => "#{1.year.from_now.to_formatted_s(:rfc822)}"
}
I use public files through a cdn.mydomain.com, which is reading from www.mydomain.com and it copies the cache-control from www.mydomain.com, that I set with public_file_server.headers.
The issue is that I want some files from /public to not have those cache-control, for example for my service-worker.js
Is there a way to set those cache control only for one folder in /public for example?
The other solution would be to remove this public_file_server.headers configuration, and setting the cache control on the cdn level (I use cdn.mydomain.com/publicfile), and keeping www.mydomain.com/serviceworker without cache control, for the service worker.
But maybe there is a chance to config this at the Rails level?
I had exactly the same problem: PWA built with Rails using CDN (Cloudfront). For the assets I want to use cache headers with far future expires, but the ServiceWorker needs Cache-control: No-cache.
Because CloudFront doesn't allow to add or change headers by itself, I need a solution on the app level. After some research I found a solution in a blogpost. The idea is to set headers via public_file_server.headers and add a middleware to change this for the ServiceWorker file.
Also, you wrote maxage=, it should be max-age=.
Here is the code I use:
production.rb:
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, max-age=15552000',
'Expires' => 1.year.from_now.to_formatted_s(:rfc822)
}
if ENV['RAILS_SERVE_STATIC_FILES'].present?
config.middleware.insert_before ActionDispatch::Static, ServiceWorkerManager, ['sw.js']
end
app/middleware/service_worker_manager.rb:
# Taken from https://codeburst.io/service-workers-rails-middleware-841d0194144d
#
class ServiceWorkerManager
# We’ll pass 'service_workers' when we register this middleware.
def initialize(app, service_workers)
#app = app
#service_workers = service_workers
end
def call(env)
# Let the next middleware classes & app do their thing first…
status, headers, response = #app.call(env)
dont_cache = #service_workers.any? { |worker_name| env['REQUEST_PATH'].include?(worker_name) }
# …and modify the response if a service worker was fetched.
if dont_cache
headers['Cache-Control'] = 'no-cache'
headers.except!('Expires')
end
[status, headers, response]
end
end

Rails 5 app cant send sendgrid emails (Cloud 9 and Heroku)

Ive checked many posts about it and still, cant find the solution..i thought that its working in deployment but no, those emails werent sent..i was just getting them in the console.
Ive checked my sendgrid credentials, i added global variables with dotenv so maybe something is wrong with my code..
I also tried that code to send email directly:
require 'sendgrid-ruby'
include SendGrid
from = Email.new(email: 'test#example.com')
to = Email.new(email: 'test#example.com')
subject = 'Sending with SendGrid is Fun'
content = Content.new(type: 'text/plain', value: 'and easy to do anywhere,
even with Ruby')
mail = Mail.new(from, subject, to, content)
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
response = sg.client.mail._('send').post(request_body: mail.to_json)
puts response.status_code
puts response.body
puts response.parsed_body
puts response.headers
with no luck:
puts response.status_code
400
=> nil
2.3.4 :016 > puts response.body
{"errors":[{"message":"Invalid type. Expected: object, given: string.","field":"(root)","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#-Request-Body-Parameters"}]}
=> nil
puts response.parsed_body
{:errors=>[{:message=>"Invalid type. Expected: object, given: string.", :field=>"(root)", :help=>"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#-Request-Body-Parameters"}]}
2.3.4 :017 > puts response.headers
{"server"=>["nginx"], "date"=>["Wed, 04 Apr 2018 17:43:43 GMT"], "content-type"=>["application/json"], "content-length"=>["191"], "connection"=>["close"], "access-control-allow-origin"=>["https://sendgrid.api-docs.io"], "access-control-allow-methods"=>["POST"], "access-control-allow-headers"=>["Authorization, Content-Type, On-behalf-of, x-sg-elas-acl"], "access-control-max-age"=>["600"], "x-no-cors-reason"=>["https://sendgrid.com/docs/Classroom/Basics/API/cors.html"]}
=> nil
My friend sent the same code from his computer and its working.. no idea what to do. Im working on Cloud9, maybe thats the problem.. i dont have to much experience in coding so id really appreciate your help guys :)
My production.rb
Rails.application.configure do
ActionMailer::Base.smtp_settings = {
:address => 'smtp.sendgrid.net',
:port => '587',
:authentication => :plain,
:user_name => ENV['SENDGRID_USERNAME'],
:password => ENV['SENDGRID_PASSWORD'],
:domain => 'heroku.com',
:enable_starttls_auto => true
}
# Code is not reloaded between requests.
config.cache_classes = true
config.eager_load = true
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { :host =>
'myapp.herokuapp.com', :protocol => 'https'}
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Attempt to read encrypted secrets from `config/secrets.yml.enc`.
# Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
# `config/secrets.yml.key`.
config.read_encrypted_secrets = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
My development.rb
Rails.application.configure do
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = { :host =>
'https://myapp.c9users.io'}
# Show full error reports.
config.consider_all_requests_local = true
# Enable/disable caching. By default caching is disabled.
if Rails.root.join('tmp/caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
}
else
config.action_controller.perform_caching = false
config.cache_store = :null_store
end
Environment.rb
require_relative 'application'
Rails.application.initialize!
ActionMailer::Base.smtp_settings = {
:address => 'smtp.sendgrid.net',
:port => '587',
:authentication => :plain,
:user_name => ENV['SENDGRID_USERNAME'],
:password => ENV['SENDGRID_PASSWORD'],
:domain => 'heroku.com',
:enable_starttls_auto => true
}
The SendGrid API is pretty fragile, and even more so in Rails. You're probably getting that error because you're using Mail instead of SendGrid::Mail. The sample code provided by SendGrid has this flaw, presumably because they tested it in a Ruby script instead of a Rails environment. Mail is already an existing class in Rails, and Rails does this weird thing where everything is automatically imported everywhere, unlike normal Ruby code.
Change the relevant lines to something like this, instead:
mail = SendGrid::Mail.new(from, subject, to, Content.new(type: 'text/plain', value: 'hi this is a test email'))
mail.add_content Content.new(type: 'text/html', value: '<h1>test email</h1><p>hi</p>')
Also note that if you want to send both a plain-text and an HTML body, you have to do it that EXACT order.
If you don't pass any content into the new method, and just call add_content twice, you'll get another one of those nonsense Invalid type. Expected: object, given: string errors, because the constructor will add nil to the list of contents.
If you pass the HTML content into the constructor and call add_content for the plain-text content, you'll get a different error saying that the plain-text content HAS to be added first.
A generally useful thing to do when you're getting these errors is to print out mail.to_json. That will give you a little more insight into what your code is sending to SendGrid.

Add Expires header to rails assets

I am currently building a rails application and trying to improve its page speed insight mark. The only remaining warning would be my assets headers.
After looking at rails documentation and some articles on the internet, here's what I came up with in my production.rb file:
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, max-age=86400',
'Expires' => "#{1.day.from_now.httpdate}"
}
Now, here's what appears in my chrome network tab for my js/css file:
cache-control:public, max-age=86400
content-encoding:gzip
content-length:90444
content-type:application/javascript
date:Tue, 22 Aug 2017 10:49:05 GMT
last-modified:Tue, 22 Aug 2017 08:49:06 GMT
server:...
The cache-control appears as it should but there's no expires header.
I also use cloudfront on top of that but I'm not sure I should/can alter the headers from there.
Am I doing it wrong?
If you are using Rails 4, then only Cache-Control response header can be set for assets served by Rails. That’s a limitation.
Your solution is working for Rails 5
There is a test in Rails 5 source code, which ensures that custom header is included in response:
def test_serves_files_with_headers
headers = {
"Access-Control-Allow-Origin" => "http://rubyonrails.org",
"Cache-Control" => "public, max-age=60",
"X-Custom-Header" => "I'm a teapot"
}
app = ActionDispatch::Static.new(DummyApp, #root, headers: headers)
response = Rack::MockRequest.new(app).request("GET", "/foo/bar.html")
assert_equal "http://rubyonrails.org", response.headers["Access-Control-Allow-Origin"]
assert_equal "public, max-age=60", response.headers["Cache-Control"]
assert_equal "I'm a teapot", response.headers["X-Custom-Header"]
end
Also, even if you add Expires header somehow, max-age will take precedence anyway.
config.public_file_server.headers = {
'Cache-Control' => 'public, s-maxage=31536000, max-age=86400',
'Expires' => "#{1.day.from_now.to_formatted_s(:rfc822)}"
}
try this

How to cache files over 1MB with rack/cache on Heroku?

I'm using a combination of Dragonfly and rack/cache hosted on Heroku.
I'm using Dragonfly for uploaded assets. Thumbnails are processed on-the-fly and stored in rack/cache for fast delivery from memcached (via the Memcachier addon).
Regular static assets are also cached in memcached via rack/cache.
My problem is that any uploaded files over 1MB are causing a 500 error in my application.
2013-07-15T10:38:27.040992+00:00 app[web.1]: DalliError: Value too large, memcached can only store 1048576 bytes per key [key: d49c36d5db74ef45e957cf169a0b27b83b9e84de, size: 1502314]
2013-07-15T10:38:27.052255+00:00 app[web.1]: cache: [GET /media/BAhbBlsHOgZmSSIdNTA3Njk3ZWFiODBmNDEwMDEzMDAzNjA4BjoGRVQ/WTW_A5Flyer_HealthcareMedicalObsGynae_WEB.pdf] miss, store
2013-07-15T10:38:27.060583+00:00 app[web.1]: !! Unexpected error while processing request: undefined method `each' for nil:NilClass
Memcache has a limit of 1MB, so I can understand why my asset was not cached, but I would rather it didn't break serving assets.
I'm not even sure where this error is coming from. Presumably from one of the other rack middlewares?
Increasing the maximum file size doesn't seem to have have any affect.
config.cache_store = :dalli_store, ENV["MEMCACHIER_SERVERS"].split(","), {¬
:username => ENV["MEMCACHIER_USERNAME"],¬
:password => ENV["MEMCACHIER_PASSWORD"],¬
:value_max_bytes => 5242880 # 5MB¬
}
Long term, I know that moving this sort of asset off of Heroku is a sensible move, but that won't be a quick job.
What can I do to serve these assets on Heroku in the meantime without errors?
So contrary to #jordelver's question, I find that setting the :value_max_bytes option of dalli does work. I'm setting up Rack::Cache in a slightly different way that maybe makes the difference.
This is what my production.rb contains to configure Rack::Cache:
client = Dalli::Client.new(ENV["MEMCACHIER_SERVERS"],
:username => ENV["MEMCACHIER_USERNAME"],
:password => ENV["MEMCACHIER_PASSWORD"],
:value_max_bytes => 10485760)
config.action_dispatch.rack_cache = {
:metastore => client,
:entitystore => client
}
config.static_cache_control = "public, max-age=2592000"
With the above, some errors will be printed to the logs for values over 1MB, but they won't cause a 5xx error for the client, just a cache miss.
P.S I work for MemCachier :) so we're interested in trying to sort this out. Please let me know if it works.
I had the same issue as #jordelver and managed to get round memcachier's limits by monkey patching Dragonfly::Response:
module Dragonfly
class Response
private
def cache_headers
if job.size > 1048576
{
"Cache-Control" => "no-cache, no-store",
"Pragma" => "no-cache"
}
else
{
"Cache-Control" => "public, max-age=31536000", # (1 year)
"ETag" => %("#{job.signature}")
}
end
end
end
end
Essentially, if the size is over 1048576 bytes, send a no-cache header.
My application.js was too big for rack-cache so I did:
# in config/environments/development.rb
config.action_dispatch.rack_cache = {
metastore: 'file:/var/cache/rack/meta',
entitystore: 'file:tmp/cache/rack/body'
}
And it works!
Stores metadata in memcache but actual file in filesystem and not in memory.

relative_url_root in ActionMailer

What's the equivalent of relative_url_root in ActionMailer?
I've tried setting it up in default_url_options, but it appends the parameter in the query string.
Adding :skip_relative_url_root = false doesn't work either.
Using :host = 'somehost.com/subdir' does work, but is that appropriate?
Thanks!
A cleaner way to do it :
config.action_mailer.default_url_options = {
:host => "somehost.com",
:only_path => false,
:script_name => "/subdir"
}
Source
Use script_name option into default_url_options of ActionMailer::Base. Based on this article.

Resources