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

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")

Related

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 :)

How to intercept and rewrite damaged JSON calls with Middleware

I'm trying to find the best place to do this. My middleware looks like this :
class Fixer
def initialize app
#app = app
end
def call env
if env["HTTP_ORIGIN"] == "https://where_i_expect_it_to_come_from.com/"
env['rack.input'] = StringIO.new('{"yo":"momma"}') # <-- But this info is not actually written before the call is passed!
end
env['rack.input'].rewind
status, headers, response = #app.call env
return [status, headers, response]
end
end
Rails.application.config.middleware.insert_before ActionDispatch::ParamsParser, Fixer
It seems that even when I rewrite the call here, the info is not actually rewritten properly. Any ideas how I can have my content written before its bubbled up?
i think this problem is the same as here: https://stackoverflow.com/questions/22712663/getting-a-routing-error-when-my-routes-are-clearly-marked and here I have a third party service that I believe is sending me malformed JSON
from what i understand you want to fix some incoming request-data.
as a simplified example:
curl -X PUT -d "user[name]=something" http://localhost:3000/users/123
i change something to phoet in the fixer
class Fixer
def initialize(app)
#app = app
end
def call(env)
env['rack.input'] = StringIO.new("user[name]=phoet")
#app.call(env)
end
end
and add it to application.rb
config.middleware.insert_before "ActionDispatch::ParamsParser", "Fixer"
when i request my app now, i see the logs
[localhost] [fdff4eb0-8387-41] Started PUT "/users/123" for 127.0.0.1 at 2014-03-30 13:16:02 -0400
[localhost] [fdff4eb0-8387-41] Processing by UsersController#update as */*
[localhost] [fdff4eb0-8387-41] Parameters: {"user"=>{"name"=>"phoet"}, "id"=>"123"}

How can I return a 404 for a specific URL with Rack middleware

A site I manage is getting constant requests for a javascript file that no longer exists, from an older version of the site. These requests take up a lot of resources because they get routed through Rails every time to return a 404. I am thinking it would be much better to have Rack handle that specific URL and return 404 itself. Is that correct? If so, how would I set that up?
I have been checking out this blog post which I think is kinda the way to move forward (ie, inheritance from some existing Rack module):
http://icelab.com.au/articles/wrapping-rack-middleware-to-exclude-certain-urls-for-rails-streaming-responses/
So I ended up writing my own little bit of middleware:
module Rack
class NotFoundUrls
def initialize(app, exclude)
#app = app
#exclude = exclude
end
def call(env)
status, headers, response = #app.call(env)
req = Rack::Request.new(env)
return [status, headers, response] if !#exclude.include?(URI.unescape(req.fullpath))
content = 'Not Found'
[404, {'Content-Type' => 'text/html', 'Content-Length' => content.size.to_s}, [content]]
end
end
end
and then adding this to the config.ru file:
use Rack::NotFoundUrls, ['/javascripts/some.old.file.js']
It's the first time I've done this so let me know if there's any glaring mistakes...
The rack-contrib gem includes a Rack::NotFound middleware component (among many other useful elements) which should do the job:
https://github.com/rack/rack-contrib/

Weak ETAGs in Rails?

