1
0
Fork 0
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:
Eileen M. Uchitelle 2017-10-04 08:30:16 -04:00 committed by Nate Berkopec
parent eb70beb1a5
commit 01699742a8
6 changed files with 88 additions and 1 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.
#

View file

@ -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, {}, []] }