mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Add early hints feature (#1403)
This commit adds the early hints lambda to the headers hash. Users can call it to emit the early hints headers. For example: ``` class Server def call env if env["REQUEST_PATH"] == "/" env['rack.early_hints'].call("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload") [200, { "X-Hello" => "World" }, ["Hello world!"]] else [200, { "X-Hello" => "World" }, ["NEAT!"]] end end end run Server.new ``` In this example, the server sends stylesheet and javascript early hints if the proxy supports it, it will send H2 pushes to the client. Of course not every proxy server supports early hints, so to enable the early hints feature with puma you have to pass the configuration variable, `--early-hints`. If `ENV['rack.early_hints']` is not set then early hints is not supported by the webserver. Early hints is off by default.
This commit is contained in:
parent
eb70beb1a5
commit
01699742a8
6 changed files with 88 additions and 1 deletions
|
@ -181,6 +181,10 @@ module Puma
|
|||
user_config.tcp_mode!
|
||||
end
|
||||
|
||||
o.on "--early-hints", "Enable early hints support" do
|
||||
user_config.early_hints
|
||||
end
|
||||
|
||||
o.on "-V", "--version", "Print the version information" do
|
||||
puts "puma version #{Puma::Const::VERSION}"
|
||||
exit 0
|
||||
|
|
|
@ -221,5 +221,7 @@ module Puma
|
|||
HIJACK_P = "rack.hijack?".freeze
|
||||
HIJACK = "rack.hijack".freeze
|
||||
HIJACK_IO = "rack.hijack_io".freeze
|
||||
|
||||
EARLY_HINTS = "rack.early_hints".freeze
|
||||
end
|
||||
end
|
||||
|
|
|
@ -241,6 +241,10 @@ module Puma
|
|||
@options[:mode] = :tcp
|
||||
end
|
||||
|
||||
def early_hints(answer=true)
|
||||
@options[:early_hints] = answer
|
||||
end
|
||||
|
||||
# Redirect STDOUT and STDERR to files specified.
|
||||
def stdout_redirect(stdout=nil, stderr=nil, append=false)
|
||||
@options[:redirect_stdout] = stdout
|
||||
|
|
|
@ -161,6 +161,10 @@ module Puma
|
|||
server.tcp_mode!
|
||||
end
|
||||
|
||||
if @options[:early_hints]
|
||||
server.early_hints = true
|
||||
end
|
||||
|
||||
unless development?
|
||||
server.leak_stack_on_error = false
|
||||
end
|
||||
|
|
|
@ -81,7 +81,7 @@ module Puma
|
|||
@precheck_closing = true
|
||||
end
|
||||
|
||||
attr_accessor :binder, :leak_stack_on_error
|
||||
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
||||
|
||||
forward :add_tcp_listener, :@binder
|
||||
forward :add_ssl_listener, :@binder
|
||||
|
@ -595,6 +595,24 @@ module Puma
|
|||
env[RACK_INPUT] = body
|
||||
env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
|
||||
|
||||
if @early_hints
|
||||
env[EARLY_HINTS] = lambda { |headers|
|
||||
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
||||
|
||||
headers.each_pair do |k, vs|
|
||||
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
||||
vs.to_s.split(NEWLINE).each do |v|
|
||||
fast_write client, "#{k}: #{v}\r\n"
|
||||
end
|
||||
else
|
||||
fast_write client, "#{k}: #{v}\r\n"
|
||||
end
|
||||
end
|
||||
|
||||
fast_write client, "\r\n".freeze
|
||||
}
|
||||
end
|
||||
|
||||
# A rack extension. If the app writes #call'ables to this
|
||||
# array, we will invoke them when the request is done.
|
||||
#
|
||||
|
|
|
@ -159,6 +159,61 @@ class TestPumaServer < Minitest::Test
|
|||
assert_equal "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n", data
|
||||
end
|
||||
|
||||
def test_early_hints_works
|
||||
@server.app = proc { |env|
|
||||
env['rack.early_hints'].call("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
|
||||
[200, { "X-Hello" => "World" }, ["Hello world!"]]
|
||||
}
|
||||
|
||||
@server.add_tcp_listener @host, @port
|
||||
@server.early_hints = true
|
||||
@server.run
|
||||
|
||||
sock = TCPSocket.new @host, @server.connected_port
|
||||
sock << "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
data = sock.read
|
||||
|
||||
expected_data = (<<EOF
|
||||
HTTP/1.1 103 Early Hints
|
||||
Link: </style.css>; rel=preload; as=style
|
||||
Link: </script.js>; rel=preload
|
||||
|
||||
HTTP/1.0 200 OK
|
||||
X-Hello: World
|
||||
Content-Length: 12
|
||||
EOF
|
||||
).split("\n").join("\r\n") + "\r\n\r\n"
|
||||
|
||||
assert_equal true, @server.early_hints
|
||||
assert_equal expected_data, data
|
||||
end
|
||||
|
||||
def test_early_hints_is_off_by_default
|
||||
@server.app = proc { |env|
|
||||
assert_nil env['rack.early_hints']
|
||||
[200, { "X-Hello" => "World" }, ["Hello world!"]]
|
||||
}
|
||||
|
||||
@server.add_tcp_listener @host, @port
|
||||
@server.run
|
||||
|
||||
sock = TCPSocket.new @host, @server.connected_port
|
||||
sock << "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
data = sock.read
|
||||
|
||||
expected_data = (<<EOF
|
||||
HTTP/1.0 200 OK
|
||||
X-Hello: World
|
||||
Content-Length: 12
|
||||
EOF
|
||||
).split("\n").join("\r\n") + "\r\n\r\n"
|
||||
|
||||
assert_nil @server.early_hints
|
||||
assert_equal expected_data, data
|
||||
end
|
||||
|
||||
def test_GET_with_no_body_has_sane_chunking
|
||||
@server.app = proc { |env| [200, {}, []] }
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue