Skip to content

Commit e78cf64

Browse files
authored
Improve multiple incoming header set handling (#640)
Queue incoming header sets with STAILQ so informational responses and final responses are delivered in order. Add an on_hset_in callback for applications that want to collect each decoded header set as it arrives, including both IETF QUIC and gQUIC paths. Keep DATA read behavior unchanged when header sets are pending, document retry behavior for overlapping header sends, and update http_client to consume header sets through the new callback. Also recognize HTTP/1.x informational reason phrases, including 100 Continue and 103 Early Hints, and extend tests for FIFO delivery and informational responses.
1 parent 3fbf9a8 commit e78cf64

11 files changed

Lines changed: 230 additions & 74 deletions

File tree

bin/http_client.c

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -765,11 +765,44 @@ maybe_perform_priority_actions (struct lsquic_stream *stream,
765765
}
766766

767767

768+
static void
769+
http_client_process_hset (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
770+
{
771+
struct hset *hset;
772+
773+
hset = lsquic_stream_get_hset(stream);
774+
if (!hset)
775+
{
776+
LSQ_ERROR("could not get header set from stream");
777+
exit(2);
778+
}
779+
if (!(st_h->sh_flags & PROCESSED_HEADERS))
780+
{
781+
/* Internal helper (not in lsquic.h); example-only timing. */
782+
st_h->sh_ttfb = lsquic_time_now();
783+
update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created);
784+
st_h->sh_flags |= PROCESSED_HEADERS;
785+
}
786+
if (s_discard_response)
787+
LSQ_DEBUG("discard response: do not dump headers");
788+
else
789+
hset_dump(hset, stdout);
790+
hset_destroy(hset);
791+
}
792+
793+
794+
static void
795+
http_client_on_hset_in (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
796+
{
797+
if (g_header_bypass)
798+
http_client_process_hset(stream, st_h);
799+
}
800+
801+
768802
static void
769803
http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
770804
{
771805
struct http_client_ctx *const client_ctx = st_h->client_ctx;
772-
struct hset *hset;
773806
ssize_t nread;
774807
unsigned old_prio, new_prio;
775808
unsigned char buf[0x200];
@@ -781,23 +814,7 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
781814
do
782815
{
783816
if (g_header_bypass && !(st_h->sh_flags & PROCESSED_HEADERS))
784-
{
785-
hset = lsquic_stream_get_hset(stream);
786-
if (!hset)
787-
{
788-
LSQ_ERROR("could not get header set from stream");
789-
exit(2);
790-
}
791-
/* Internal helper (not in lsquic.h); example-only timing. */
792-
st_h->sh_ttfb = lsquic_time_now();
793-
update_sample_stats(&s_stat_ttfb, st_h->sh_ttfb - st_h->sh_created);
794-
if (s_discard_response)
795-
LSQ_DEBUG("discard response: do not dump headers");
796-
else
797-
hset_dump(hset, stdout);
798-
hset_destroy(hset);
799-
st_h->sh_flags |= PROCESSED_HEADERS;
800-
}
817+
http_client_process_hset(stream, st_h);
801818
else if (nread = (s_discard_response
802819
? lsquic_stream_readf(stream, discard, st_h)
803820
: lsquic_stream_read(stream, buf,
@@ -955,6 +972,7 @@ static struct lsquic_stream_if http_client_if = {
955972
.on_write = http_client_on_write,
956973
.on_close = http_client_on_close,
957974
.on_hsk_done = http_client_on_hsk_done,
975+
.on_hset_in = http_client_on_hset_in,
958976
};
959977

960978

docs/apiref.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,21 @@ the engine to communicate with the user code:
13961396

13971397
This callback is optional.
13981398

1399+
.. member:: void (*on_hset_in) (lsquic_stream_t *s, lsquic_stream_ctx_t *h)
1400+
1401+
This callback is called when a decoded HTTP header set becomes
1402+
available on a stream. It is only used when the optional header set
1403+
interface (:member:`lsquic_engine_api.ea_hsi_if`) is specified.
1404+
1405+
The application can call :func:`lsquic_stream_get_hset()` from this
1406+
callback to take ownership of the header set. This is useful for
1407+
HTTP/3 responses that contain more than one header set, such as one or
1408+
more informational responses followed by the final response.
1409+
1410+
This callback is optional. If it is not specified, header sets can
1411+
still be collected during the next :member:`lsquic_stream_if.on_read`
1412+
callback.
1413+
13991414
.. member:: ssize_t (*on_dg_write)(lsquic_conn_t *c, void *buf, size_t buf_sz)
14001415

14011416
Called when datagram is ready to be written. Write at most
@@ -1819,6 +1834,20 @@ more information.
18191834

18201835
0 on success or -1 on error.
18211836

1837+
LSQUIC does not maintain a user-visible queue of header sets for this
1838+
function. A stream can have at most one encoded header block pending in
1839+
the stream object. If a previous call to this function produced a header
1840+
block that could not be written out completely, a subsequent call made
1841+
before that pending block is flushed fails with ``-1`` and sets ``errno``
1842+
to ``EAGAIN``.
1843+
1844+
This is intentional: queuing multiple outbound header sets internally
1845+
would create a second buffering layer with unclear ownership and resource
1846+
limits. Applications that send more than one header set on a stream, for
1847+
example an informational response followed by the final response, should
1848+
serialize those calls and retry after LSQUIC has flushed the previous
1849+
header block.
1850+
18221851
Receiving HTTP Headers
18231852
----------------------
18241853

@@ -1904,6 +1933,10 @@ fields yourself. In that case, the header set must be "read" from the stream vi
19041933
``hsi_create_header_set()`` callback. After this call, the ownership of
19051934
the header set is transferred to the caller.
19061935

1936+
When :member:`lsquic_stream_if.on_hset_in` is specified, the application
1937+
can call this function from that callback. Otherwise, call this function
1938+
from :member:`lsquic_stream_if.on_read` before reading stream data.
1939+
19071940
This call must precede calls to :func:`lsquic_stream_read()`,
19081941
:func:`lsquic_stream_readv()`, and :func:`lsquic_stream_readf()`.
19091942

docs/tutorial.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,10 @@ this tutorial, it is good to know that they are available.
333333
- Callbacks to control memory allocation for outgoing packets. These are useful when sending packets using a custom library. For example, when all packets must be in contiguous memory.
334334
- Callbacks to observe connection ID lifecycle. These are useful in multi-process applications.
335335
- Callbacks that provide access to a shared-memory hash. This is also used in multi-process applications.
336-
- HTTP header set processing. These callbacks may be used in HTTP mode for HTTP/3 and Google QUIC.
336+
- HTTP header set processing. These callbacks may be used in HTTP mode for
337+
HTTP/3 and Google QUIC. The optional ``on_hset_in`` stream callback
338+
notifies the application when a decoded header set is ready to be collected
339+
using :func:`lsquic_stream_get_hset()`.
337340

338341
Please refer to :ref:`apiref-engine-settings` for details.
339342

@@ -365,6 +368,7 @@ The optional callbacks are used to observe some events in the connection lifecyc
365368
void (*on_hsk_done)(lsquic_conn_t *c, enum lsquic_hsk_status s);
366369
void (*on_new_token)(lsquic_conn_t *c, const unsigned char *token,
367370
void (*on_sess_resume_info)(lsquic_conn_t *c, const unsigned char *, size_t);
371+
void (*on_hset_in) (lsquic_stream_t *s, lsquic_stream_ctx_t *h);
368372
};
369373

370374
On new connection

include/lsquic.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ struct lsquic_stream_if {
236236
void (*on_conncloseframe_received)(lsquic_conn_t *c,
237237
int app_error, uint64_t error_code,
238238
const char *reason, int reason_len);
239+
/**
240+
* Optional callback is called when a new header set becomes available.
241+
* The user may pick it off immediately using lsquic_stream_get_hset().
242+
*/
243+
void (*on_hset_in) (lsquic_stream_t *s, lsquic_stream_ctx_t *h);
239244
};
240245

241246
struct ssl_ctx_st;

src/liblsquic/lsquic_full_conn.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4108,7 +4108,6 @@ synthesize_push_request (struct full_conn *conn, void *hset,
41084108
if (lsquic_http1x_if == conn->fc_enpub->enp_hsi_if)
41094109
uh->uh_flags |= UH_H1H;
41104110
uh->uh_hset = hset;
4111-
uh->uh_next = NULL;
41124111

41134112
return uh;
41144113
}

src/liblsquic/lsquic_full_conn_ietf.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4166,7 +4166,6 @@ ietf_full_conn_ci_push_stream (struct lsquic_conn *lconn, void *hset,
41664166
uh->uh_exclusive = 0;
41674167
uh->uh_flags = UH_FIN;
41684168
uh->uh_hset = hset;
4169-
uh->uh_next = NULL;
41704169

41714170
memset(promise, 0, sizeof(*promise));
41724171
promise->pp_refcnt = 1; /* This function itself keeps a reference */
@@ -10099,4 +10098,3 @@ static const struct lsquic_stream_if unicla_if =
1009910098
static const struct lsquic_stream_if *unicla_if_ptr = &unicla_if;
1010010099

1010110100
typedef char dcid_elem_fits_in_128_bytes[sizeof(struct dcid_elem) <= 128 ? 1 : - 1];
10102-

src/liblsquic/lsquic_headers.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#ifndef LSQUIC_HEADERS_H
33
#define LSQUIC_HEADERS_H 1
44

5+
#include <sys/queue.h>
6+
57
/* When ea_hsi_if is not specified, the headers are converted to a C string
68
* that contains HTTP/1.x-like header structure.
79
*/
@@ -36,8 +38,8 @@ struct uncompressed_headers
3638
UH_H1H = (1 << 2), /* uh_hset points to http1x_headers */
3739
} uh_flags:8;
3840
void *uh_hset;
39-
struct uncompressed_headers
40-
*uh_next;
41+
STAILQ_ENTRY(uncompressed_headers)
42+
uh_next;
4143
};
4244

4345
#endif

src/liblsquic/lsquic_http1x_if.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ code_str_to_reason (const char code_str[HTTP_CODE_LEN])
192192
#define HTTP_REASON_CODE(code, reason) [code - 100] = reason
193193
HTTP_REASON_CODE(100, "Continue"),
194194
HTTP_REASON_CODE(101, "Switching Protocols"),
195+
HTTP_REASON_CODE(103, "Early Hints"),
195196
HTTP_REASON_CODE(200, "OK"),
196197
HTTP_REASON_CODE(201, "Created"),
197198
HTTP_REASON_CODE(202, "Accepted"),
@@ -240,7 +241,7 @@ code_str_to_reason (const char code_str[HTTP_CODE_LEN])
240241
memcpy(code_buf, code_str, HTTP_CODE_LEN);
241242
code_buf[HTTP_CODE_LEN] = '\0';
242243
code = strtol(code_buf, NULL, 10) - 100;
243-
if (code > 0 && code < (long) (sizeof(http_reason_phrases) /
244+
if (code >= 0 && code < (long) (sizeof(http_reason_phrases) /
244245
sizeof(http_reason_phrases[0])))
245246
return http_reason_phrases[code];
246247
else

0 commit comments

Comments
 (0)