Skip to content

Commit 2dd465a

Browse files
committed
refactor(http3): merge hackney_quic into hackney_h3
Collapses the thin quic adapter and the h3 high-level module into a single hackney_h3. Public low-level message tag renamed from {quic, ConnRef, _} to {h3, ConnRef, _}; public low-level API moves from hackney_quic: to hackney_h3:. Updates hackney_conn, tests, xref config, and docs accordingly.
1 parent 3f89906 commit 2dd465a

15 files changed

Lines changed: 432 additions & 481 deletions

DEVELOPMENT.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ rebar3 eunit
5252
Run specific test modules:
5353

5454
```bash
55-
rebar3 eunit --module=hackney_quic_tests
55+
rebar3 eunit --module=hackney_h3_low_level_tests
5656
rebar3 eunit --module=hackney_http3_tests
5757
```
5858

@@ -87,7 +87,7 @@ docker run --rm hackney-test
8787
Run specific test modules:
8888

8989
```bash
90-
docker run --rm hackney-test bash -c "rebar3 eunit --module=hackney_quic_tests"
90+
docker run --rm hackney-test bash -c "rebar3 eunit --module=hackney_h3_low_level_tests"
9191
```
9292

9393
### Interactive debugging in Docker
@@ -108,9 +108,7 @@ HTTP/3 support uses a pure Erlang QUIC implementation from the `quic` dependency
108108

109109
### Source Files
110110

111-
- `src/hackney_quic.erl` - QUIC/HTTP3 transport wrapper
112-
- `src/hackney_qpack.erl` - QPACK header compression
113-
- `src/hackney_h3.erl` - HTTP/3 high-level API
111+
- `src/hackney_h3.erl` - HTTP/3 high-level + low-level adapter over `quic_h3`
114112

115113
The underlying QUIC implementation is in the `quic` dependency which provides:
116114
- TLS 1.3 handshake

NEWS.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ UNRELEASED
88
- HTTP/3 is now delegated to the `erlang_quic` library's `quic_h3` module
99
(branch `feat/http3`). Hackney no longer ships its own HTTP/3 framing,
1010
QPACK codec, control-stream or unidirectional-stream handling:
11-
- `hackney_quic.erl` is now a thin ~270 LOC gen_server adapter that
12-
translates `{quic_h3, Conn, _}` events into the existing
13-
`{quic, ConnRef, _}' message protocol consumed by `hackney_conn`.
14-
- The public `hackney_quic` API now exposes `send_request/3` (atomic
15-
stream-open + HEADERS) instead of the separate `open_stream/1` +
16-
`send_headers/4` pair; `hackney_h3` updated to use it.
11+
- `hackney_quic.erl` has been merged into `hackney_h3.erl`: the single
12+
module now holds both the high-level request API and the gen_server
13+
adapter that translates `{quic_h3, Conn, _}` events.
14+
- The public low-level message tag is now `{h3, ConnRef, _}` (previously
15+
`{quic, ConnRef, _}`). External subscribers to these events must retag
16+
their receives.
17+
- The public low-level API moves from `hackney_quic:` to `hackney_h3:`
18+
(`connect/4`, `send_request/3`, `send_data/4`, `reset_stream/3`,
19+
`close/2`, `process/1`).
1720
- `hackney_qpack.erl` removed (~622 LOC); the QPACK codec lives in
1821
`quic_qpack` in the `quic` dependency.
1922
- H3 peername/sockname/setopts/peercert now return `{error, not_supported}`:

guides/design.md

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ hackney_sup
2121
└── hackney_altsvc (Alt-Svc cache for HTTP/3 discovery)
2222
2323
QUIC connections (HTTP/3):
24-
├── hackney_quic.erl (HTTP/3 wrapper using pure Erlang QUIC)
25-
└── hackney_qpack.erl (QPACK header compression)
24+
└── hackney_h3.erl (HTTP/3 high-level + adapter over quic_h3)
2625
```
2726