What is the best way to tell rails to use weak instead of strong ETAGs when using methods fresh_when and stale??
The reason I ask is that nginx (correctly) removes strong ETAG headers from responses when on-the-fly gzipping is enabled.
I took the code from #grosser's answer and turned it into a Gem:
https://rubygems.org/gems/rails_weak_etags
https://github.com/johnnaegle/rails_weak_etags
You can just add this to your gemfile:
gem 'rails_weak_etags'
And it will be installed into your middleware before Rack::ConditionalGet:
> bundle exec rake middleware
....
use RailsWeakEtags::Middleware
use Rack::ConditionalGet
use Rack::ETag
....
Then all the e-tags generated by rails, either with Rack::ETag or with explicit e-tags will be converted to weak. Using a patched, or version > 1.7.3 of nginx, will then let you use e-tags and gzip compression.
RACK 1.6 defaults etags to weak - this gem is no longer helpful if you upgrade.
middleware:
class WeakEtagMiddleware
def initialize(app)
#app = app
end
def call(env)
# make request etags "strong"
etag = env['HTTP_IF_NONE_MATCH']
if etag && etag =~ /^W\//
env['HTTP_IF_NONE_MATCH'] = etag[2..-1]
end
status, headers, body = #app.call(env)
# make response etags "weak"
etag = headers['ETag']
if etag && etag !~ /^W\//
headers['ETag'] = "W/#{etag}"
end
[status, headers, body]
end
end
plus add middleware
Rails.application.config.middleware.insert_before(Rack::ETag, WeakEtagMiddleware)
plus unit tests
context WeakEtagMiddleware do
let(:backend) { Rack::ConditionalGet.new(Rack::ETag.new(lambda { |env| [env["status"] || 200, {}, ["XXX"]] })) }
let(:app) { WeakEtagMiddleware.new(backend) }
let(:expected_digest_1) { "bc9189406be84ec297464a514221406d" }
let(:env) { {"REQUEST_METHOD" => "GET"} }
should "converts etags to weak" do
status, headers, body = app.call(env)
assert_equal %{W/"#{expected_digest_1}"}, headers["ETag"]
assert_equal status, 200
end
should "not add etags to responses without etag" do
status, headers, body = app.call(env.merge("status" => 400))
refute headers["ETag"]
assert_equal status, 400
end
should "recognize weak ETags" do
status, headers, body = app.call(env.merge("HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_1}"}))
assert_equal status, 304
end
should "not recognize invalid ETags" do
status, headers, body = app.call(env.merge("HTTP_IF_NONE_MATCH" => %{W/"something-not-fresh"}))
assert_equal status, 200
end
end
plus integration tests
require_relative "../helpers/test_helper"
class WeakEtagsTest < ActionController::IntegrationTest
class TestController < ActionController::Base
def auto
render :text => "XXX"
end
def fresh
if stale? :etag => "YYY"
render :text => "XXX"
end
end
end
additional_routes do
get '/test/weak_etags/:action', :controller => 'weak_etags_test/test'
end
fixtures :accounts, :users
context "weak etags" do
let(:expected_digest_1) { "bc9189406be84ec297464a514221406d" }
let(:expected_digest_2) { "fd7c5c4fdaa97163ee4ba8842baa537a" }
should "auto adds weak etags" do
get "/test/weak_etags/auto"
assert_equal "XXX", #response.body
assert_equal %{W/"#{expected_digest_1}"}, #response.headers["ETag"]
end
should "adds weak etags through fresh_when" do
get "/test/weak_etags/fresh"
assert_equal "XXX", #response.body
assert_equal %{W/"#{expected_digest_2}"}, #response.headers["ETag"]
end
should "recognize auto-added ETags" do
get "/test/weak_etags/auto", {}, {"HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_1}"}}
assert_response :not_modified
end
should "recognize fresh ETags" do
get "/test/weak_etags/fresh", {}, {"HTTP_IF_NONE_MATCH" => %{W/"#{expected_digest_2}"}}
assert_response :not_modified
end
end
end
It looks like Rack::ETag will use weak-etags in the future:
https://github.com/rack/rack/issues/681
https://github.com/rack/rack/commit/12528d4567d8e6c1c7e9422fee6cd8b43c4389bf
Here's an alternative that avoids making any changes in your application server. This directive converts all etags returned by your application to weak etags before they get stripped from the response. Put it inside your inside your nginx config:
location / {
add_header ETag "W/$sent_http_ETAG";
}
I've checked that this works with nginx 1.7.6.

Mongrel::DirHandler equivalent for Passenger

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.

Resources