Skip to content

Commit

Permalink
Move to HPAX library
Browse files Browse the repository at this point in the history
  • Loading branch information
mtrudel committed Sep 21, 2021
1 parent 93d8536 commit a4361e2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 40 deletions.
26 changes: 10 additions & 16 deletions lib/bandit/http2/connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ defmodule Bandit.HTTP2.Connection do
defstruct local_settings: %Bandit.HTTP2.Settings{},
remote_settings: %Bandit.HTTP2.Settings{},
fragment_frame: nil,
send_hpack_state: HPack.Table.new(4096),
recv_hpack_state: HPack.Table.new(4096),
send_hpack_state: HPAX.new(4096),
recv_hpack_state: HPAX.new(4096),
send_window_size: 65_535,
recv_window_size: 65_535,
streams: %Bandit.HTTP2.StreamCollection{},
Expand Down Expand Up @@ -94,8 +94,7 @@ defmodule Bandit.HTTP2.Connection do
|> StreamCollection.update_initial_send_window_size(frame.settings.initial_window_size)
|> StreamCollection.update_max_concurrent_streams(frame.settings.max_concurrent_streams)

{:ok, send_hpack_state} =
HPack.Table.resize(frame.settings.header_table_size, connection.send_hpack_state)
send_hpack_state = HPAX.resize(connection.send_hpack_state, frame.settings.header_table_size)