2827
## Connection Process (hackney_conn)
@@ -304,13 +303,13 @@ Like TCP connections, QUIC uses an event-driven architecture where the owner pro
304303
│ 2. Receives {select, Resource, Ref, ready_input} │
305304
│ └── Socket has data ready │
306305
│ │
307-
│ 3. Calls hackney_quic:process(ConnRef) │
306+
│ 3. Calls hackney_h3:process(ConnRef) │
308307
│ └── Receives UDP packets │
309308
│ └── Processes QUIC frames │
310309
│ └── Triggers events (headers, data, etc.) │
311310
│ └── Returns next timeout in ms │
312311
│ │
313-
│ 4. Receives {quic, ConnRef, Event} │
312+
│ 4. Receives {h3, ConnRef, Event} │
314313
│ └── {connected, Info} │
315314
│ └── {stream_headers, StreamId, Headers, Fin} │
316315
│ └── {stream_data, StreamId, Data, Fin} │
@@ -325,26 +324,16 @@ Like TCP connections, QUIC uses an event-driven architecture where the owner pro
325324

326325
```
327326
┌─────────────────────────────────────────────────────────────────┐
328-
│ hackney_quic.erl │
329-
│ - connect/4: Start QUIC connection │
330-
│ - process/1: Process pending I/O │
331-
│ - open_stream/1: Create new HTTP/3 stream │
332-
│ - send_headers/4: Send HTTP/3 request headers │
327+
│ hackney_h3.erl │
328+
│ - connect/4: Start QUIC connection via quic_h3 │
329+
│ - send_request/3: Open stream + send HEADERS │
333330
│ - send_data/4: Send request body │
331+
│ - reset_stream/3: Cancel an in-flight stream │
334332
│ - close/2: Close connection │
335333
└─────────────────────────────────────────────────────────────────┘
336334
337335
338336
┌─────────────────────────────────────────────────────────────────┐
339-
│ hackney_qpack.erl │
340-
│ - encode/1: Encode HTTP headers to QPACK format │
341-
│ - decode/1: Decode QPACK-encoded headers │
342-
│ - Static table with 99 predefined headers │
343-
│ - Huffman encoding/decoding │
344-
└─────────────────────────────────────────────────────────────────┘
345-
346-
347-
┌─────────────────────────────────────────────────────────────────┐
348337
│ quic application (dependency) │
349338
│ - QUIC protocol implementation (RFC 9000) │
350339
│ - TLS 1.3 handshake │
@@ -358,7 +347,7 @@ Like TCP connections, QUIC uses an event-driven architecture where the owner pro
358347
```
359348
Owner Process quic library
360349
│ │
361-
hackney_quic:connect(...) │
350+
hackney_h3:connect(...) │
362351
├────────────────────────────────────►│ Create UDP socket
363352
│ │ Generate TLS keys
364353
│ │ Send Initial packet
@@ -367,17 +356,17 @@ Owner Process quic library
367356
│ {select, _, _, ready_input} │
368357
│◄────────────────────────────────────┤ UDP packet received
369358
│ │
370-
hackney_quic:process(ConnRef) │
359+
hackney_h3:process(ConnRef) │
371360
├────────────────────────────────────►│ Process QUIC packets
372361
│ │ Continue handshake
373-
│ {quic, ConnRef, {connected, Info}} │
362+
│ {h3, ConnRef, {connected, Info}} │
374363
│◄────────────────────────────────────┤
375364
│ │
376365
│ ... (request/response cycle) ... │
377366
│ │
378-
hackney_quic:close(ConnRef, ...) │
367+
hackney_h3:close(ConnRef, ...) │
379368
├────────────────────────────────────►│ Send CONNECTION_CLOSE
380-
│ {quic, ConnRef, {closed, normal}} │
369+
│ {h3, ConnRef, {closed, normal}} │
381370
│◄────────────────────────────────────┤
382371
│ │
383372
```

guides/http3_guide.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Hackney supports HTTP/3, the latest version of HTTP built on QUIC (UDP-based tra
1717

1818
## Requirements
1919

20-
HTTP/3 support is provided by the [`erlang_quic`](https://github.com/benoitc/erlang_quic) dependency (module `quic_h3`), which handles the QUIC transport, QPACK header compression, HTTP/3 framing, and control streams. Hackney hosts only a thin adapter (`hackney_quic`) that translates `quic_h3` events into the internal connection protocol. No C dependencies, no external binaries required.
20+
HTTP/3 support is provided by the [`erlang_quic`](https://github.com/benoitc/erlang_quic) dependency (module `quic_h3`), which handles the QUIC transport, QPACK header compression, HTTP/3 framing, and control streams. Hackney hosts only a thin adapter (`hackney_h3`) that translates `quic_h3` events into the internal connection protocol. No C dependencies, no external binaries required.
2121

2222
## Quick Start
2323

@@ -186,24 +186,24 @@ Like HTTP/2, HTTP/3 multiplexes requests as streams on a single QUIC connection:
186186

187187
The high-level `hackney:get/post/...` functions cover the common case. For
188188
servers that send streamed responses, or when you want to drive several
189-
requests concurrently on the same QUIC connection, use the `hackney_quic`
189+
requests concurrently on the same QUIC connection, use the `hackney_h3`
190190
adapter directly.
191191

192192
### Connect
193193

194194
```erlang
195-
{ok, ConnRef} = hackney_quic:connect(<<"cloudflare.com">>, 443, #{}, self()).
195+
{ok, ConnRef} = hackney_h3:connect(<<"cloudflare.com">>, 443, #{}, self()).
196196

197197
receive
198-
{quic, ConnRef, {connected, _Info}} -> ok
198+
{h3, ConnRef, {connected, _Info}} -> ok
199199
after 5000 ->
200200
error(connect_timeout)
201201
end.
202202
```
203203

204-
`hackney_quic:connect/4` registers the calling process as the owner of the
204+
`hackney_h3:connect/4` registers the calling process as the owner of the
205205
connection. All events for the connection arrive as messages of the form
206-
`{quic, ConnRef, Event}`.
206+
`{h3, ConnRef, Event}`.
207207

208208
### Send a request
209209

@@ -218,15 +218,15 @@ Headers = [
218218
{<<":authority">>, <<"cloudflare.com">>},
219219
{<<":path">>, <<"/cdn-cgi/trace">>}
220220
],
221-
{ok, StreamId} = hackney_quic:send_request(ConnRef, Headers, true).
221+
{ok, StreamId} = hackney_h3:send_request(ConnRef, Headers, true).
222222
```
223223

224224
For requests with a body:
225225

226226
```erlang
227-
{ok, StreamId} = hackney_quic:send_request(ConnRef, Headers, false),
228-
ok = hackney_quic:send_data(ConnRef, StreamId, <<"chunk-1">>, false),
229-
ok = hackney_quic:send_data(ConnRef, StreamId, <<"chunk-2">>, true). %% Fin
227+
{ok, StreamId} = hackney_h3:send_request(ConnRef, Headers, false),
228+
ok = hackney_h3:send_data(ConnRef, StreamId, <<"chunk-1">>, false),
229+
ok = hackney_h3:send_data(ConnRef, StreamId, <<"chunk-2">>, true). %% Fin
230230
```
231231

232232
### Receive the response
@@ -237,18 +237,18 @@ the `StreamId`:
237237
```erlang
238238
recv(ConnRef, StreamId, Status, Headers, Body) ->
239239
receive
240-
{quic, ConnRef, {stream_headers, StreamId, RespHeaders, _Fin}} ->
240+
{h3, ConnRef, {stream_headers, StreamId, RespHeaders, _Fin}} ->
241241
{<<":status">>, S} = lists:keyfind(<<":status">>, 1, RespHeaders),
242242
recv(ConnRef, StreamId, binary_to_integer(S),
243243
[H || {K, _} = H <- RespHeaders, K =/= <<":status">>],
244244
Body);
245-
{quic, ConnRef, {stream_data, StreamId, Chunk, true}} ->
245+
{h3, ConnRef, {stream_data, StreamId, Chunk, true}} ->
246246
{ok, Status, Headers, <<Body/binary, Chunk/binary>>};
247-
{quic, ConnRef, {stream_data, StreamId, Chunk, false}} ->
247+
{h3, ConnRef, {stream_data, StreamId, Chunk, false}} ->
248248
recv(ConnRef, StreamId, Status, Headers, <<Body/binary, Chunk/binary>>);
249-
{quic, ConnRef, {stream_reset, StreamId, ErrorCode}} ->
249+
{h3, ConnRef, {stream_reset, StreamId, ErrorCode}} ->
250250
{error, {stream_reset, ErrorCode}};
251-
{quic, ConnRef, {closed, Reason}} ->
251+
{h3, ConnRef, {closed, Reason}} ->
252252
{error, {closed, Reason}}
253253
after 30000 ->
254254
{error, timeout}
@@ -265,9 +265,9 @@ Since each request gets its own `StreamId`, you can have several in flight
265265
on the same QUIC connection and demultiplex on the StreamId in your receive:
266266

267267
```erlang
268-
{ok, S1} = hackney_quic:send_request(ConnRef, headers(<<"/">>), true),
269-
{ok, S2} = hackney_quic:send_request(ConnRef, headers(<<"/cdn-cgi/trace">>), true),
270-
{ok, S3} = hackney_quic:send_request(ConnRef, headers(<<"/robots.txt">>), true),
268+
{ok, S1} = hackney_h3:send_request(ConnRef, headers(<<"/">>), true),
269+
{ok, S2} = hackney_h3:send_request(ConnRef, headers(<<"/cdn-cgi/trace">>), true),
270+
{ok, S3} = hackney_h3:send_request(ConnRef, headers(<<"/robots.txt">>), true),
271271

272272
%% Collect responses as they complete; order is not guaranteed.
273273
collect(ConnRef, sets:from_list([S1, S2, S3]), #{}).
@@ -276,12 +276,12 @@ collect(_ConnRef, Pending, Acc) when map_size(Acc) =:= sets:size(Pending) ->
276276
Acc;
277277
collect(ConnRef, Pending, Acc) ->
278278
receive
279-
{quic, ConnRef, {stream_headers, SId, Hs, _}} ->
279+
{h3, ConnRef, {stream_headers, SId, Hs, _}} ->
280280
collect(ConnRef, Pending, Acc#{SId => {Hs, <<>>}});
281-
{quic, ConnRef, {stream_data, SId, Chunk, true}} ->
281+
{h3, ConnRef, {stream_data, SId, Chunk, true}} ->
282282
#{SId := {Hs, Body}} = Acc,
283283
collect(ConnRef, Pending, Acc#{SId => {Hs, <<Body/binary, Chunk/binary>>}});
284-
{quic, ConnRef, {stream_data, SId, Chunk, false}} ->
284+
{h3, ConnRef, {stream_data, SId, Chunk, false}} ->
285285
#{SId := {Hs, Body}} = Acc,
286286
collect(ConnRef, Pending, Acc#{SId => {Hs, <<Body/binary, Chunk/binary>>}})
287287
end.
@@ -293,13 +293,13 @@ Use `reset_stream/3` to abort a single in-flight request without tearing
293293
down the connection:
294294

295295
```erlang
296-
ok = hackney_quic:reset_stream(ConnRef, StreamId, 16#0102). %% H3_REQUEST_CANCELLED
296+
ok = hackney_h3:reset_stream(ConnRef, StreamId, 16#0102). %% H3_REQUEST_CANCELLED
297297
```
298298

299299
### Close
300300

301301
```erlang
302-
hackney_quic:close(ConnRef, normal).
302+
hackney_h3:close(ConnRef, normal).
303303
```
304304

305305
### Event reference

rebar.config

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@
3131
{quic_h3, send_data, 4},
3232
{quic_h3, cancel, 3},
3333
{quic_h3, close, 1},
34-
%% hackney_quic wrapper functions called by hackney_conn/hackney_h3
35-
{hackney_quic, connect, 4},
36-
{hackney_quic, close, 2},
37-
{hackney_quic, process, 1},
38-
{hackney_quic, send_request, 3},
39-
{hackney_quic, send_data, 4},
4034
%% hackney_h3 functions called by hackney_conn
35+
{hackney_h3, connect, 4},
36+
{hackney_h3, close, 2},
37+
{hackney_h3, process, 1},
38+
{hackney_h3, send_request, 3},
4139
{hackney_h3, send_request, 6},
4240
{hackney_h3, send_request_headers, 5},
41+
{hackney_h3, send_data, 4},
4342
{hackney_h3, send_body_chunk, 4},
4443
{hackney_h3, finish_send_body, 3},
4544
{hackney_h3, parse_response_headers, 1}

0 commit comments

Comments
 (0)