Mongrel::DirHandler equivalent for Passenger - ruby-on-rails

I'm using Mongrel::DirHandler to control response headers for static files - this works great on my dev machine. My production machine uses Passenger so my headers aren't getting set. How do I control headers for static files when using Passenger?
snippet from my environment.rb:
if defined? Mongrel::DirHandler
module Mongrel
class DirHandler
def send_file_with_expires(req_path, request, response, header_only=false)
if req_path =~ /((\/images)|javascripts|stylesheets)/
response.header['Cache-Control'] = 'max-age=315360000'
response.header['Expires'] = (Time.now + 10.years).rfc2822
else
response.header["Last-Modified"] = Time.now.httpdate
response.header["Expires"] = 0
# HTTP 1.0
response.header["Pragma"] = 'no-cache'
# HTTP 1.1 ‘pre-check=0, post-check=0′ (IE specific)
response.header["Cache-Control"] = 'no-store, no-cache, must-revalidate, max-age=0, pre-check=0, post-check=0'
end
send_file_without_expires(req_path, request, response, header_only)
end
alias_method :send_file_without_expires, :send_file
alias_method :send_file, :send_file_with_expires
end
end
end

Since you're using Passenger, I assume you're under apache, so your request isn't going through Mongrel anymore. If so, you can establish rules on the .htaccess file inside the public directory of your application.
Here's an explination on how to do it.

Related

How to use Postman to connect to Ruby on Rails 5 Actioncable (Websocket)?

I am running a Rails 5.2.7 engine which has actioncable (websockets) set up. The websocket connection is successfully established when the application is run via the browser, using a React UI with actioncable-js-jwt, a version of actioncable's js package that supports jwt. However I am unable to establish the connection via postman's beta websockets option.
My connection.rb file is very simple:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
# Public: Invoked as part of the websocket connection establishment
def connect
self.current_user = {
...(hardcoded values)
}
end
end
end
Engine.rb looks like this:
require 'action_cable/engine'
module RoundaboutEngine
class << self
def cable
#cable ||= ActionCable::Server::Configuration.new
end
end
class Engine < ::Rails::Engine
isolate_namespace RoundaboutEngine
class << self
def server
#server ||= ActionCable::Server::Base.new(config: RoundaboutEngine.cable)
end
end
config.roundaboutengine_cable = RoundaboutEngine.cable
config.roundaboutengine_cable.mount_path = '/cable'
config.roundaboutengine_cable.connection_class = -> { RoundaboutEngine::Connection }
config.roundaboutengine_cable.disable_request_forgery_protection = true
initializer 'roundaboutengine_cable.cable.config' do
RoundaboutEngine.cable.cable = { adapter: 'async' }
end
initializer 'roundaboutengine_cable.cable.logger' do
RoundaboutEngine.cable.logger ||= ::Rails.logger
end
end
end
The browser request network tab shows the following:
Request URL: ws://localhost:9292/cable
Request Method: GET
Status Code: 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: [string]
Sec-WebSocket-Protocol: actioncable-v1-json
Upgrade: websocket
In postman, I also use ws://localhost:9292/cable as a raw connection. However, it seems to turn that into an http url, as shown in the request information:
Request URL: http://localhost:9292/cable
Request Method: GET
Status Code: 404 Not Found
Request Headers
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: [string]
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: localhost:9292
I am not sure why postman is turning the request into an http request, or how to make this work.
I also tried adding Sec-WebSocket-Protocol: actioncable-v1-json to the postman request headers, with no luck.
Resolved: I had to replace a line in engine.rb
I removed:
config.roundaboutengine_cable.disable_request_forgery_protection = true
I added:
config.action_cable.allowed_request_origins = [/http:\/\/*/, /https:\/\/*/, nil]
In particular, the nil was important for postman and is what was causing the problem.

How to set Cache-control for ActiveStorage Disk service?

I have a simple Rails 6 app with ActiveStorage. I use local disk storage. When I inspect responses from representation url like this
http://localhost:3000/rails/active_storage/disk/some-long-hash/IMG_0951.jpeg?content_type=image%2Fjpeg&disposition=inline%3B+filename%3D%22IMG_0951.jpeg%22%3B+filename%2A%3DUTF-8%27%27IMG_0951.jpeg
I see headers Cache-Control: max-age=0, private, must-revalidate
The question is how to make Rails to set public caching header with some age?
The #show method for ActiveStorage::DiskController is difficult to override but it can be done.
A simpler approach is to add an after_action callback for the existing #show method to insert the Cache-Control header when it is called:
# config/initializers/active_storage.rb
require 'active_storage/current'
ActiveStorage::Current.url_options = { host: 'localhost', port: 3000 }
require 'active_storage/set_current'
require 'active_storage/base_controller'
require 'active_storage/file_server'
require 'active_storage/disk_controller'
class ActiveStorage::DiskController < ActiveStorage::BaseController
after_action do
response.set_header('Cache-Control', 'max-age=3600, public') if action_name == 'show'
end
end
Requesting an ActiveStorage URL then returns the custom Cache-Control header value in the response:
HTTP/1.1 200 OK
Cache-Control: max-age=3600, public
...

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

Force headers on all Rails 404/500 responses

I am trying to set the 'X-Frame-Options' header on all responses returned by my Rails application. It seems like this header is not set on 404 or 500 type responses. How can I configure Rails to always include this header?
It appears I somehow need to hook into Rails to ensure these headers are always set.
I am having success using the below middleware as my 'exceptions_app'.
class XSecurityHandler
def initialize(app)
#app = app
end
def call(env)
_status, headers, response = #app.call(env)
headers['X-Frame-Options'] = "SAMEORIGIN"
headers['X-Content-Type-Options'] = "nosniff"
[status(env), headers, response]
end
private
def status(env)
path = env["ORIGINAL_FULLPATH"]
if path == "/404"
404
elsif path == "/422"
422
else
500
end
end
end
Set default headers in config/application.rb:
config.action_dispatch.default_headers['X-Frame-Options'] = 'SAMEORIGIN'
but it wont work, if you have configured reverse proxy (like nginx) to serve static assets which does not exists (404), but i think you know about this :)

WEBrick alter HTTP response headers on a per-file extension basis

Is it possible to modify the WEBrick HTTP response headers globally for a specific file extension, for example to serve all files with svgz extension to include the HTTP Header "Content-Encoding: gzip" in the HTTP response? I can't seem to figure out how to do this.
lib/dps/compression.rb
module Dps
class Compression
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
if File.extname(env['REQUEST_URI']) == ".svgz" && status == 200
headers["Content-Encoding"] = "gzip"
else
nil
end
[status, headers, response]
end
end
end
config/application.rb
config.autoload_paths += Dir["#{config.root}/lib/**/"]
config.middleware.insert_before("ActionDispatch::Static", "Dps::Compression")

Resources