do_pending_sends(socket, %{
connection
Expand Down Expand Up @@ -163,19 +162,16 @@ defmodule Bandit.HTTP2.Connection do
def handle_frame(%Frame.Headers{end_headers: true} = frame, socket, connection) do
with block <- frame.fragment,
end_stream <- frame.end_stream,
{:ok, recv_hpack_state, headers} <- HPack.decode(block, connection.recv_hpack_state),
{:hpack, {:ok, headers, recv_hpack_state}} <-
{:hpack, HPAX.decode(block, connection.recv_hpack_state)},
{:ok, stream} <- StreamCollection.get_stream(connection.streams, frame.stream_id),
{:ok, stream} <-
Stream.recv_headers(stream, headers, end_stream, connection.peer, connection.plug),
{:ok, stream} <- Stream.recv_end_of_stream(stream, end_stream),
{:ok, streams} <- StreamCollection.put_stream(connection.streams, stream) do
{:ok, :continue, %{connection | recv_hpack_state: recv_hpack_state, streams: streams}}
else
{:error, :decode_error} ->
handle_error(Constants.compression_error(), "Header decode error", socket, connection)

# https://github.com/OleMchls/elixir-hpack/issues/16
other when is_binary(other) ->
{:hpack, _} ->
handle_error(Constants.compression_error(), "Header decode error", socket, connection)

{:error, {:connection, error_code, error_message}} ->
Expand Down Expand Up @@ -294,7 +290,8 @@ defmodule Bandit.HTTP2.Connection do
#

def send_headers(stream_id, pid, headers, end_stream, socket, connection) do
with {:ok, send_hpack_state, block} <- HPack.encode(headers, connection.send_hpack_state),
with enc_headers <- Enum.map(headers, fn {key, value} -> {:store, key, value} end),
{block, send_hpack_state} <- HPAX.encode(enc_headers, connection.send_hpack_state),
{:ok, stream} <- StreamCollection.get_stream(connection.streams, stream_id),
:ok <- Stream.owner?(stream, pid),
{:ok, stream} <- Stream.send_headers(stream),
Expand All @@ -309,10 +306,6 @@ defmodule Bandit.HTTP2.Connection do

{:ok, %{connection | send_hpack_state: send_hpack_state, streams: streams}}
else
{:error, :encode_error} ->
# Not explcitily documented in RFC7540
handle_error(Constants.compression_error(), "Header encode error", socket, connection)

{:error, {:connection, error_code, error_message}} ->
handle_error(error_code, error_message, socket, connection)

Expand Down Expand Up @@ -408,7 +401,8 @@ defmodule Bandit.HTTP2.Connection do
promised_stream_id <- StreamCollection.next_local_stream_id(connection.streams),
{:ok, stream} <- StreamCollection.get_stream(connection.streams, promised_stream_id),
{:ok, stream} <- Stream.send_push_headers(stream, headers),
{:ok, send_hpack_state, block} <- HPack.encode(headers, connection.send_hpack_state),
enc_headers <- Enum.map(headers, fn {key, value} -> {:store, key, value} end),
{block, send_hpack_state} <- HPAX.encode(enc_headers, connection.send_hpack_state),
:ok <- send_push_promise_frame(stream_id, promised_stream_id, block, socket, connection),
{:ok, stream} <- Stream.start_push(stream, headers, connection.peer, connection.plug),
{:ok, streams} <- StreamCollection.put_stream(connection.streams, stream) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule Bandit.MixProject do
[
{:thousand_island, "~> 0.4.3"},
{:plug, "~> 1.11"},
{:hpack, "~> 3.0.0"},
{:hpax, "~> 0.1.0", github: "mtrudel/hpax", branch: "fix-dynamic-resizing"},
{:finch, "~> 0.7", only: [:dev, :test]},
{:ex_doc, "~> 0.24", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"finch": {:hex, :finch, "0.7.0", "2352962c81fd54952788d66e5eed436a7b734745e0c1cdd5b28a28184fe3e5cd", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4e046b6a4f010898a036244d680ae212331aab2f0a1005bcded3e3e3364f0025"},
"hpack": {:hex, :hpack, "3.0.0", "fccfb69a17a4206cecf28e30caac7d621e89d001aab1e950f7978950afc42b0e", [:mix], [], "hexpm", "71bbbd587d1a1017283969c835d6f89a6cfa0e296cc104c1475f73dcdf3460c6"},
"hpax": {:git, "https://github.com/mtrudel/hpax.git", "792e3a52235b8aa73da41d885a86c8ea4799a336", [branch: "fix-dynamic-resizing"]},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
Expand Down
34 changes: 20 additions & 14 deletions test/bandit/http2/protocol_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -263,20 +263,23 @@ defmodule HTTP2ProtocolTest do
{:ok, 1, _} = SimpleH2Client.recv_window_update(socket)

# We assume that 60k of random data will get hpacked down into somewhere
# between 32768 and 49152 bytes, so we'll need 3 packets total
# between 49152 and 65536 bytes, so we'll need 3 packets total
{:ok, <<16_384::24, 1::8, 0::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, header_fragment} = :ssl.recv(socket, 16_384)

{:ok, <<16_384::24, 9::8, 0::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_1} = :ssl.recv(socket, 16_384)

{:ok, <<16_384::24, 9::8, 0::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_2} = :ssl.recv(socket, 16_384)

{:ok, <<length::24, 9::8, 4::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_2} = :ssl.recv(socket, length)
{:ok, fragment_3} = :ssl.recv(socket, length)

{:ok, _ctx, headers} =
[header_fragment, fragment_1, fragment_2]
{:ok, headers, _ctx} =
[header_fragment, fragment_1, fragment_2, fragment_3]
|> IO.iodata_to_binary()
|> HPack.decode(HPack.Table.new(4096))
|> HPAX.decode(HPAX.new(4096))

assert headers == [
{":status", "200"},
Expand Down Expand Up @@ -376,8 +379,8 @@ defmodule HTTP2ProtocolTest do
{"x-request-header", "Request"}
]

ctx = HPack.Table.new(4096)
{:ok, _, headers} = HPack.encode(headers, ctx)
ctx = HPAX.new(4096)
{headers, _} = headers |> Enum.map(fn {k, v} -> {:store, k, v} end) |> HPAX.encode(ctx)
IO.iodata_to_binary(headers)
end

Expand Down Expand Up @@ -495,7 +498,7 @@ defmodule HTTP2ProtocolTest do
test "returns a stream error if sent headers with uppercase names", context do
socket = SimpleH2Client.setup_connection(context)

# HPack won't encode capitalized headers so take example from H2Spec
# Take example from H2Spec
headers =
<<130, 135, 68, 137, 98, 114, 209, 65, 226, 240, 123, 40, 147, 65, 139, 8, 157, 92, 11,
129, 112, 220, 109, 199, 26, 127, 64, 6, 88, 45, 84, 69, 83, 84, 2, 111, 107>>
Expand Down Expand Up @@ -774,7 +777,7 @@ defmodule HTTP2ProtocolTest do
# Shrink our decoding table size
SimpleH2Client.exchange_client_settings(socket, <<1::16, 1::32>>)

{:ok, ctx} = HPack.Table.resize(1, ctx)
ctx = HPAX.resize(ctx, 1)

SimpleH2Client.send_simple_headers(socket, 3, :get, "/body_response", context.port)
{:ok, 3, false, ^expected_headers, _ctx} = SimpleH2Client.recv_headers(socket, ctx)
Expand Down Expand Up @@ -973,21 +976,24 @@ defmodule HTTP2ProtocolTest do
{:ok, 1, _} = SimpleH2Client.recv_window_update(socket)

# We assume that 60k of random data will get hpacked down into somewhere
# between 32764 and 49148 bytes, so we'll need 3 packets total
# between 49148 and 65532 bytes, so we'll need 4 packets total
# Note that we're reading the promised_stream_id field as part of the frame header
{:ok, <<16_384::24, 5::8, 0::8, 0::1, 1::31, 2::32>>} = :ssl.recv(socket, 13)
{:ok, header_fragment} = :ssl.recv(socket, 16_380)

{:ok, <<16_384::24, 9::8, 0::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_1} = :ssl.recv(socket, 16_384)

{:ok, <<16_384::24, 9::8, 0::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_2} = :ssl.recv(socket, 16_384)

{:ok, <<length::24, 9::8, 4::8, 0::1, 1::31>>} = :ssl.recv(socket, 9)
{:ok, fragment_2} = :ssl.recv(socket, length)
{:ok, fragment_3} = :ssl.recv(socket, length)

{:ok, _ctx, headers} =
[header_fragment, fragment_1, fragment_2]
{:ok, headers, _ctx} =
[header_fragment, fragment_1, fragment_2, fragment_3]
|> IO.iodata_to_binary()
|> HPack.decode(HPack.Table.new(4096))
|> HPAX.decode(HPAX.new(4096))

assert headers == [
{":method", "GET"},
Expand Down
18 changes: 9 additions & 9 deletions test/support/simple_h2_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule SimpleH2Client do
end

def exchange_client_settings(socket, settings \\ <<>>) do
:ssl.send(socket, <<byte_size(settings)::24, 4, 0, 0, 0, 0, 0>>)
:ssl.send(socket, <<IO.iodata_length(settings)::24, 4, 0, 0, 0, 0, 0>>)
:ssl.send(socket, settings)
{:ok, <<0, 0, 0, 4, 1, 0, 0, 0, 0>>} = :ssl.recv(socket, 9)
end
Expand All @@ -53,7 +53,7 @@ defmodule SimpleH2Client do
:ssl.send(socket, <<0, 0, 8, 7, 0, 0, 0, 0, 0, last_stream_id::32, error_code::32>>)
end

def send_simple_headers(socket, stream_id, verb, path, port, ctx \\ HPack.Table.new(4096)) do
def send_simple_headers(socket, stream_id, verb, path, port, ctx \\ HPAX.new(4096)) do
{verb, end_stream} =
case verb do
:get -> {"GET", true}
Expand All @@ -75,8 +75,8 @@ defmodule SimpleH2Client do
)
end

def send_headers(socket, stream_id, end_stream, headers, ctx \\ HPack.Table.new(4096)) do
{:ok, _, headers} = HPack.encode(headers, ctx)
def send_headers(socket, stream_id, end_stream, headers, ctx \\ HPAX.new(4096)) do
{headers, _} = headers |> Enum.map(fn {k, v} -> {:store, k, v} end) |> HPAX.encode(ctx)
flags = if end_stream, do: 0x05, else: 0x04

:ssl.send(socket, [
Expand All @@ -91,14 +91,14 @@ defmodule SimpleH2Client do
:ssl.send(socket, <<0, 0, 5, 2, 0, stream_id::32, dependent_stream_id::32, weight::8>>)
end

def successful_response?(socket, stream_id, end_stream, ctx \\ HPack.Table.new(4096)) do
def successful_response?(socket, stream_id, end_stream, ctx \\ HPAX.new(4096)) do
{:ok, ^stream_id, ^end_stream, [{":status", "200"} | _], _ctx} = recv_headers(socket, ctx)
end

def recv_headers(socket, ctx \\ HPack.Table.new(4096)) do
def recv_headers(socket, ctx \\ HPAX.new(4096)) do
{:ok, <<length::24, 1::8, flags::8, 0::1, stream_id::31>>} = :ssl.recv(socket, 9)
{:ok, header_block} = :ssl.recv(socket, length)
{:ok, ctx, headers} = HPack.decode(header_block, ctx)
{:ok, headers, ctx} = HPAX.decode(header_block, ctx)
{:ok, stream_id, (flags &&& 0x01) == 0x01, headers, ctx}
end

Expand Down Expand Up @@ -127,12 +127,12 @@ defmodule SimpleH2Client do
end
end

def recv_push_promise(socket, ctx \\ HPack.Table.new(4096)) do
def recv_push_promise(socket, ctx \\ HPAX.new(4096)) do
{:ok, <<length::24, 5::8, 0x4::8, 0::1, stream_id::31, 0::1, promised_stream_id::31>>} =
:ssl.recv(socket, 13)

{:ok, header_block} = :ssl.recv(socket, length - 4)
{:ok, ctx, headers} = HPack.decode(header_block, ctx)
{:ok, headers, ctx} = HPAX.decode(header_block, ctx)
{:ok, stream_id, promised_stream_id, headers, ctx}
end

Expand Down

0 comments on commit a4361e2

Please sign in to comment.