Skip to content

Commit

Permalink
Add the ability of returning arbitrary headers to ActionDispatch::Static
Browse files Browse the repository at this point in the history
Now ActionDispatch::Static can accept HTTP headers so that developers
will have control of returning arbitrary headers like
'Access-Control-Allow-Origin' when a response is delivered. They can
be configured through `#config.public_file_server.headers`:

  config.public_file_server.headers = {
    "Cache-Control"               => "public, max-age=60",
    "Access-Control-Allow-Origin" => "http://rubyonrails.org"
  }

Also deprecate `config.static_cache_control` in favor of
`config.public_file_server.headers`.
  • Loading branch information
yuki24 committed May 2, 2015
1 parent 2e7fd4a commit 57e68ad
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 18 deletions.
19 changes: 19 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
* Deprecate `config.static_cache_control` in favor of
`config.public_file_server.headers`

*Yuki Nishijima*

* Add the ability of returning arbitrary headers to ActionDispatch::Static

Now ActionDispatch::Static can accept HTTP headers so that developers
will have control of returning arbitrary headers like
'Access-Control-Allow-Origin' when a response is delivered. They can be
configured with `#config`:

config.public_file_server.headers = {
"Cache-Control" => "public, max-age=60",
"Access-Control-Allow-Origin" => "http://rubyonrails.org"
}

*Yuki Nishijima*

* Fix rake routes not showing the right format when
nesting multiple routes.

Expand Down
20 changes: 13 additions & 7 deletions actionpack/lib/action_dispatch/middleware/static.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

module ActionDispatch
# This middleware returns a file's contents from disk in the body response.
# When initialized, it can accept an optional 'Cache-Control' header, which
# will be set when a response containing a file's contents is delivered.
# When initialized, it can accept optional HTTP headers, which will be set
# when a response containing a file's contents is delivered.
#
# This middleware will render the file specified in `env["PATH_INFO"]`
# where the base path is in the +root+ directory. For example, if the +root+
Expand All @@ -13,11 +13,10 @@ module ActionDispatch
# located at `public/assets/application.js` if the file exists. If the file
# does not exist, a 404 "File not Found" response will be returned.
class FileHandler
def initialize(root, cache_control)
def initialize(root, headers: )
@root = root.chomp('/')
@compiled_root = /^#{Regexp.escape(root)}/
headers = cache_control && { 'Cache-Control' => cache_control }
@file_server = ::Rack::File.new(@root, headers)
@file_server = ::Rack::File.new(@root, headers)
end


Expand Down Expand Up @@ -104,9 +103,16 @@ def gzip_file_path(path)
# produce a directory traversal using this middleware. Only 'GET' and 'HEAD'
# requests will result in a file being returned.
class Static
def initialize(app, path, cache_control=nil)
def initialize(app, path, deprecated_cache_control = :not_set, headers: nil)
if deprecated_cache_control != :not_set
ActiveSupport::Deprecation.warn("The `cache_control = '...'` argument is deprecated," \
"replaced by `headers: { 'Cache-Control' => '...' }`, " \
" and will be removed in Rails 5.1.")
(headers ||= {})['Cache-Control'.freeze] = deprecated_cache_control
end

@app = app
@file_handler = FileHandler.new(path, cache_control)
@file_handler = FileHandler.new(path, headers: headers)
end

def call(env)
Expand Down
3 changes: 3 additions & 0 deletions actionpack/lib/action_dispatch/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Railtie < Rails::Railtie # :nodoc:
'X-Content-Type-Options' => 'nosniff'
}

config.public_file_server = ActiveSupport::OrderedOptions.new
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }

config.eager_load_namespaces << ActionDispatch

initializer "action_dispatch.configure" do |app|
Expand Down
33 changes: 26 additions & 7 deletions actionpack/test/dispatch/static_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
require 'zlib'

module StaticTests
DummyApp = lambda { |env|
[200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
}

def setup
silence_warnings do
@default_internal_encoding = Encoding.default_internal
Expand Down Expand Up @@ -37,7 +41,11 @@ def test_handles_urls_with_ascii_8bit_on_win_31j
end

def test_sets_cache_control
response = get("/index.html")
app = assert_deprecated do
ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
end
response = Rack::MockRequest.new(app).request("GET", "/index.html")

assert_html "/index.html", response
assert_equal "public, max-age=60", response.headers["Cache-Control"]
end
Expand Down Expand Up @@ -179,6 +187,21 @@ def test_serves_gzip_files_with_not_modified
assert_equal nil, response.headers['Vary']
end

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

# Windows doesn't allow \ / : * ? " < > | in filenames
unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
def test_serves_static_file_with_colon
Expand Down Expand Up @@ -229,14 +252,10 @@ def with_static_file(file)
end

class StaticTest < ActiveSupport::TestCase
DummyApp = lambda { |env|
[200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
}

def setup
super
@root = "#{FIXTURE_LOAD_PATH}/public"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
@app = ActionDispatch::Static.new(DummyApp, @root, headers: {'Cache-Control' => "public, max-age=60"})
end

def public_path
Expand Down Expand Up @@ -266,7 +285,7 @@ class StaticEncodingTest < StaticTest
def setup
super
@root = "#{FIXTURE_LOAD_PATH}/公共"
@app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
@app = ActionDispatch::Static.new(DummyApp, @root, headers: {'Cache-Control' => "public, max-age=60"})
end

def public_path
Expand Down
12 changes: 10 additions & 2 deletions railties/lib/rails/application/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ class Configuration < ::Rails::Engine::Configuration
:eager_load, :exceptions_app, :file_watcher, :filter_parameters,
:force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
:railties_order, :relative_url_root, :secret_key_base, :secret_token,
:serve_static_files, :ssl_options, :static_cache_control, :session_options,
:serve_static_files, :ssl_options, :session_options,
:time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x

attr_writer :log_level
attr_reader :encoding
attr_reader :encoding, :static_cache_control

def initialize(*)
super
Expand Down Expand Up @@ -51,6 +51,14 @@ def initialize(*)
@x = Custom.new
end

def static_cache_control=(value)
ActiveSupport::Deprecation.warn("static_cache_control is deprecated and will be removed in Rails 5.1. " \
"Please use `config.public_file_server.headers['Cache-Control'] = #{value} instead.")

@static_cache_control = value
public_file_server.headers['Cache-Control'.freeze] = value
end

def encoding=(value)
@encoding = value
silence_warnings do
Expand Down
2 changes: 1 addition & 1 deletion railties/lib/rails/application/default_middleware_stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def build_stack
middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header

if config.serve_static_files
middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control
middleware.use ::ActionDispatch::Static, paths["public"].first, headers: config.public_file_server.headers
end

if rack_cache = load_rack_cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Rails.application.configure do

# Configure static file server for tests with Cache-Control for performance.
config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'

# Show full error reports and disable caching.
config.consider_all_requests_local = true
Expand Down
10 changes: 10 additions & 0 deletions railties/test/application/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,16 @@ def assert_utf8
end
end

test "config.static_cache_control is deprecated" do
make_basic_app do |application|
assert_deprecated do
application.config.static_cache_control = "public, max-age=60"
end

assert_equal application.config.static_cache_control, "public, max-age=60"
end
end

test "Use key_generator when secret_key_base is set" do
make_basic_app do |application|
application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33'
Expand Down

0 comments on commit 57e68ad

Please sign in to comment.