Skip to content

Commit

Permalink
Support Rack 3 (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
dentarg authored Oct 7, 2023
1 parent b97c5e7 commit a9cd089
Show file tree
Hide file tree
Showing 45 changed files with 197 additions and 115 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ permissions:

jobs:
test:
name: Ruby ${{ matrix.ruby }} Rack ${{ matrix.rack }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-20.04 ]
rack: [ '~> 2.0', '~> 3.0' ]
ruby: [ 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2 ]
gemfile: [ Gemfile ]
exclude:
# Rack 3 needs >= Ruby 2.4
- { ruby: 2.2, rack: '~> 3.0' }
- { ruby: 2.3, rack: '~> 3.0' }
runs-on: ${{ matrix.os }}
env:
RACK_VERSION: ${{ matrix.rack }}
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ gem 'minitest', '~> 5.6'
gem 'minitest-hooks', '~> 1.0'
gem 'mail', '~> 2.3', '>= 2.6.4'
gem 'nbio-csshttprequest', '~> 1.0'
gem 'rack', ENV['RACK_VERSION']
gem 'rake'
gem 'rdoc', '~> 5.0'
gem 'ruby-prof'
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/access.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def ipmasks_for_path(env)
end

def forbidden!
[403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
[403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
end

def ip_authorized?(request, ipmasks)
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/backstage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def call(env)
if File.exist?(@file)
content = File.read(@file)
length = content.bytesize.to_s
[503, {'Content-Type' => 'text/html', 'Content-Length' => length}, [content]]
[503, {'content-type' => 'text/html', 'content-length' => length}, [content]]
else
@app.call(env)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/bounce_favicon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def initialize(app)

def call(env)
if env["PATH_INFO"] == "/favicon.ico"
[404, {"Content-Type" => "text/html", "Content-Length" => "0"}, []]
[404, {"content-type" => "text/html", "content-length" => "0"}, []]
else
@app.call(env)
end
Expand Down
5 changes: 4 additions & 1 deletion lib/rack/contrib/common_cookies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ class CommonCookies
LOCALHOST_OR_IP_REGEXP = /^([\d.]+|localhost)$/
PORT = /:\d+$/

HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app)
@app = app
end

def call(env)
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
headers = HEADERS_KLASS.new.merge(headers)

host = env['HTTP_HOST'].sub PORT, ''
share_cookie(headers, host)
Expand Down
4 changes: 3 additions & 1 deletion lib/rack/contrib/csshttprequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Rack

# A Rack middleware for providing CSSHTTPRequest responses.
class CSSHTTPRequest
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app)
@app = app
Expand All @@ -15,7 +17,7 @@ def initialize(app)
# the CSSHTTPRequest encoder
def call(env)
status, headers, response = @app.call(env)
headers = Utils::HeaderHash.new(headers)
headers = HEADERS_KLASS.new.merge(headers)

if chr_request?(env)
encoded_response = encode(response)
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/deflect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def call env
end

def deflect!
[403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, []]
[403, { 'content-type' => 'text/html', 'content-length' => '0' }, []]
end

def deflect? env
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/enforce_valid_encoding.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def call env
Rack::Utils.unescape(full_path).valid_encoding?
@app.call env
else
[400, {'Content-Type'=>'text/plain'}, ['Bad Request']]
[400, {'content-type'=>'text/plain'}, ['Bad Request']]
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/rack/contrib/expectation_cascade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class ExpectationCascade
Expect = "HTTP_EXPECT".freeze
ContinueExpectation = "100-continue".freeze

ExpectationFailed = [417, {"Content-Type" => "text/html"}, []].freeze
NotFound = [404, {"Content-Type" => "text/html"}, []].freeze
ExpectationFailed = [417, {"content-type" => "text/html"}, []]
NotFound = [404, {"content-type" => "text/html"}, []]

attr_reader :apps

Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/host_meta.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def initialize(app, &block)

def call(env)
if env['PATH_INFO'] == '/host-meta'
[200, {'Content-Type' => 'application/host-meta'}, [@response]]
[200, {'content-type' => 'application/host-meta'}, [@response]]
else
@app.call(env)
end
Expand Down
8 changes: 4 additions & 4 deletions lib/rack/contrib/json_body_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ module Rack
# === Parse POST and GET requests only
# use Rack::JSONBodyParser, verbs: ['POST', 'GET']
#
# === Parse POST|PATCH|PUT requests whose Content-Type matches 'json'
# === Parse POST|PATCH|PUT requests whose content-type matches 'json'
# use Rack::JSONBodyParser, media: /json/
#
# === Parse POST requests whose Content-Type is 'application/json' or 'application/vnd+json'
# === Parse POST requests whose content-type is 'application/json' or 'application/vnd+json'
# use Rack::JSONBodyParser, verbs: ['POST'], media: ['application/json', 'application/vnd.api+json']
#
class JSONBodyParser
Expand Down Expand Up @@ -63,7 +63,7 @@ def call(env)
end
rescue JSON::ParserError
body = { error: 'Failed to parse body as JSON' }.to_json
header = { 'Content-Type' => 'application/json' }
header = { 'content-type' => 'application/json' }
return Rack::Response.new(body, 400, header).finish
end
@app.call(env)
Expand All @@ -75,7 +75,7 @@ def update_form_hash_with_json_body(env)
body = env[Rack::RACK_INPUT]
return unless (body_content = body.read) && !body_content.empty?

body.rewind # somebody might try to read this stream
body.rewind if body.respond_to?(:rewind) # somebody might try to read this stream
env.update(
Rack::RACK_REQUEST_FORM_HASH => @parser.call(body_content),
Rack::RACK_REQUEST_FORM_INPUT => body
Expand Down
7 changes: 5 additions & 2 deletions lib/rack/contrib/jsonp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class JSONP
# "\342\200\251" # => "\u2029"
U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"]

HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app)
@app = app
end
Expand All @@ -42,7 +45,7 @@ def call(env)
return status, headers, response
end

headers = HeaderHash.new(headers)
headers = HEADERS_KLASS.new.merge(headers)

if is_json?(headers) && has_callback?(request)
callback = request.params['callback']
Expand Down Expand Up @@ -108,7 +111,7 @@ def pad(callback, response)
end

def bad_request(body = "Bad Request")
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
[ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
end

end
Expand Down
10 changes: 5 additions & 5 deletions lib/rack/contrib/lazy_conditional_get.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ def initialize app, cache={}

def call env
if reading? env and fresh? env
return [304, {'Last-Modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
return [304, {'last-modified' => env['HTTP_IF_MODIFIED_SINCE']}, []]
end

status, headers, body = @app.call env
headers = Utils::HeaderHash.new(headers)
headers = Rack.release < "3" ? Utils::HeaderHash.new(headers) : headers

update_cache unless (reading?(env) or skipping?(headers))
headers['Last-Modified'] = cached_value if stampable? headers
headers['last-modified'] = cached_value if stampable? headers
[status, headers, body]
end

Expand All @@ -96,11 +96,11 @@ def reading? env
end

def skipping? headers
headers['Rack-Lazy-Conditional-Get'] == 'skip'
headers['rack-lazy-conditional-get'] == 'skip'
end

def stampable? headers
!headers.has_key?('Last-Modified') and headers['Rack-Lazy-Conditional-Get'] == 'yes'
!headers.has_key?('last-modified') and headers['rack-lazy-conditional-get'] == 'yes'
end

def update_cache
Expand Down
5 changes: 4 additions & 1 deletion lib/rack/contrib/locale.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

module Rack
class Locale
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app)
@app = app
end
Expand All @@ -16,7 +19,7 @@ def call(env)

env['rack.locale'] = I18n.locale = locale.to_s
status, headers, body = @app.call(env)
headers = Utils::HeaderHash.new(headers)
headers = HEADERS_KLASS.new.merge(headers)

unless headers['Content-Language']
headers['Content-Language'] = locale.to_s
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/mailexceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def send_notification(exception, env)

def extract_body(env)
if io = env['rack.input']
io.rewind
io.rewind if io.respond_to?(:rewind)
io.read
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/contrib/not_found.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def initialize(path = nil, content_type = 'text/html')
end

def call(env)
[404, {'Content-Type' => @content_type, 'Content-Length' => @length}, [@content]]
[404, {'content-type' => @content_type, 'content-length' => @length}, [@content]]
end
end
end
4 changes: 2 additions & 2 deletions lib/rack/contrib/post_body_content_type_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def initialize(app, &block)

def call(env)
if Rack::Request.new(env).media_type == APPLICATION_JSON && (body = env[POST_BODY].read).length != 0
env[POST_BODY].rewind # somebody might try to read this stream
env[POST_BODY].rewind if env[POST_BODY].respond_to?(:rewind) # somebody might try to read this stream
env.update(FORM_HASH => @block.call(body), FORM_INPUT => env[POST_BODY])
end
@app.call(env)
Expand All @@ -84,7 +84,7 @@ def call(env)
end

def bad_request(body = 'Bad Request')
[ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s }, [body] ]
[ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
end
end
end
4 changes: 2 additions & 2 deletions lib/rack/contrib/profiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ def print(printer, result)
end

def headers(printer, env, mode)
headers = { 'Content-Type' => CONTENT_TYPES[printer.name] }
headers = { 'content-type' => CONTENT_TYPES[printer.name] }
if printer == ::RubyProf::CallTreePrinter
filename = ::File.basename(env['PATH_INFO'])
headers['Content-Disposition'] =
headers['content-disposition'] =
%(attachment; filename="#{filename}.#{mode}.tree")
end
headers
Expand Down
3 changes: 2 additions & 1 deletion lib/rack/contrib/relative_redirect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ def initialize(app, &block)
# does not start with a slash, make location relative to the path requested.
def call(env)
status, headers, body = @app.call(env)
headers = Rack::Utils::HeaderHash.new(headers)
headers_klass = Rack.release < "3" ? Rack::Utils::HeaderHash : Rack::Headers
headers = headers_klass.new.merge(headers)

if [301,302,303, 307,308].include?(status) and loc = headers['Location'] and !%r{\Ahttps?://}o.match(loc)
absolute = @absolute_proc.call(env, [status, headers, body])
Expand Down
3 changes: 2 additions & 1 deletion lib/rack/contrib/response_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def initialize(app, cache, &block)
# subdirectory of cache. Otherwise, cache the body of the response as the value with the path as the key.
def call(env)
status, headers, body = @app.call(env)
headers = Rack::Utils::HeaderHash.new(headers)
headers_klass = Rack.release < "3" ? Rack::Utils::HeaderHash : Rack::Headers
headers = headers_klass.new.merge(headers)

if env['REQUEST_METHOD'] == 'GET' and env['QUERY_STRING'] == '' and status == 200 and path = @path_proc.call(env, [status, headers, body])
if @cache.is_a?(String)
Expand Down
8 changes: 6 additions & 2 deletions lib/rack/contrib/response_headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@

module Rack
# Allows you to tap into the response headers. Yields a Rack::Utils::HeaderHash
# of current response headers to the block. Example:
# (Rack 2) or a Rack::Headers (Rack 3) of current response headers to the block.
# Example:
#
# use Rack::ResponseHeaders do |headers|
# headers['X-Foo'] = 'bar'
# headers.delete('X-Baz')
# end
#
class ResponseHeaders
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app, &block)
@app = app
@block = block
end

def call(env)
response = @app.call(env)
headers = Utils::HeaderHash.new(response[1])
headers = HEADERS_KLASS.new.merge(response[1])
@block.call(headers)
response[1] = headers
response
Expand Down
6 changes: 4 additions & 2 deletions lib/rack/contrib/static_cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ module Rack


class StaticCache
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS

def initialize(app, options={})
@app = app
Expand All @@ -67,7 +69,7 @@ def initialize(app, options={})
end
end
root = options[:root] || Dir.pwd
@file_server = Rack::File.new(root)
@file_server = Rack::Files.new(root)
@cache_duration = options[:duration] || 1
@versioning_enabled = options.fetch(:versioning, true)
if @versioning_enabled
Expand All @@ -87,7 +89,7 @@ def call(env)
end

status, headers, body = @file_server.call(env)
headers = Utils::HeaderHash.new(headers)
headers = HEADERS_KLASS.new.merge(headers)

if @no_cache[url].nil?
headers['Cache-Control'] ="max-age=#{@duration_in_seconds}, public"
Expand Down
2 changes: 1 addition & 1 deletion rack-contrib.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Gem::Specification.new do |s|

s.required_ruby_version = '>= 2.2.2'

s.add_runtime_dependency 'rack', '~> 2.0'
s.add_runtime_dependency 'rack', '< 4'

s.homepage = "https://github.com/rack/rack-contrib/"
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "rack-contrib", "--main", "README"]
Expand Down
2 changes: 1 addition & 1 deletion test/spec_rack_access.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
describe "Rack::Access" do

before do
@app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['hello']] }
@app = lambda { |env| [200, { 'content-type' => 'text/plain' }, ['hello']] }
@mock_addr_1 = '111.111.111.111'
@mock_addr_2 = '192.168.1.222'
@mock_addr_localhost = '127.0.0.1'
Expand Down
Loading

0 comments on commit a9cd089

Please sign in to comment.