summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2022-03-22 16:30:00 +0100
committerCarlos Garcia Campos <cgarcia@igalia.com>2022-03-22 16:35:46 +0100
commit6d22d79dba8ec17df17773e4389b4c94a6f9e2c0 (patch)
tree2e988b0c698c036b1438b7cbc6346cd01439f6aa
parent8e03778377dac2fdef5dcbdefc861e1fe4cf15ab (diff)
downloadlibsoup-6d22d79dba8ec17df17773e4389b4c94a6f9e2c0.tar.gz
http2: fix handling of 100 continue responses
When the request contains the continue expectation we only send the headers and wait for the 100 response to then send the body data.
-rw-r--r--libsoup/http2/soup-client-message-io-http2.c86
1 files changed, 68 insertions, 18 deletions
diff --git a/libsoup/http2/soup-client-message-io-http2.c b/libsoup/http2/soup-client-message-io-http2.c
index c1f12f28..67feb582 100644
--- a/libsoup/http2/soup-client-message-io-http2.c
+++ b/libsoup/http2/soup-client-message-io-http2.c
@@ -119,9 +119,11 @@ typedef struct {
gboolean paused;
guint32 stream_id;
gboolean can_be_restarted;
+ gboolean expect_continue;
} SoupHTTP2MessageData;
static void soup_client_message_io_http2_finished (SoupClientMessageIO *iface, SoupMessage *msg);
+static ssize_t on_data_source_read_callback (nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data);
static void
NGCHECK (int return_code)
@@ -169,6 +171,22 @@ frame_type_to_string (nghttp2_frame_type type)
}
static const char *
+headers_category_to_string (nghttp2_headers_category catergory)
+{
+ switch (catergory) {
+ case NGHTTP2_HCAT_REQUEST:
+ return "REQUEST";
+ case NGHTTP2_HCAT_RESPONSE:
+ return "RESPONSE";
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ return "PUSH_RESPONSE";
+ case NGHTTP2_HCAT_HEADERS:
+ return "HEADERS";
+ }
+ g_assert_not_reached ();
+}
+
+static const char *
state_to_string (SoupHTTP2IOState state)
{
switch (state) {
@@ -599,7 +617,7 @@ on_begin_frame_callback (nghttp2_session *session,
switch (hd->type) {
case NGHTTP2_HEADERS:
- if (data->state < STATE_READ_HEADERS) {
+ if (data->state == STATE_WRITE_DONE) {
soup_message_set_metrics_timestamp (data->item->msg, SOUP_MESSAGE_METRICS_RESPONSE_START);
advance_state_from (data, STATE_WRITE_DONE, STATE_READ_HEADERS);
}
@@ -691,27 +709,53 @@ on_frame_recv_callback (nghttp2_session *session,
}
switch (frame->hd.type) {
- case NGHTTP2_HEADERS:
+ case NGHTTP2_HEADERS: {
+ guint status = soup_message_get_status (data->msg);
+
if (data->metrics)
data->metrics->response_header_bytes_received += frame->hd.length + FRAME_HEADER_SIZE;
- if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE && frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) {
- h2_debug (io, data, "[HEADERS] status %u", soup_message_get_status (data->msg));
- if (SOUP_STATUS_IS_INFORMATIONAL (soup_message_get_status (data->msg))) {
+ h2_debug (io, data, "[HEADERS] category=%s status=%u",
+ headers_category_to_string (frame->headers.cat), status);
+ switch (frame->headers.cat) {
+ case NGHTTP2_HCAT_HEADERS:
+ if (!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)) {
+ io->in_callback--;
+ return 0;
+ }
+ break;
+ case NGHTTP2_HCAT_RESPONSE:
+ if (SOUP_STATUS_IS_INFORMATIONAL (status)) {
+ if (data->expect_continue && status == SOUP_STATUS_CONTINUE) {
+ nghttp2_data_provider data_provider;
+
+ data_provider.source.ptr = soup_message_get_request_body_stream (data->msg);
+ data_provider.read_callback = on_data_source_read_callback;
+ nghttp2_submit_data (io->session, NGHTTP2_FLAG_END_STREAM, frame->hd.stream_id, &data_provider);
+ io_try_write (io, !data->item->async);
+ }
+
soup_message_got_informational (data->msg);
soup_message_cleanup_response (data->msg);
io->in_callback--;
return 0;
}
+ break;
+ case NGHTTP2_HCAT_PUSH_RESPONSE:
+ g_warn_if_reached ();
+ break;
+ default:
+ g_assert_not_reached ();
+ }
- if (soup_message_get_status (data->msg) == SOUP_STATUS_NO_CONTENT ||
- frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
- h2_debug (io, data, "Stream done");
- advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA);
- }
- soup_message_got_headers (data->msg);
+ if (soup_message_get_status (data->msg) == SOUP_STATUS_NO_CONTENT || frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ h2_debug (io, data, "Stream done");
+ advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA);
}
+ soup_message_got_headers (data->msg);
+
break;
+ }
case NGHTTP2_DATA:
if (data->metrics)
data->metrics->response_body_bytes_received += frame->data.hd.length + FRAME_HEADER_SIZE;
@@ -825,7 +869,8 @@ on_frame_send_callback (nghttp2_session *session,
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
g_assert (data);
- h2_debug (io, data, "[SEND] [HEADERS] finished=%d",
+ h2_debug (io, data, "[SEND] [HEADERS] category=%s finished=%d",
+ headers_category_to_string (frame->headers.cat),
(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) ? 1 : 0);
if (data->metrics)
@@ -1266,13 +1311,18 @@ send_message_request (SoupMessage *msg,
nghttp2_priority_spec priority_spec;
nghttp2_priority_spec_init (&priority_spec, 0, message_priority_to_weight (msg), 0);
- nghttp2_data_provider data_provider;
- if (body_stream) {
- data_provider.source.ptr = body_stream;
- data_provider.read_callback = on_data_source_read_callback;
+ int32_t stream_id;
+ if (body_stream && soup_message_headers_get_expectations (soup_message_get_request_headers (msg)) & SOUP_EXPECTATION_CONTINUE) {
+ data->expect_continue = TRUE;
+ stream_id = nghttp2_submit_headers (io->session, 0, -1, &priority_spec, (const nghttp2_nv *)headers->data, headers->len, data);
+ } else {
+ nghttp2_data_provider data_provider;
+ if (body_stream) {
+ data_provider.source.ptr = body_stream;
+ data_provider.read_callback = on_data_source_read_callback;
+ }
+ stream_id = nghttp2_submit_request (io->session, &priority_spec, (const nghttp2_nv *)headers->data, headers->len, body_stream ? &data_provider : NULL, data);
}
-
- int32_t stream_id = nghttp2_submit_request (io->session, &priority_spec, (const nghttp2_nv *)headers->data, headers->len, body_stream ? &data_provider : NULL, data);
if (stream_id == NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) {
set_error_for_data (data,
g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,