From 61dda4ef9579abed17ad1715cdb950f33c5b24d1 Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Thu, 8 Sep 2022 23:19:37 +1000 Subject: [PATCH 1/7] bump version of rack-test to allow for rack-3 --- sprockets.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprockets.gemspec b/sprockets.gemspec index 6ef3332cc..8c7cc45a2 100644 --- a/sprockets.gemspec +++ b/sprockets.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |s| s.add_development_dependency "timecop", "~> 0.9.1" s.add_development_dependency "minitest", "~> 5.0" s.add_development_dependency "nokogiri", "~> 1.3" - s.add_development_dependency "rack-test", "~> 0.6" + s.add_development_dependency "rack-test", "~> 2.0.0" s.add_development_dependency "rake", "~> 12.0" s.add_development_dependency "sass", "~> 3.4" s.add_development_dependency "sassc", "~> 2.0" From 0f4783e48a777d7c7febfdae48415ef2ce58517c Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Thu, 8 Sep 2022 23:23:03 +1000 Subject: [PATCH 2/7] relax rack --- sprockets.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprockets.gemspec b/sprockets.gemspec index 8c7cc45a2..47f20e14b 100644 --- a/sprockets.gemspec +++ b/sprockets.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.files = Dir["README.md", "CHANGELOG.md", "MIT-LICENSE", "lib/**/*.rb"] s.executables = ["sprockets"] - s.add_dependency "rack", "> 1", "< 3" + s.add_dependency "rack", "> 1", "< 4" s.add_dependency "concurrent-ruby", "~> 1.0" s.add_development_dependency "m", ">= 0" From 12e5fd99af92b39d25dadfed206c1f3c19997375 Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Thu, 8 Sep 2022 23:23:13 +1000 Subject: [PATCH 3/7] require rack/lint --- test/sprockets_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/sprockets_test.rb b/test/sprockets_test.rb index 51fc12174..8b45d0aa1 100644 --- a/test/sprockets_test.rb +++ b/test/sprockets_test.rb @@ -4,6 +4,7 @@ require "sprockets/environment" require "fileutils" require "timecop" +require "rack/lint" old_verbose, $VERBOSE = $VERBOSE, false Encoding.default_external = 'UTF-8' From 06925180af205000b689345061cfa62faad85ad6 Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Thu, 8 Sep 2022 23:35:31 +1000 Subject: [PATCH 4/7] lower case headers --- lib/sprockets/server.rb | 36 +++++++++---------- test/test_server.rb | 76 ++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/lib/sprockets/server.rb b/lib/sprockets/server.rb index 231ea7505..f3cdb4f54 100644 --- a/lib/sprockets/server.rb +++ b/lib/sprockets/server.rb @@ -148,39 +148,39 @@ def not_modified_response(env, etag) # Returns a 400 Forbidden response tuple def bad_request_response(env) if head_request?(env) - [ 400, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ] + [ 400, { "content-type" => "text/plain", "content-length" => "0" }, [] ] else - [ 400, { "Content-Type" => "text/plain", "Content-Length" => "11" }, [ "Bad Request" ] ] + [ 400, { "content-type" => "text/plain", "content-length" => "11" }, [ "Bad Request" ] ] end end # Returns a 403 Forbidden response tuple def forbidden_response(env) if head_request?(env) - [ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ] + [ 403, { "content-type" => "text/plain", "content-length" => "0" }, [] ] else - [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ] + [ 403, { "content-type" => "text/plain", "content-length" => "9" }, [ "Forbidden" ] ] end end # Returns a 404 Not Found response tuple def not_found_response(env) if head_request?(env) - [ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ] + [ 404, { "content-type" => "text/plain", "content-length" => "0", "x-cascade" => "pass" }, [] ] else - [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ] + [ 404, { "content-type" => "text/plain", "content-length" => "9", "x-cascade" => "pass" }, [ "Not found" ] ] end end def method_not_allowed_response - [ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ] + [ 405, { "content-type" => "text/plain", "content-length" => "18" }, [ "Method Not Allowed" ] ] end def precondition_failed_response(env) if head_request?(env) - [ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ] + [ 412, { "content-type" => "text/plain", "content-length" => "0", "x-cascade" => "pass" }, [] ] else - [ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ] + [ 412, { "content-type" => "text/plain", "content-length" => "19", "x-cascade" => "pass" }, [ "Precondition Failed" ] ] end end @@ -189,7 +189,7 @@ def precondition_failed_response(env) def javascript_exception_response(exception) err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})" body = "throw Error(#{err.inspect})" - [ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ] + [ 200, { "content-type" => "application/javascript", "content-length" => body.bytesize.to_s }, [ body ] ] end # Returns a CSS response that hides all elements on the page and @@ -242,7 +242,7 @@ def css_exception_response(exception) } CSS - [ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ] + [ 200, { "content-type" => "text/css; charset=utf-8", "content-length" => body.bytesize.to_s }, [ body ] ] end # Escape special characters for use inside a CSS content("...") string @@ -258,18 +258,18 @@ def cache_headers(env, etag) headers = {} # Set caching headers - headers["Cache-Control"] = +"public" - headers["ETag"] = %("#{etag}") + headers["cache-control"] = +"public" + headers["etag"] = %("#{etag}") # If the request url contains a fingerprint, set a long # expires on the response if path_fingerprint(env["PATH_INFO"]) - headers["Cache-Control"] << ", max-age=31536000, immutable" + headers["cache-control"] << ", max-age=31536000, immutable" # Otherwise set `must-revalidate` since the asset could be modified. else - headers["Cache-Control"] << ", must-revalidate" - headers["Vary"] = "Accept-Encoding" + headers["cache-control"] << ", must-revalidate" + headers["vary"] = "Accept-Encoding" end headers @@ -279,7 +279,7 @@ def headers(env, asset, length) headers = {} # Set content length header - headers["Content-Length"] = length.to_s + headers["content-length"] = length.to_s # Set content type header if type = asset.content_type @@ -287,7 +287,7 @@ def headers(env, asset, length) if type.start_with?("text/") && asset.charset type += "; charset=#{asset.charset}" end - headers["Content-Type"] = type + headers["content-type"] = type end headers.merge(cache_headers(env, asset.etag)) diff --git a/test/test_server.rb b/test/test_server.rb index 8f2259102..f6689a209 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -36,15 +36,15 @@ def app test "serve single source file" do get "/assets/foo.js" assert_equal 200, last_response.status - assert_equal "9", last_response.headers['Content-Length'] - assert_equal "Accept-Encoding", last_response.headers['Vary'] + assert_equal "9", last_response.headers['content-length'] + assert_equal "Accept-Encoding", last_response.headers['vary'] assert_equal "var foo;\n", last_response.body end test "serve single self file" do get "/assets/foo.self.js" assert_equal 200, last_response.status - assert_equal "9", last_response.headers['Content-Length'] + assert_equal "9", last_response.headers['content-length'] assert_equal "var foo;\n", last_response.body end @@ -64,15 +64,15 @@ def app assert_equal 200, last_response.status assert_equal "\n(function() {\n application.boot();\n})();\n", last_response.body - assert_equal "43", last_response.headers['Content-Length'] + assert_equal "43", last_response.headers['content-length'] end test "serve source with content type headers" do get "/assets/application.js" - assert_equal "application/javascript", last_response.headers['Content-Type'] + assert_equal "application/javascript", last_response.headers['content-type'] get "/assets/bootstrap.css" - assert_equal "text/css; charset=utf-8", last_response.headers['Content-Type'] + assert_equal "text/css; charset=utf-8", last_response.headers['content-type'] end test "serve source with etag headers" do @@ -80,14 +80,14 @@ def app get "/assets/application.js" assert_equal "\"#{digest}\"", - last_response.headers['ETag'] + last_response.headers['etag'] end test "not modified partial response when if-none-match etags match" do get "/assets/application.js" assert_equal 200, last_response.status etag, cache_control, expires, vary = last_response.headers.values_at( - 'ETag', 'Cache-Control', 'Expires', 'Vary' + 'etag', 'cache-control', 'expires', 'vary' ) assert_nil expires @@ -97,15 +97,15 @@ def app assert_equal 304, last_response.status # Allow 304 headers - assert_equal cache_control, last_response.headers['Cache-Control'] - assert_equal etag, last_response.headers['ETag'] + assert_equal cache_control, last_response.headers['cache-control'] + assert_equal etag, last_response.headers['etag'] assert_nil last_response.headers['Expires'] - assert_equal vary, last_response.headers['Vary'] + assert_equal vary, last_response.headers['vary'] # Disallowed 304 headers - refute last_response.headers['Content-Type'] - refute last_response.headers['Content-Length'] - refute last_response.headers['Content-Encoding'] + refute last_response.headers['content-type'] + refute last_response.headers['content-length'] + refute last_response.headers['content-encoding'] end test "response when if-none-match etags don't match" do @@ -113,15 +113,15 @@ def app 'HTTP_IF_NONE_MATCH' => "nope" assert_equal 200, last_response.status - assert_equal '"b452c9ae1d5c8d9246653e0d93bc83abce0ee09ef725c0f0a29a41269c217b83"', last_response.headers['ETag'] - assert_equal '52', last_response.headers['Content-Length'] + assert_equal '"b452c9ae1d5c8d9246653e0d93bc83abce0ee09ef725c0f0a29a41269c217b83"', last_response.headers['etag'] + assert_equal '52', last_response.headers['content-length'] end test "not modified partial response with fingerprint and if-none-match etags match" do get "/assets/application.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] digest = etag[/"(.+)"/, 1] get "/assets/application-#{digest}.js", {}, @@ -133,7 +133,7 @@ def app get "/assets/prehashed-988881adc9fc3655077dc2d4d757d480b5ea0e11.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] digest = etag[/"(.+)"/, 1] assert_equal 'edabfd0f1ac5fcdae82cc7d92d1c52abb671797a3948fa9040aec1db8e61c327', digest @@ -143,7 +143,7 @@ def app get "/assets/esbuild-TQDC3LZV.digested.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] digest = etag[/"(.+)"/, 1] assert_equal '3ebac3dc00b383de6cbdfa470d105f5a9f22708fb72c63db917ad37f288ac708', digest @@ -153,7 +153,7 @@ def app get "/assets/application.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] digest = etag[/"(.+)"/, 1] get "/assets/application-#{digest}.js", {}, @@ -177,7 +177,7 @@ def app get "/assets/application.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] get "/assets/application-0000000000000000000000000000000000000000.js", {}, 'HTTP_IF_NONE_MATCH' => etag @@ -187,14 +187,14 @@ def app test "ok partial response when if-match etags match" do get "/assets/application.js" assert_equal 200, last_response.status - etag = last_response.headers['ETag'] + etag = last_response.headers['etag'] get "/assets/application.js", {}, 'HTTP_IF_MATCH' => etag assert_equal 200, last_response.status - assert_equal '"b452c9ae1d5c8d9246653e0d93bc83abce0ee09ef725c0f0a29a41269c217b83"', last_response.headers['ETag'] - assert_equal '52', last_response.headers['Content-Length'] + assert_equal '"b452c9ae1d5c8d9246653e0d93bc83abce0ee09ef725c0f0a29a41269c217b83"', last_response.headers['etag'] + assert_equal '52', last_response.headers['content-length'] end test "precondition failed with if-match is a mismatch" do @@ -202,7 +202,7 @@ def app 'HTTP_IF_MATCH' => '"000"' assert_equal 412, last_response.status - refute last_response.headers['ETag'] + refute last_response.headers['etag'] end test "not found with if-match" do @@ -225,23 +225,23 @@ def app test "fingerprint digest sets expiration to the future" do get "/assets/application.js" - digest = last_response.headers['ETag'][/"(.+)"/, 1] + digest = last_response.headers['etag'][/"(.+)"/, 1] get "/assets/application-#{digest}.js" assert_equal 200, last_response.status - assert_match %r{max-age}, last_response.headers['Cache-Control'] - assert_match %r{immutable}, last_response.headers['Cache-Control'] + assert_match %r{max-age}, last_response.headers['cache-control'] + assert_match %r{immutable}, last_response.headers['cache-control'] end test "fingerprint digest of file self" do get "/assets/application.self.js" - digest = last_response.headers['ETag'][/"(.+)"/, 1] + digest = last_response.headers['etag'][/"(.+)"/, 1] get "/assets/application.self-#{digest}.js" assert_equal 200, last_response.status assert_equal "\n(function() {\n application.boot();\n})();\n", last_response.body - assert_equal "43", last_response.headers['Content-Length'] - assert_match %r{max-age}, last_response.headers['Cache-Control'] + assert_equal "43", last_response.headers['content-length'] + assert_match %r{max-age}, last_response.headers['cache-control'] end test "bad fingerprint digest returns a 404" do @@ -250,14 +250,14 @@ def app head "/assets/application-0000000000000000000000000000000000000000.js" assert_equal 404, last_response.status - assert_equal "0", last_response.headers['Content-Length'] + assert_equal "0", last_response.headers['content-length'] assert_equal "", last_response.body end test "missing source" do get "/assets/none.js" assert_equal 404, last_response.status - assert_equal "pass", last_response.headers['X-Cascade'] + assert_equal "pass", last_response.headers['x-cascade'] end test "re-throw JS exceptions in the browser" do @@ -302,7 +302,7 @@ def app head "/assets/.-0000000./etc/passwd" assert_equal 403, last_response.status - assert_equal "0", last_response.headers['Content-Length'] + assert_equal "0", last_response.headers['content-length'] assert_equal "", last_response.body end @@ -336,8 +336,8 @@ def app test "serving static assets" do get "/assets/logo.png" assert_equal 200, last_response.status - assert_equal "image/png", last_response.headers['Content-Type'] - refute last_response.headers['Content-Encoding'] + assert_equal "image/png", last_response.headers['content-type'] + refute last_response.headers['content-encoding'] assert_equal File.binread(fixture_path("server/app/images/logo.png")), last_response.body end @@ -347,8 +347,8 @@ def app head "/assets/foo.js" assert_equal 200, last_response.status - assert_equal "application/javascript", last_response.headers['Content-Type'] - assert_equal "0", last_response.headers['Content-Length'] + assert_equal "application/javascript", last_response.headers['content-type'] + assert_equal "0", last_response.headers['content-length'] assert_equal "", last_response.body post "/assets/foo.js" From 17d77237f60aa0c2c339e414b2efd3a658a872e7 Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Fri, 9 Sep 2022 01:36:39 +1000 Subject: [PATCH 5/7] Add Changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 844a0c16c..73176bf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Get upgrade notes from Sprockets 3.x to 4.x at https://github.com/rails/sprockets/blob/master/UPGRADING.md -- Fix `Sprockets::Server` to return response headers to compatible with with Rack::Lint 2.0. +- Add support for Rack 3.0 [#758](https://github.com/rails/sprockets/pull/758) ## 4.1.0 From 8d2862ebc4a269185681fc88d489404664a2d63f Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Sun, 11 Sep 2022 08:59:49 +1000 Subject: [PATCH 6/7] unsure that we are using a version of rack that supports lower case headers --- sprockets.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sprockets.gemspec b/sprockets.gemspec index 47f20e14b..b3e0ec923 100644 --- a/sprockets.gemspec +++ b/sprockets.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.files = Dir["README.md", "CHANGELOG.md", "MIT-LICENSE", "lib/**/*.rb"] s.executables = ["sprockets"] - s.add_dependency "rack", "> 1", "< 4" + s.add_dependency "rack", ">= 2.2.4", "< 4" s.add_dependency "concurrent-ruby", "~> 1.0" s.add_development_dependency "m", ">= 0" From a9f9ebb2bd902034314ae7950746dd54928891bb Mon Sep 17 00:00:00 2001 From: Lachlan Sylvester Date: Sun, 11 Sep 2022 09:10:16 +1000 Subject: [PATCH 7/7] update changelog entry to make it explict that the headers will change --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73176bf0f..a9e699cb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ Get upgrade notes from Sprockets 3.x to 4.x at https://github.com/rails/sprockets/blob/master/UPGRADING.md -- Add support for Rack 3.0 [#758](https://github.com/rails/sprockets/pull/758) +- Add support for Rack 3.0. Headers set by sprockets will now be lower case. [#758](https://github.com/rails/sprockets/pull/758) ## 4.1.0