Skip to content

Commit d92be75

Browse files
authored
Expose options (Huffman encoding for now) (#12)
1 parent 6b84f5a commit d92be75

File tree

3 files changed

+75
-24
lines changed

3 files changed

+75
-24
lines changed

lib/hpax.ex

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ defmodule HPAX do
4343
encoding_context = HPAX.new(4096)
4444
4545
"""
46-
@spec new(non_neg_integer()) :: Table.t()
47-
def new(max_table_size) when is_integer(max_table_size) and max_table_size >= 0 do
48-
Table.new(max_table_size)
46+
@spec new(non_neg_integer(), [Table.option()]) :: Table.t()
47+
def new(max_table_size, options \\ [])
48+
when is_integer(max_table_size) and max_table_size >= 0 do
49+
Table.new(max_table_size, options)
4950
end
5051

5152
@doc """
@@ -255,28 +256,32 @@ defmodule HPAX do
255256

256257
defp encode_headers([{action, name, value} | rest], table, acc)
257258
when action in @valid_header_actions and is_binary(name) and is_binary(value) do
259+
huffman? = table.huffman == :always
260+
258261
{encoded, table} =
259262
case Table.lookup_by_header(table, name, value) do
260263
{:full, index} ->
261264
{encode_indexed_header(index), table}
262265

263266
{:name, index} when action == :store ->
264-
{encode_literal_header_with_indexing(index, value), Table.add(table, name, value)}
267+
{encode_literal_header_with_indexing(index, value, huffman?),
268+
Table.add(table, name, value)}
265269

266270
{:name, index} when action in [:store_name, :no_store] ->
267-
{encode_literal_header_without_indexing(index, value), table}
271+
{encode_literal_header_without_indexing(index, value, huffman?), table}
268272

269273
{:name, index} when action == :never_store ->
270-
{encode_literal_header_never_indexed(index, value), table}
274+
{encode_literal_header_never_indexed(index, value, huffman?), table}
271275

272276
:not_found when action in [:store, :store_name] ->
273-
{encode_literal_header_with_indexing(name, value), Table.add(table, name, value)}
277+
{encode_literal_header_with_indexing(name, value, huffman?),
278+
Table.add(table, name, value)}
274279

275280
:not_found when action == :no_store ->
276-
{encode_literal_header_without_indexing(name, value), table}
281+
{encode_literal_header_without_indexing(name, value, huffman?), table}
277282

278283
:not_found when action == :never_store ->
279-
{encode_literal_header_never_indexed(name, value), table}
284+
{encode_literal_header_never_indexed(name, value, huffman?), table}
280285
end
281286

282287
encode_headers(rest, table, [acc, encoded])
@@ -286,27 +291,27 @@ defmodule HPAX do
286291
<<1::1, Types.encode_integer(index, 7)::bitstring>>
287292
end
288293

289-
defp encode_literal_header_with_indexing(index, value) when is_integer(index) do
290-
[<<1::2, Types.encode_integer(index, 6)::bitstring>>, Types.encode_binary(value, false)]
294+
defp encode_literal_header_with_indexing(index, value, huffman) when is_integer(index) do
295+
[<<1::2, Types.encode_integer(index, 6)::bitstring>>, Types.encode_binary(value, huffman)]
291296
end
292297

293-
defp encode_literal_header_with_indexing(name, value) when is_binary(name) do
294-
[<<1::2, 0::6>>, Types.encode_binary(name, false), Types.encode_binary(value, false)]
298+
defp encode_literal_header_with_indexing(name, value, huffman) when is_binary(name) do
299+
[<<1::2, 0::6>>, Types.encode_binary(name, huffman), Types.encode_binary(value, huffman)]
295300
end
296301

297-
defp encode_literal_header_without_indexing(index, value) when is_integer(index) do
298-
[<<0::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, false)]
302+
defp encode_literal_header_without_indexing(index, value, huffman) when is_integer(index) do
303+
[<<0::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, huffman)]
299304
end
300305

301-
defp encode_literal_header_without_indexing(name, value) when is_binary(name) do
302-
[<<0::4, 0::4>>, Types.encode_binary(name, false), Types.encode_binary(value, false)]
306+
defp encode_literal_header_without_indexing(name, value, huffman) when is_binary(name) do
307+
[<<0::4, 0::4>>, Types.encode_binary(name, huffman), Types.encode_binary(value, huffman)]
303308
end
304309

