Skip to content

Commit

Permalink
Add plain_hash_response_headers plugin, using a plain hash for respon…
Browse files Browse the repository at this point in the history
…se headers on Rack 3 for much better performance

This plugin changes behavior on Rack 3 to use a plain hash for
response headers, as Roda has historically done for Rack 2,
instead of the default behavior of using Rack::Headers.
Using this plugin gains back most the performance lost in the
switch to Rack 3.

With Empty Roda App (Roda.route{''}):

Before switch to lower case headers on Rack 3:

* With Rack 2: 190k i/s
* With Rack 3: 120k i/s

After switch to lower case headers on Rack 3:

* With Rack 2: 185k i/s
* With Rack 3:  92k i/s
* With Rack 3 and plain_hash_response_headers plugin: 182k i/s

Support running all of Roda's specs with the
plain_hash_response_headers plugin using the
PLAIN_HASH_RESPONSE_HEADERS environment variable.
  • Loading branch information
jeremyevans committed Jun 19, 2023
1 parent 837781d commit bb137bc
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
= master

* Add plain_hash_response_headers plugin, using a plain hash for response headers on Rack 3 for much better performance (jeremyevans)

* Use lower case response header keys by default on Rack 3, instead of relying on Rack::Headers conversion (jeremyevans)

= 3.69.0 (2023-06-13)
Expand Down
32 changes: 32 additions & 0 deletions lib/roda/plugins/plain_hash_response_headers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen-string-literal: true

#
class Roda
module RodaPlugins
# The response_headers_plain_hash plugin will change Roda to
# use a plain hash for response headers. This is Roda's
# default behavior on Rack 2, but on Rack 3+, Roda defaults
# to using Rack::Headers for response headers for backwards
# compatibility (Rack::Headers automatically lower cases header
# keys).
#
# On Rack 3+, you should use this plugin for better performance
# if you are sure all headers in your application and middleware
# are already lower case (lower case response header keys are
# required by the Rack 3 spec).
module PlainHashResponseHeaders
if defined?(Rack::Headers) && Rack::Headers.is_a?(Class)
module ResponseMethods
private

# Use plain hash for headers
def _initialize_headers
{}
end
end
end
end

register_plugin(:plain_hash_response_headers, PlainHashResponseHeaders)
end
end
23 changes: 23 additions & 0 deletions spec/plugin/plain_hash_response_headers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require_relative "../spec_helper"

describe "plain_hash_response_headers plugin" do
it "uses plain hashes for response headers" do
app(:plain_hash_response_headers) do |r|
r.get 'up' do
response.headers['UP'] = 'U'
end

response.headers['down'] = 'd'
end

if Rack.release >= '3' && ENV['LINT']
proc{req('/up')}.must_raise Rack::Lint::LintError
else
header('up', '/up').must_be_nil
header('UP', '/up').must_equal 'U'
end
header('down').must_equal 'd'
header('DOWN').must_be_nil
req[1].must_be_instance_of Hash
end
end
26 changes: 26 additions & 0 deletions spec/response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,32 @@
header('foo').must_equal "bar"
body.must_equal 'true'
end

it "uses plain hash for response headers" do
app do |r|
response.headers['UP'] = 'U'
response.headers['down'] = 'd'
end

req[1].must_be_instance_of Hash
header('up').must_be_nil
header('UP').must_equal 'U'
header('down').must_equal 'd'
header('DOWN').must_be_nil
end if Rack.release < '3'

it "uses Rack::Headers for response headers" do
app do |r|
response.headers['UP'] = 'U'
response.headers['down'] = 'd'
end

req[1].must_be_instance_of Rack::Headers
header('up').must_equal 'U'
header('UP').must_equal 'U'
header('down').must_equal 'd'
header('DOWN').must_equal 'd'
end if Rack.release >= '3'
end

describe "response #write" do
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
end
end

if ENV['PLAIN_HASH_RESPONSE_HEADERS']
Roda.plugin :plain_hash_response_headers
end

RodaResponseHeaders = Roda::RodaResponseHeaders

$RODA_WARN = true
Expand Down
1 change: 1 addition & 0 deletions www/pages/documentation.erb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
<li><a href="rdoc/classes/Roda/RodaPlugins/DropBody.html">drop_body</a>: Automatically drops response body and Content-Type/Content-Length headers for response statuses indicating no body.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/Halt.html">halt</a>: Augments request halt method for support for setting response status and/or response body.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/ModuleInclude.html">module_include</a>: Adds request_module and response_module class methods for adding modules/methods to request/response classes.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/PlainHashResponseHeaders.html">plain_hash_response_headers</a>: Uses plain hashes for response headers on Rack 3, for much better performance.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/R.html">r</a>: Adds r method for accessing the request, useful when r local variable is not in scope.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/RequestAref.html">request_aref</a>: Adds configurable handling for [] and []= request methods.</li>
<li><a href="rdoc/classes/Roda/RodaPlugins/RequestHeaders.html">request_headers</a>: Adds a headers method to the request object, for easier access to request headers.</li>
Expand Down

0 comments on commit bb137bc

Please sign in to comment.