提交 bd2542b7 编写于 作者: E Eileen M. Uchitelle 提交者: GitHub

Merge pull request #30744 from eileencodes/early-hints

Implement H2 Early Hints for Rails
* Add ability to enable Early Hints for HTTP/2
If supported by the server, and enabled in Puma this allows H2 Early Hints to be used.
The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested.
*Eileen M. Uchitelle*, *Aaron Patterson*
* Simplify cookies middleware with key rotation support
Use the `rotate` method for both `MessageEncryptor` and
......
......@@ -199,6 +199,23 @@ def headers
@headers ||= Http::Headers.new(self)
end
# Early Hints is an HTTP/2 status code that indicates hints to help a client start
# making preparations for processing the final response.
#
# If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
#
# The +send_early_hints+ method accepts an hash of links as follows:
#
# send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
#
# If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
# Early Hints headers are included by default if supported.
def send_early_hints(links)
return unless env["rack.early_hints"]
env["rack.early_hints"].call(links)
end
# Returns a +String+ with the last requested path including their params.
#
# # get '/foo'
......
......@@ -1304,3 +1304,18 @@ class RequestFormData < BaseRequestTest
assert !request.form_data?
end
end
class EarlyHintsRequestTest < BaseRequestTest
def setup
super
@env["rack.early_hints"] = lambda { |links| links }
@request = stub_request
end
test "when early hints is set in the env link headers are sent" do
early_hints = @request.send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
expected_hints = { "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload" }
assert_equal expected_hints, early_hints
end
end
......@@ -37,6 +37,9 @@ module AssetTagHelper
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
# source, and include other JavaScript or CoffeeScript files inside the manifest.
#
# If the server supports Early Hints header links for these assets will be
# automatically pushed.
#
# ==== Options
#
# When the last parameter is a hash you can add HTML attributes using that
......@@ -77,12 +80,20 @@ module AssetTagHelper
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
early_hints_links = []
sources_tags = sources.uniq.map { |source|
href = path_to_javascript(source, path_options)
early_hints_links << "<#{href}>; rel=preload; as=script"
tag_options = {
"src" => path_to_javascript(source, path_options)
"src" => href
}.merge!(options)
content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
request.send_early_hints("Link" => early_hints_links.join("\n"))
sources_tags
end
# Returns a stylesheet link tag for the sources specified as arguments. If
......@@ -92,6 +103,9 @@ def javascript_include_tag(*sources)
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
# apply to all media types.
#
# If the server supports Early Hints header links for these assets will be
# automatically pushed.
#
# stylesheet_link_tag "style"
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
#
......@@ -113,14 +127,22 @@ def javascript_include_tag(*sources)
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
early_hints_links = []
sources_tags = sources.uniq.map { |source|
href = path_to_stylesheet(source, path_options)
early_hints_links << "<#{href}>; rel=preload; as=stylesheet"
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
"href" => path_to_stylesheet(source, path_options)
"href" => href
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
request.send_early_hints("Link" => early_hints_links.join("\n"))
sources_tags
end
# Returns a link tag that browsers and feed readers can use to auto-detect
......
......@@ -19,6 +19,7 @@ def protocol() "http://" end
def ssl?() false end
def host_with_port() "localhost" end
def base_url() "http://www.example.com" end
def send_early_hints(links) end
end.new
@controller.request = @request
......@@ -653,7 +654,9 @@ def setup
@controller = BasicController.new
@controller.config.relative_url_root = "/collaboration/hieraki"
@request = Struct.new(:protocol, :base_url).new("gopher://", "gopher://www.example.com")
@request = Struct.new(:protocol, :base_url) do
def send_early_hints(links); end
end.new("gopher://", "gopher://www.example.com")
@controller.request = @request
end
......
......@@ -6,11 +6,15 @@ class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
attr_accessor :output_buffer
attr_reader :request
setup do
@old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json
ActiveSupport.escape_html_entities_in_json = true
@template = self
@request = Class.new do
def send_early_hints(links) end
end.new
end
def teardown
......
......@@ -127,6 +127,7 @@ class ServerCommand < Base # :nodoc:
class_option "dev-caching", aliases: "-C", type: :boolean, default: nil,
desc: "Specifies whether to perform caching in development."
class_option "restart", type: :boolean, default: nil, hide: true
class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints."
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
......@@ -161,7 +162,8 @@ def server_options
daemonize: options[:daemon],
pid: pid,
caching: options["dev-caching"],
restart_cmd: restart_command
restart_cmd: restart_command,
early_hints: early_hints
}
end
end
......@@ -227,6 +229,10 @@ def restart_command
"bin/rails server #{@server} #{@original_options.join(" ")} --restart"
end
def early_hints
options[:early_hints]
end
def pid
File.expand_path(options[:pid])
end
......
......@@ -81,6 +81,18 @@ def test_caching_with_option
assert_equal false, options[:caching]
end
def test_early_hints_with_option
args = ["--early-hints"]
options = parse_arguments(args)
assert_equal true, options[:early_hints]
end
def test_early_hints_is_nil_by_default
args = []
options = parse_arguments(args)
assert_nil options[:early_hints]
end
def test_log_stdout
with_rack_env nil do
with_rails_env nil do
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册