Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/mint/http2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ defmodule Mint.HTTP2 do
@default_max_header_list_size 256 * 1024

@valid_client_settings [
:header_table_size,
:max_concurrent_streams,
:initial_window_size,
:max_frame_size,
Expand Down Expand Up @@ -240,6 +241,7 @@ defmodule Mint.HTTP2 do

# Settings that the client communicates to the server.
client_settings: %{
header_table_size: 4096,
max_concurrent_streams: 100,
initial_window_size: @default_stream_window_size,
max_header_list_size: @default_max_header_list_size,
Expand Down Expand Up @@ -2296,13 +2298,18 @@ defmodule Mint.HTTP2 do
end
end

valid_client_settings_without_iws = @valid_client_settings -- [:initial_window_size]
valid_client_settings_without_iws =
@valid_client_settings -- [:initial_window_size, :header_table_size]

defp apply_client_settings(conn, client_settings) do
Enum.reduce(client_settings, conn, fn
{:initial_window_size, initial_window_size}, conn ->
update_client_initial_window_size(conn, initial_window_size)

{:header_table_size, header_table_size}, conn ->
conn = update_in(conn.decode_table, &HPAX.protocol_resize(&1, header_table_size))
put_in(conn.client_settings.header_table_size, header_table_size)

{setting, value}, conn when setting in unquote(valid_client_settings_without_iws) ->
update_in(conn.client_settings, &%{&1 | setting => value})

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Mint.MixProject do
defp deps do
[
{:castore, "~> 0.1.0 or ~> 1.0", optional: true},
{:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0"},
{:hpax, "~> 1.1"},

# Dev/test dependencies
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"},
"ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"},
"excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"hpax": {:hex, :hpax, "1.1.0", "782931867cc23217c68fb5f68fe1a11f5e7544c7fda82c8a7019a5df5a4a1cdf", [:mix], [], "hexpm", "0b8d0f05832f55571d65ac720f79bf8994138ffbb133209dc4685eae0ad456a8"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
Expand Down
160 changes: 160 additions & 0 deletions test/mint/http2/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2430,6 +2430,146 @@ defmodule Mint.HTTP2Test do
end

describe "settings" do
test "the header table size setting is applied when the server acknowledges it",
%{conn: conn} do
{conn, ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

{:ok, conn} = HTTP2.put_settings(conn, header_table_size: 8192)
assert_recv_frames [settings(params: [header_table_size: 8192])]

assert {:ok, %HTTP2{} = conn, []} =
stream_frames(conn, [settings(flags: set_flags(:settings, [:ack]), params: [])])

assert HTTP2.get_client_setting(conn, :header_table_size) == 8192

# The server can now signal a dynamic table size of 8192 at the start of a block.
server = Process.get(@server_pdict_key)
Process.put(@server_pdict_key, update_in(server.encode_table, &HPAX.resize(&1, 8192)))

assert {:ok, %HTTP2{}, [{:status, ^ref, 200}, {:headers, ^ref, []}, {:done, ^ref}]} =
stream_frames(conn, [
{:headers, stream_id, [{":status", "200"}], [:end_headers, :end_stream]}
])
end

test "a raised header table size leaves the table size to the server", %{conn: conn} do
{conn, ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

# The server picks a 64 byte table and stores ":status: 404" in it, which takes 42 bytes.
assert {:ok, %HTTP2{} = conn, responses} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0x3F, 0x21, 0x48, 3, "404">>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert [{:status, ^ref, 404}, {:headers, ^ref, []}, {:done, ^ref}] = responses

{:ok, conn} = HTTP2.put_settings(conn, header_table_size: 8192)
assert_recv_frames [settings(params: [header_table_size: 8192])]

assert {:ok, %HTTP2{} = conn, []} =
stream_frames(conn, [settings(flags: set_flags(:settings, [:ack]), params: [])])

# The server didn't ask for more room, so storing ":status: 500" evicts ":status: 404".
{conn, ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

assert {:ok, %HTTP2{} = conn, responses} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0x48, 3, "500">>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert [{:status, ^ref, 500}, {:headers, ^ref, []}, {:done, ^ref}] = responses

{conn, _ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

assert {:error, %HTTP2{} = conn, error, []} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0xBF>>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert_http2_error error, {:compression_error, debug_data}
assert debug_data =~ "unable to decode headers: {:index_not_found, 63}"

assert_recv_frames [goaway(error_code: :compression_error)]

refute HTTP2.open?(conn)
end

test "a lowered header table size has to be signalled by the server", %{conn: conn} do
conn = fill_server_header_table(conn)

{:ok, conn} = HTTP2.put_settings(conn, header_table_size: 0)
assert_recv_frames [settings(params: [header_table_size: 0])]

assert {:ok, %HTTP2{} = conn, []} =
stream_frames(conn, [settings(flags: set_flags(:settings, [:ack]), params: [])])

{conn, _ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

assert {:error, %HTTP2{} = conn, error, []} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0x88>>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert_http2_error error, {:compression_error, debug_data}
assert debug_data =~ "unable to decode headers: :missing_size_update"

assert_recv_frames [goaway(error_code: :compression_error)]

refute HTTP2.open?(conn)
end

test "a response that signals the lowered header table size is accepted", %{conn: conn} do
conn = fill_server_header_table(conn)

{:ok, conn} = HTTP2.put_settings(conn, header_table_size: 0)
assert_recv_frames [settings(params: [header_table_size: 0])]

assert {:ok, %HTTP2{} = conn, []} =
stream_frames(conn, [settings(flags: set_flags(:settings, [:ack]), params: [])])

{conn, ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

assert {:ok, %HTTP2{} = conn, responses} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0x20, 0x88>>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert [{:status, ^ref, 200}, {:headers, ^ref, []}, {:done, ^ref}] = responses
assert HTTP2.open?(conn)
end

@tag connect_options: [
receive_window_update_threshold: 8,
client_settings: [initial_window_size: 16]
Expand Down Expand Up @@ -3210,6 +3350,26 @@ defmodule Mint.HTTP2Test do
end
end

defp fill_server_header_table(conn) do
{conn, ref} = open_request(conn)

assert_recv_frames [headers(stream_id: stream_id)]

# ":status: 404" stored with incremental indexing, 42 bytes in the server's table.
assert {:ok, %HTTP2{} = conn, responses} =
stream_frames(conn, [
headers(
stream_id: stream_id,
hbf: <<0x48, 3, "404">>,
flags: set_flags(:headers, [:end_headers, :end_stream])
)
])

assert [{:status, ^ref, 404}, {:headers, ^ref, []}, {:done, ^ref}] = responses

conn
end

defp start_server_async(_context) do
{:ok, port, server_socket_task} = TestServer.listen_and_accept()
%{server_port: port, server_socket_task: server_socket_task}
Expand Down
Loading