305-
defp encode_literal_header_never_indexed(index, value) when is_integer(index) do
306-
[<<1::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, false)]
310+
defp encode_literal_header_never_indexed(index, value, huffman) when is_integer(index) do
311+
[<<1::4, Types.encode_integer(index, 4)::bitstring>>, Types.encode_binary(value, huffman)]
307312
end
308313

309-
defp encode_literal_header_never_indexed(name, value) when is_binary(name) do
310-
[<<1::4, 0::4>>, Types.encode_binary(name, false), Types.encode_binary(value, false)]
314+
defp encode_literal_header_never_indexed(name, value, huffman) when is_binary(name) do
315+
[<<1::4, 0::4>>, Types.encode_binary(name, huffman), Types.encode_binary(value, huffman)]
311316
end
312317
end

lib/hpax/table.ex

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@ defmodule HPAX.Table do
33

44
defstruct [
55
:max_table_size,
6+
:huffman,
67
entries: [],
78
size: 0,
89
length: 0
910
]
1011

1112
@type t() :: %__MODULE__{
1213
max_table_size: non_neg_integer(),
14+
huffman: huffman_strategy(),
1315
entries: [{binary(), binary()}],
1416
size: non_neg_integer(),
1517
length: non_neg_integer()
1618
}
1719

20+
@type huffman_strategy() :: :always | :never
21+
@type option :: {:huffman, huffman_strategy()}
22+
1823
@static_table [
1924
{":authority", nil},
2025
{":method", "GET"},
@@ -88,9 +93,10 @@ defmodule HPAX.Table do
8893
The maximum size is not the maximum number of entries but rather the maximum size as defined in
8994
http://httpwg.org/specs/rfc7541.html#maximum.table.size.
9095
"""
91-
@spec new(non_neg_integer()) :: t()
92-
def new(max_table_size) do
93-
%__MODULE__{max_table_size: max_table_size}
96+
@spec new(non_neg_integer(), [option()]) :: t()
97+
def new(max_table_size, options \\ []) do
98+
huffman = Keyword.get(options, :huffman, :always)
99+
%__MODULE__{max_table_size: max_table_size, huffman: huffman}
94100
end
95101

96102
@doc """

test/hpax_test.exs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,46 @@ defmodule HPAXTest do
2121
assert headers == [{"custom-key", "custom-header"}]
2222
end
2323

24+
# https://http2.github.io/http2-spec/compression.html#rfc.section.C.3.1
25+
test "encode/2 with a literal example from the spec" do
26+
table = HPAX.new(1000, huffman: :never)
27+
28+
headers = [
29+
{:store, ":method", "GET"},
30+
{:store, ":scheme", "http"},
31+
{:store, ":path", "/"},
32+
{:store, ":authority", "www.example.com"}
33+
]
34+
35+
assert {encoded, %HPAX.Table{}} = HPAX.encode(headers, table)
36+
37+
expected =
38+
<<0x82, 0x86, 0x84, 0x41, 0x0F, 0x77, 0x77, 0x77, 0x2E, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C,
39+
0x65, 0x2E, 0x63, 0x6F, 0x6D>>
40+
41+
assert IO.iodata_to_binary(encoded) == expected
42+
end
43+
44+
# https://http2.github.io/http2-spec/compression.html#rfc.section.C.4.1
45+
test "encode/2 with a Huffman example from the spec" do
46+
table = HPAX.new(1000)
47+
48+
headers = [
49+
{:store, ":method", "GET"},
50+
{:store, ":scheme", "http"},
51+
{:store, ":path", "/"},
52+
{:store, ":authority", "www.example.com"}
53+
]
54+
55+
assert {encoded, %HPAX.Table{}} = HPAX.encode(headers, table)
56+
57+
expected =
58+
<<0x82, 0x86, 0x84, 0x41, 0x8C, 0xF1, 0xE3, 0xC2, 0xE5, 0xF2, 0x3A, 0x6B, 0xA0, 0xAB, 0x90,
59+
0xF4, 0xFF>>
60+
61+
assert IO.iodata_to_binary(encoded) == expected
62+
end
63+
2464
test "manually doing operations on the table that property-based testing would be " <>
2565
"so much better at doing :( we need stateful testing folks" do
2666
enc_table = HPAX.new(1000)

0 commit comments

Comments
 (0)