Skip to content

Commit 4bbda11

Browse files
authored
Fix http2 manual write (#419)
1 parent 62a03c5 commit 4bbda11

File tree

6 files changed

+123
-11
lines changed

6 files changed

+123
-11
lines changed

include/aws/http/connection_manager.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ struct aws_http_connection_manager_options {
8484
* Optional.
8585
* HTTP/2 specific configuration. Check `struct aws_http2_connection_options` for details of each config
8686
*/
87-
struct aws_http2_setting *initial_settings_array;
87+
const struct aws_http2_setting *initial_settings_array;
8888
size_t num_initial_settings;
8989
size_t max_closed_streams;
9090
bool http2_conn_manual_window_management;

include/aws/http/http2_stream_manager.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ struct aws_http2_stream_manager_options {
7979
* - For stream level window control, `enable_read_back_pressure` will enable manual control. The initial window
8080
* size needs to be set through `initial_settings_array`.
8181
*/
82-
struct aws_http2_setting *initial_settings_array;
82+
const struct aws_http2_setting *initial_settings_array;
8383
size_t num_initial_settings;
8484
size_t max_closed_streams;
8585
bool conn_manual_window_management;
@@ -187,7 +187,7 @@ struct aws_http2_stream_manager *aws_http2_stream_manager_release(struct aws_htt
187187
AWS_HTTP_API
188188
struct aws_http2_stream_manager *aws_http2_stream_manager_new(
189189
struct aws_allocator *allocator,
190-
struct aws_http2_stream_manager_options *options);
190+
const struct aws_http2_stream_manager_options *options);
191191

192192
/**
193193
* Acquire a stream from stream manager asynchronously.

source/h2_stream.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,6 @@ struct aws_h2_stream *aws_h2_stream_new_request(
244244
stream->base.on_destroy = options->on_destroy;
245245
stream->base.client_data = &stream->base.client_or_server_data.client;
246246
stream->base.client_data->response_status = AWS_HTTP_STATUS_CODE_UNKNOWN;
247-
struct aws_byte_cursor method;
248-
AWS_ZERO_STRUCT(method);
249-
if (aws_http_message_get_request_method(options->request, &method)) {
250-
goto error;
251-
}
252-
stream->base.request_method = aws_http_str_to_method(method);
253247
aws_linked_list_init(&stream->thread_data.outgoing_writes);
254248
aws_linked_list_init(&stream->synced_data.pending_write_list);
255249

@@ -276,6 +270,12 @@ struct aws_h2_stream *aws_h2_stream_new_request(
276270
aws_raise_error(AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL);
277271
goto error;
278272
}
273+
struct aws_byte_cursor method;
274+
AWS_ZERO_STRUCT(method);
275+
if (aws_http_message_get_request_method(options->request, &method)) {
276+
goto error;
277+
}
278+
stream->base.request_method = aws_http_str_to_method(method);
279279

280280
/* Init H2 specific stuff */
281281
stream->thread_data.state = AWS_H2_STREAM_STATE_IDLE;
@@ -289,7 +289,7 @@ struct aws_h2_stream *aws_h2_stream_new_request(
289289
struct aws_h2_stream_data_write *body_write =
290290
aws_mem_calloc(stream->base.alloc, 1, sizeof(struct aws_h2_stream_data_write));
291291
body_write->data_stream = aws_input_stream_acquire(body_stream);
292-
body_write->end_stream = true;
292+
body_write->end_stream = !stream->manual_write;
293293
aws_linked_list_push_back(&stream->thread_data.outgoing_writes, &body_write->node);
294294
}
295295

source/http2_stream_manager.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@ static void s_make_request_task(struct aws_channel_task *task, void *arg, enum a
871871
.on_complete = s_on_stream_complete,
872872
.on_destroy = s_on_stream_destroy,
873873
.user_data = pending_stream_acquisition,
874+
.http2_use_manual_data_writes = pending_stream_acquisition->options.http2_use_manual_data_writes,
874875
};
875876
/* TODO: we could put the pending acquisition back to the list if the connection is not available for new request.
876877
*/
@@ -1051,7 +1052,7 @@ void s_stream_manager_on_zero_external_ref(struct aws_http2_stream_manager *stre
10511052

10521053
struct aws_http2_stream_manager *aws_http2_stream_manager_new(
10531054
struct aws_allocator *allocator,
1054-
struct aws_http2_stream_manager_options *options) {
1055+
const struct aws_http2_stream_manager_options *options) {
10551056

10561057
AWS_PRECONDITION(allocator);
10571058
/* The other options are validated by the aws_http_connection_manager_new */

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ add_test_case(h2_client_error_from_incoming_headers_callback_reset_stream)
482482
add_test_case(h2_client_error_from_incoming_headers_done_callback_reset_stream)
483483
add_test_case(h2_client_error_from_incoming_body_callback_reset_stream)
484484
add_test_case(h2_client_manual_data_write)
485+
add_test_case(h2_client_manual_data_write_with_body)
485486
add_test_case(h2_client_manual_data_write_no_data)
486487
add_test_case(h2_client_manual_data_write_connection_close)
487488

tests/test_h2_client.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5336,6 +5336,116 @@ TEST_CASE(h2_client_manual_data_write) {
53365336
return s_tester_clean_up();
53375337
}
53385338

5339+
TEST_CASE(h2_client_manual_data_write_with_body) {
5340+
5341+
ASSERT_SUCCESS(s_tester_init(allocator, ctx));
5342+
/* get connection preface and acks out of the way */
5343+
ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer));
5344+
ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer));
5345+
size_t frame_count = h2_decode_tester_frame_count(&s_tester.peer.decode);
5346+
5347+
struct aws_http_message *request = aws_http2_message_new_request(allocator);
5348+
ASSERT_NOT_NULL(request);
5349+
5350+
struct aws_http_header request_headers_src[] = {
5351+
DEFINE_HEADER(":method", "GET"),
5352+
DEFINE_HEADER(":scheme", "https"),
5353+
DEFINE_HEADER(":path", "/"),
5354+
};
5355+
aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src));
5356+
struct aws_http_make_request_options request_options = {
5357+
.self_size = sizeof(request_options),
5358+
.request = request,
5359+
.http2_use_manual_data_writes = true,
5360+
};
5361+
size_t total_length = 0;
5362+
5363+
/* set request body */
5364+
const char *body_src = "hello";
5365+
struct aws_byte_cursor body_cursor = aws_byte_cursor_from_c_str(body_src);
5366+
struct aws_input_stream *request_body = aws_input_stream_new_from_cursor(allocator, &body_cursor);
5367+
aws_http_message_set_body_stream(request, request_body);
5368+
int64_t body_length = 0;
5369+
ASSERT_SUCCESS(aws_input_stream_get_length(request_body, &body_length));
5370+
total_length += (size_t)body_length;
5371+
aws_input_stream_release(request_body);
5372+
5373+
struct aws_http_stream *stream = aws_http_connection_make_request(s_tester.connection, &request_options);
5374+
ASSERT_NOT_NULL(stream);
5375+
5376+
aws_http_stream_activate(stream);
5377+
testing_channel_drain_queued_tasks(&s_tester.testing_channel);
5378+
uint32_t stream_id = aws_http_stream_get_id(stream);
5379+
5380+
struct aws_byte_buf payload;
5381+
aws_byte_buf_init(&payload, allocator, 1024);
5382+
5383+
struct h2_client_manual_data_write_ctx test_ctx = {
5384+
.allocator = allocator,
5385+
.data = payload,
5386+
};
5387+
5388+
/* Simulate writes coming in over time */
5389+
for (int idx = 0; idx < 1000; ++idx) {
5390+
struct aws_input_stream *data_stream = s_h2_client_manual_data_write_generate_data(&test_ctx);
5391+
int64_t stream_length = 0;
5392+
ASSERT_SUCCESS(aws_input_stream_get_length(data_stream, &stream_length));
5393+
total_length += (size_t)stream_length;
5394+
struct aws_http2_stream_write_data_options write = {
5395+
.data = data_stream,
5396+
.on_complete = NULL,
5397+
.user_data = NULL,
5398+
};
5399+
ASSERT_SUCCESS(aws_http2_stream_write_data(stream, &write));
5400+
/* fake peer sends WINDOW_UPDATE */
5401+
struct aws_h2_frame *peer_frame = aws_h2_frame_new_window_update(allocator, stream_id, (uint32_t)stream_length);
5402+
ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame));
5403+
/* Connection level window update */
5404+
peer_frame = aws_h2_frame_new_window_update(allocator, 0, (uint32_t)stream_length);
5405+
ASSERT_SUCCESS(h2_fake_peer_send_frame(&s_tester.peer, peer_frame));
5406+
if (idx % 10 == 0) {
5407+
testing_channel_drain_queued_tasks(&s_tester.testing_channel);
5408+
ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer));
5409+
}
5410+
aws_input_stream_release(data_stream);
5411+
}
5412+
struct aws_http2_stream_write_data_options last_write = {.end_stream = true};
5413+
5414+
ASSERT_SUCCESS(aws_http2_stream_write_data(stream, &last_write));
5415+
5416+
testing_channel_drain_queued_tasks(&s_tester.testing_channel);
5417+
ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer));
5418+
size_t frame_count2 = h2_decode_tester_frame_count(&s_tester.peer.decode);
5419+
/* Peer should received header frame without end_stream and mutiple data frames and combined payload length should
5420+
* be the same as total length sent. */
5421+
struct h2_decoded_frame *header_frame = h2_decode_tester_get_frame(&s_tester.peer.decode, frame_count);
5422+
ASSERT_UINT_EQUALS(AWS_H2_FRAME_T_HEADERS, header_frame->type);
5423+
ASSERT_FALSE(header_frame->end_stream);
5424+
size_t received_length = 0;
5425+
for (size_t i = frame_count + 1; i < frame_count2; i++) {
5426+
struct h2_decoded_frame *data_frame = h2_decode_tester_get_frame(&s_tester.peer.decode, i);
5427+
ASSERT_UINT_EQUALS(AWS_H2_FRAME_T_DATA, data_frame->type);
5428+
received_length += data_frame->data_payload_len;
5429+
if (i == frame_count2 - 1) {
5430+
ASSERT_TRUE(data_frame->end_stream);
5431+
} else {
5432+
ASSERT_FALSE(data_frame->end_stream);
5433+
}
5434+
}
5435+
ASSERT_UINT_EQUALS(received_length, total_length);
5436+
5437+
aws_http_message_release(request);
5438+
aws_http_stream_release(stream);
5439+
5440+
/* close the connection */
5441+
aws_http_connection_close(s_tester.connection);
5442+
5443+
aws_byte_buf_clean_up(&test_ctx.data);
5444+
5445+
/* clean up */
5446+
return s_tester_clean_up();
5447+
}
5448+
53395449
TEST_CASE(h2_client_manual_data_write_no_data) {
53405450

53415451
ASSERT_SUCCESS(s_tester_init(allocator, ctx));

0 commit comments

Comments
 (0)