Joe DeVivo / @joedevivo
+-----------------------------------------------+ | Length (24) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +---------------------------------------------------------------+
A connection has multiple streams each of which can accept a single request message, and send a single response message, each message consisting of multiple frames in order
What you need to know!
A whole spec just for header compression
Name Value +------------+---------------+ | :path | / | +------------+---------------+ | user-agent | IE6 | +------------+---------------+ | :method | POST | +------------+---------------+ | accept | text/plain | +------------+---------------+ | cookie | ... | +------------+---------------+
hpack:encode(Headers, Context).
+--------------------------+ | | | Encoded Header Block | | | +--------------------------+
+--------+ +--------+ +--------+ |Fragment| |Fragment| |Fragment| | #1 | | #2 | | #3 | | | | | | | +--------+ +--------+ +--------+
+------------+ +------------+ +------------+ | HEADERS | |CONTINUATION| |CONTINUATION| +------------+ +------------+ +------------+ | NONE | | NONE | |END_HEADERS | +------------+ +------------+ +------------+ |Fragment #1 | |Fragment #2 | |Fragment #3 | | | | | | | +------------+ +------------+ +------------+
+------------+ | HEADERS | +------------+ | NONE | +------------+ |Fragment #1 | | | +------------+
+------------+ |CONTINUATION| +------------+ | NONE | +------------+ |Fragment #2 | | | +------------+
+------------+ |CONTINUATION| +------------+ |END_HEADERS | +------------+ |Fragment #3 | | | +------------+
+------------+ +------------+ +------------+ | HEADERS | |CONTINUATION| |CONTINUATION| +------------+ +------------+ +------------+ | NONE | | NONE | |END_HEADERS | +------------+ +------------+ +------------+ |Fragment #1 | |Fragment #2 | |Fragment #3 | | | | | | | +------------+ +------------+ +------------+
+--------+ +--------+ +--------+ |Fragment| |Fragment| |Fragment| | #1 | | #2 | | #3 | | | | | | | +--------+ +--------+ +--------+
+--------------------------+ | | | Encoded Header Block | | | +--------------------------+
hpack:decode(BinaryHeaderBlock, Context).
Name Value +------------+---------------+ | :path | / | +------------+---------------+ | user-agent | IE6 | +------------+---------------+ | :method | POST | +------------+---------------+ | accept | text/plain | +------------+---------------+ | cookie | ... | +------------+---------------+
HTTP is stateless
Stateless protocols are repetitive
Stateless protocols are repetitive
+-------+--------------------+---------------+
| Index | Header Name | Header Value |
+-------+--------------------+---------------+
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| 13 | :status | 404 |
| 14 | :status | 500 |
| 15 | accept-charset | |
| 16 | accept-encoding | gzip, deflate |
...
| 57 | transfer-encoding | |
| 58 | user-agent | |
| 59 | vary | |
| 60 | via | |
| 61 | www-authenticate | |
+-------+--------------------+---------------+
[{1 , <<":authority">> , undefined},
{2 , <<":method">> , <<"GET">>},
{3 , <<":method">> , <<"POST">>},
{4 , <<":path">> , <<"/">>},
{5 , <<":path">> , <<"/index.html">>},
{6 , <<":scheme">> , <<"http">>},
{7 , <<":scheme">> , <<"https">>},
{8 , <<":status">> , <<"200">>},
{13 , <<":status">> , <<"404">>},
{14 , <<":status">> , <<"500">>},
{15 , <<"accept-charset">> , undefined},
{16 , <<"accept-encoding">> , <<"gzip, deflate">>},
...
{57 , <<"transfer-encoding">>, undefined},
{58 , <<"user-agent">> , undefined},
{59 , <<"vary">> , undefined},
{60 , <<"via">> , undefined},
{61 , <<"www-authenticate">> , undefined}]
Add your own!
Indexes 62+
Bounded by size in the HTTP/2 Connection Settings
-type header_name() :: binary(). -type header_value():: binary(). -define(DYNAMIC_TABLE_MIN_INDEX, 62). -record(dynamic_table, { table = [] :: [{pos_integer(), header_name(), header_value()}], max_size = 4096 :: pos_integer(), size = 0 :: non_neg_integer() }). -type dynamic_table() :: #dynamic_table{}.
-spec encode([{binary(), binary()}], encode_context()) -> {binary(), encode_context()}. -spec decode(binary(), decode_context()) -> {headers(), decode_context()}.
Encoding a header that has already been encoded, does not change the context
StaticTable = hpack:new_encode_context(), {HeaderBin, StaticTable} = hpack:encode([{<<":status">>, <<"200">>}], StaticTable). StaticTable = hpack:new_decode_context(), {[{<<":status">>, <<"200">>}], StaticTable} = hpack:decode(HeaderBin, StaticTable).
Encoding something new, changes the dynamic table
StaticTable = hpack:new_encode_context(), {HeaderBin, NewContext} = hpack:encode([{<<":status">>, <<"600">>}], StaticTable), NewContext =/= StaticTable, %% Second time we try and encode this header {HeaderBin, NewContext} = hpack:encode([{<<":status">>, <<"600">>}], NewContext).
Given two peers: X & Y, connected over C
+---------------+ +---------------+
|Peer X (Client)| |Peer Y (Server)|
+-------------------+---------------+ +---------------+-------------------+
| | | |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
| |Plain Req | |Encode (A1)| | Encoded Request | |Decode (A2)| |Plain Req | |
| | Headers |-->| Context |-->| Headers |-->| Context |-->| Headers | |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
| | Cloud | |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
| |Plain Resp| |Decode (B2)| |Encoded Response | |Encode (B1)| |Plain Resp| |
| | Headers |<--| Context |<--| Headers |<--+- Context |<--| Headers | |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
| | | |
| | | |
+-----------------------------------+ +-----------------------------------+
+---------------+ +---------------+
|Peer X (Client)| |Peer Y (Server)|
+-------------------+---------------+ +---------------+-------------------+
| | | |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
| |Plain Req | | | | Encoded Request | | | |Plain Req | |
| |Headers #1|-->| |-->| Headers #1 |-->| |-->|Headers #1| |
| +----------+ | | +--+-----------+--+ | | +----------+ |
| +----------+ | | +--+-----------+--+ | | +----------+ |
| |Plain Req | | | | Encoded Request | | | |Plain Req | |
| |Headers #2|-->| |-->| Headers #2 |-->| |-->|Headers #2| |
| +----------+ |Encode (A1)| +--+-----------+--+ |Decode (A2)| +----------+ |
| +----------+ | Context | +--+-----------+--+ | Context | +----------+ |
| |Plain Req | | | | Encoded Request | | | |Plain Req | |
| |Headers #3|-->| |-->| Headers #3 |-->| |-->|Headers #3| |
| +----------+ | | +--+-----------+--+ | | +----------+ |
| +----------+ | | +--+-----------+--+ | | +----------+ |
| |Plain Req | | | | Encoded Request | | | |Plain Req | |
| |Headers #4|-->| |-->| Headers #4 |-->| |-->|Headers #4| |
| +----------+ +-----------+ +--+-----------+--+ +-----------+ +----------+ |
+-----------------------------------+ +-----------------------------------+
+---------------+ +---------------+
|Peer X (Client)| |Peer Y (Server)|
+---------------+ +---------------+------------------------------+
| | |
---------+ +--+-----------+--+ +-----------+ +----------+ |
| | Encoded Request | | | |Plain Req | |
|-->| Headers #1 |------------->| |-->|Headers #1| |
| +--+-----------+--+ | | +----------+ |
| +--+-----------+--+ | | +----------+ |
| | Encoded Request | | | | Bad Req | |
|-->| Headers #2 |------\ | |-->|Headers #3| |
code (A1)| +--+-----------+--+ ----\----->|Decode (A2)| +----------+ |
Context | +--+-----------+--+ / \ | Context | +----------+ |
| | Encoded Request |-/ \ | | |Bad Req | |
|-->| Headers #3 | --->| |-->|Headers #2| |
| +--+-----------+--+ | | +----------+ |
| +--+-----------+--+ | | +----------+ |
| | Encoded Request | | | |Plain Req | |
|-->| Headers #4 |------------->| |-->|Headers #4| |
---------+ +--+-----------+--+ +-----------+ +----------+ |
| | |
---------+ +--+-----------+--+ +-----------+ +----------+ |
code (B2)| |Encoded Response | |Encode (B1)| |Plain Resp| |
Context |<--| Headers |<-------------| Context |<--| Headers | |
---------+ +--+-----------+--+ +-----------+ +----------+ |
----------------+ +----------------------------------------------+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Index (7+) | +---+---------------------------+
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | 1 1 1 1 1 |
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB |
+---+---------------------------+
...
+---+---------------------------+
| 0 | Value-(2^N-1) MSB |
+---+---------------------------+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306>=128, encode(154), I=1306/128 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10<128, encode(10), done +---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | +---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | +---+---+---+---+---+---+---+---+
encode(Int, N) when Int < (1 bsl N - 1) -> <<Int:N>>; encode(Int, N) -> Prefix = 1 bsl N - 1, Remaining = Int - Prefix, Bin = encode_(Remaining, <<>>), <<Prefix:N, Bin/binary>>.
encode(Int, N) when Int < (1 bsl N - 1) -> <<Int:N>>;
encode(Int, N) -> Prefix = 1 bsl N - 1, %% (2^N)-1 Remaining = Int - Prefix, Bin = encode_(Remaining, <<>>), <<Prefix:N, Bin/binary>>.
-spec encode_(non_neg_integer(), binary()) -> binary(). encode_(I, BinAcc) -> LeastSigSeven = (I rem 128), RestToEncode = I bsr 7, case RestToEncode of 0 -> <<BinAcc/binary, LeastSigSeven>>; _ -> %% Adds the continuation bit ThisByte = 2#10000000 bor LeastSigSeven, encode_(RestToEncode, <<BinAcc/binary, ThisByte>>) end.
EncInt = hpack_integer:encode(1337, 5) -> <<2#11111:5, hpack_integer:encode_(1306, <<>>)>>. hpack_integer:encode_(1306, <<>>) -> hpack_integer:encode_(10, <<2#10011010>>). hpack_integer:encode_(10, <<2#10011010>>) -> <<2#10011010,2#00001010>>. EncInt = <<2#11111:5,2#10011010,2#00001010>>.
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0<128, encode(0), done +---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Index (6+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
code
code as bits as hex len
sym aligned to MSB aligned in
to LSB bits
' ' ( 32) |010100 14 [ 6]
'!' ( 33) |11111110|00 3f8 [10]
'"' ( 34) |11111110|01 3f9 [10]
'#' ( 35) |11111111|1010 ffa [12]
'$' ( 36) |11111111|11001 1ff9 [13]
'0' ( 48) |00000 0 [ 5]
'1' ( 49) |00001 1 [ 5]
'2' ( 50) |00010 2 [ 5]
'r' (114) |101100 2c [ 6]
's' (115) |01000 8 [ 5]
't' (116) |01001 9 [ 5]
(253) |11111111|11111111|11111101|111 7ffffef [27]
(254) |11111111|11111111|11111110|000 7fffff0 [27]
(255) |11111111|11111111|11111011|10 3ffffee [26]
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+
decode(<<>>, HeadersAcc, C) -> {HeadersAcc, C}; %% First bit is '1', so it's an 'Indexed Header Feild' decode(<<2#1:1,_/bits>>=B, HeaderAcc, Context) -> decode_indexed_header(B, HeaderAcc, Context); %% First two bits are '01' so it's a 'Literal Header Field with Incremental Indexing' decode(<<2#01:2,_/bits>>=B, HeaderAcc, Context) -> decode_literal_header_with_indexing(B, HeaderAcc, Context); %% First four bits are '0000' so it's a 'Literal Header Field without Indexing' decode(<<2#0000:4,_/bits>>=B, HeaderAcc, Context) -> decode_literal_header_without_indexing(B, HeaderAcc, Context); %% First four bits are '0001' so it's a 'Literal Header Field never Indexed' decode(<<2#0001:4,_/bits>>=B, HeaderAcc, Context) -> decode_literal_header_never_indexed(B, HeaderAcc, Context); %% First three bits are '001' so it's a 'Dynamic Table Size Update' decode(<<2#001:3,_/bits>>=B, HeaderAcc, Context) -> decode_dynamic_table_size_update(B, HeaderAcc, Context);
Headers1 = [
{<<":path">>, <<"/">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"some custom value">>}
],
HeaderContext1 = hpack:new_encode_context(),
{HeadersBin1, HeaderContext2} = hpack:encode(Headers1, HeaderContext1),
Headers2 = [
{<<":path">>, <<"/some_file.html">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"some custom value">>}
],
{HeadersBin2, HeaderContext3} = hpack:encode(Headers2, HeaderContext2),
Headers3 = [
{<<":path">>, <<"/some_file.html">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"new value">>}
],
{HeadersBin3, _HeaderContext4} = hpack:encode(Headers3, HeaderContext3),
Headers1 = [
{<<":path">>, <<"/">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"some custom value">>}
],
Header: :path: /
Representation: Indexed Header Field
Index: 4
Header: user-agent: my cool browser
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 58
Value: my cool browser
Header: x-custom-header: some custom value
Representation: Literal Header Field with Incremental Indexing - New Name
Name: x-custom-header
Value: some custom value
DynamicTable = [
{62,<<"x-custom-header">>,<<"some custom value">>},
{63,<<"user-agent">>, <<"my cool browser">>}
]
Headers2 = [
{<<":path">>, <<"/some_file.html">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"some custom value">>}
],
Header: :path: /some_file.html
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 4
Value: /some_file.html
Header: user-agent: my cool browser
Representation: Indexed Header Field
Index: 64
Header: x-custom-header: some custom value
Representation: Indexed Header Field
Index: 63
[
{62,<<":path">>, <<"/some_file.html">>},
{63,<<"x-custom-header">>,<<"some custom value">>},
{64,<<"user-agent">>, <<"my cool browser">>}
]
Headers3 = [
{<<":path">>, <<"/some_file.html">>},
{<<"user-agent">>, <<"my cool browser">>},
{<<"x-custom-header">>, <<"new value">>}
],
Header: :path: /some_file.html
Representation: Indexed Header Field
Index: 62
Header: user-agent: my cool browser
Representation: Indexed Header Field
Index: 64
Header: x-custom-header: new value
Representation: Literal Header Field with Incremental Indexing - Indexed Name
Index: 63
Value: new value
[
{62,<<"x-custom-header">>,<<"new value">>},
{63,<<":path">>, <<"/some_file.html">>},
{64,<<"x-custom-header">>,<<"some custom value">>},
{65,<<"user-agent">>, <<"my cool browser">>}
]