Skip to content

Commit af32dd9

Browse files
committed
allow write_chunk also to be routed via write_data
1 parent b3e2af9 commit af32dd9

4 files changed

Lines changed: 180 additions & 27 deletions

File tree

source/h1_encoder.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,11 @@ static int s_scan_outgoing_headers(
152152

153153
static int s_validate_manual_data_writes(const struct aws_h1_encoder_message *encoder_message, bool has_body_stream) {
154154

155-
/* Manual data writes require Content-Length header */
156-
if (encoder_message->content_length == 0) {
157-
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Manual data writes require Content-Length header");
158-
return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_FIELD);
159-
}
160-
161-
/* Manual data writes cannot use chunked encoding */
162-
if (encoder_message->has_chunked_encoding_header) {
163-
AWS_LOGF_ERROR(AWS_LS_HTTP_STREAM, "id=static: Manual data writes cannot use Transfer-Encoding: chunked");
155+
/* Manual data writes require either Content-Length or chunked encoding */
156+
if (encoder_message->content_length == 0 && !encoder_message->has_chunked_encoding_header) {
157+
AWS_LOGF_ERROR(
158+
AWS_LS_HTTP_STREAM,
159+
"id=static: Manual data writes require Content-Length header or Transfer-Encoding: chunked");
164160
return aws_raise_error(AWS_ERROR_HTTP_INVALID_HEADER_FIELD);
165161
}
166162

@@ -277,7 +273,9 @@ int aws_h1_encoder_message_init_from_request(
277273

278274
message->body = aws_input_stream_acquire(aws_http_message_get_body_stream(request));
279275
message->pending_chunk_list = pending_chunk_list;
280-
message->pending_data_write_list = use_manual_data_writes ? pending_data_write_list : NULL;
276+
/* Only set pending_data_write_list for Content-Length manual writes.
277+
* Chunked manual writes go through pending_chunk_list instead (converted in s_stream_write_data). */
278+
message->pending_data_write_list = NULL;
281279

282280
struct aws_byte_cursor method;
283281
int err = aws_http_message_get_request_method(request, &method);
@@ -330,6 +328,11 @@ int aws_h1_encoder_message_init_from_request(
330328
if (err) {
331329
goto error;
332330
}
331+
/* Content-Length manual writes use pending_data_write_list.
332+
* Chunked manual writes go through pending_chunk_list instead. */
333+
if (!message->has_chunked_encoding_header) {
334+
message->pending_data_write_list = pending_data_write_list;
335+
}
333336
}
334337

335338
/* request-line: "{method} {uri} {version}\r\n" */

source/h1_stream.c

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -383,20 +383,12 @@ static int s_stream_write_data(
383383
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
384384
}
385385

386-
struct aws_h1_data_write *data_write = aws_h1_data_write_new(stream_base->alloc, options);
387-
if (!data_write) {
388-
AWS_LOGF_ERROR(
389-
AWS_LS_HTTP_STREAM,
390-
"id=%p: Failed to create data write, error %d (%s).",
391-
(void *)stream_base,
392-
aws_last_error(),
393-
aws_error_name(aws_last_error()));
394-
return AWS_OP_ERR;
395-
}
396-
397386
int error_code = 0;
398387
bool should_schedule_task = false;
399388

389+
/* Check if this stream uses chunked encoding — if so, convert write_data into chunks */
390+
bool is_chunked = false;
391+
400392
{ /* BEGIN CRITICAL SECTION */
401393
s_stream_lock_synced_data(stream);
402394

@@ -425,14 +417,12 @@ static int s_stream_write_data(
425417
goto unlock;
426418
}
427419

420+
is_chunked = stream->synced_data.using_chunked_encoding;
421+
428422
if (options->end_stream) {
429423
stream->synced_data.has_final_data_write = true;
430424
}
431425

432-
aws_linked_list_push_back(&stream->synced_data.pending_data_write_list, &data_write->node);
433-
should_schedule_task = !stream->synced_data.is_cross_thread_work_task_scheduled;
434-
stream->synced_data.is_cross_thread_work_task_scheduled = true;
435-
436426
unlock:
437427
s_stream_unlock_synced_data(stream);
438428
} /* END CRITICAL SECTION */
@@ -444,10 +434,65 @@ static int s_stream_write_data(
444434
(void *)stream_base,
445435
error_code,
446436
aws_error_name(error_code));
447-
aws_h1_data_write_destroy(data_write);
448437
return aws_raise_error(error_code);
449438
}
450439

440+
if (is_chunked) {
441+
/* Convert write_data into chunk(s) for chunked encoding */
442+
int64_t data_len = 0;
443+
if (aws_input_stream_get_length(options->data, &data_len)) {
444+
AWS_LOGF_ERROR(
445+
AWS_LS_HTTP_STREAM,
446+
"id=%p: Failed to get data stream length for chunked conversion",
447+
(void *)stream_base);
448+
return AWS_OP_ERR;
449+
}
450+
451+
struct aws_http1_chunk_options chunk_opts = {
452+
.chunk_data = options->data,
453+
.chunk_data_size = (uint64_t)data_len,
454+
.on_complete = options->on_complete,
455+
.user_data = options->user_data,
456+
};
457+
if (aws_http1_stream_write_chunk(stream_base, &chunk_opts)) {
458+
return AWS_OP_ERR;
459+
}
460+
461+
/* If end_stream, also submit terminating zero-length chunk */
462+
if (options->end_stream) {
463+
struct aws_http1_chunk_options terminator = {
464+
.chunk_data_size = 0,
465+
};
466+
if (aws_http1_stream_write_chunk(stream_base, &terminator)) {
467+
return AWS_OP_ERR;
468+
}
469+
}
470+
471+
return AWS_OP_SUCCESS;
472+
}
473+
474+
/* Content-Length path: create data write and push to pending list */
475+
struct aws_h1_data_write *data_write = aws_h1_data_write_new(stream_base->alloc, options);
476+
if (!data_write) {
477+
AWS_LOGF_ERROR(
478+
AWS_LS_HTTP_STREAM,
479+
"id=%p: Failed to create data write, error %d (%s).",
480+
(void *)stream_base,
481+
aws_last_error(),
482+
aws_error_name(aws_last_error()));
483+
return AWS_OP_ERR;
484+
}
485+
486+
{ /* BEGIN CRITICAL SECTION */
487+
s_stream_lock_synced_data(stream);
488+
489+
aws_linked_list_push_back(&stream->synced_data.pending_data_write_list, &data_write->node);
490+
should_schedule_task = !stream->synced_data.is_cross_thread_work_task_scheduled;
491+
stream->synced_data.is_cross_thread_work_task_scheduled = true;
492+
493+
s_stream_unlock_synced_data(stream);
494+
} /* END CRITICAL SECTION */
495+
451496
AWS_LOGF_TRACE(AWS_LS_HTTP_STREAM, "id=%p: Queued data write", (void *)stream_base);
452497

453498
if (should_schedule_task) {

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ add_test_case(h1_client_write_data_after_final)
165165
add_test_case(h1_client_write_data_exceeds_content_length)
166166
add_test_case(h1_client_write_data_less_than_content_length)
167167
add_test_case(h1_client_unified_write_data_api)
168+
add_test_case(h1_client_write_data_chunked_single)
169+
add_test_case(h1_client_write_data_chunked_multiple)
168170

169171
add_test_case(strutil_trim_http_whitespace)
170172
add_test_case(strutil_is_http_token)

tests/test_h1_client.c

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static int s_tester_init_ex(struct tester *tester, struct aws_allocator *alloc,
104104
tester->alloc = alloc;
105105

106106
struct aws_logger_standard_options logger_options = {
107-
.level = AWS_LOG_LEVEL_TRACE,
107+
.level = AWS_LOG_LEVEL_DEBUG,
108108
.file = stderr,
109109
};
110110
ASSERT_SUCCESS(aws_logger_init_standard(&tester->logger, tester->alloc, &logger_options));
@@ -5607,3 +5607,106 @@ H1_CLIENT_TEST_CASE(h1_client_unified_write_data_api) {
56075607
ASSERT_SUCCESS(s_tester_clean_up(&tester));
56085608
return AWS_OP_SUCCESS;
56095609
}
5610+
5611+
/* Test write_data with chunked encoding - single write */
5612+
H1_CLIENT_TEST_CASE(h1_client_write_data_chunked_single) {
5613+
(void)ctx;
5614+
struct tester tester;
5615+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
5616+
5617+
struct aws_http_message *request = s_new_default_chunked_put_request(allocator);
5618+
5619+
struct client_stream_tester stream_tester;
5620+
ASSERT_SUCCESS(client_stream_tester_init(
5621+
&stream_tester,
5622+
allocator,
5623+
&(struct client_stream_tester_options){
5624+
.request = request,
5625+
.connection = tester.connection,
5626+
.use_manual_data_writes = true,
5627+
}));
5628+
5629+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5630+
aws_http_message_destroy(request);
5631+
5632+
struct aws_byte_cursor data = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("write more tests");
5633+
struct aws_input_stream *input_stream = aws_input_stream_new_from_cursor(allocator, &data);
5634+
5635+
struct aws_http_stream_write_data_options write_options = {
5636+
.data = input_stream,
5637+
.end_stream = true,
5638+
};
5639+
5640+
ASSERT_SUCCESS(aws_http_stream_write_data(stream_tester.stream, &write_options));
5641+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5642+
5643+
const char *expected = "PUT /plan.txt HTTP/1.1\r\n"
5644+
"Transfer-Encoding: chunked\r\n"
5645+
"\r\n"
5646+
"10\r\n"
5647+
"write more tests"
5648+
"\r\n"
5649+
"0\r\n"
5650+
"\r\n";
5651+
ASSERT_SUCCESS(testing_channel_check_written_messages_str(&tester.testing_channel, allocator, expected));
5652+
5653+
aws_input_stream_release(input_stream);
5654+
client_stream_tester_clean_up(&stream_tester);
5655+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
5656+
return AWS_OP_SUCCESS;
5657+
}
5658+
5659+
/* Test write_data with chunked encoding - multiple writes */
5660+
H1_CLIENT_TEST_CASE(h1_client_write_data_chunked_multiple) {
5661+
(void)ctx;
5662+
struct tester tester;
5663+
ASSERT_SUCCESS(s_tester_init(&tester, allocator));
5664+
5665+
struct aws_http_message *request = s_new_default_chunked_put_request(allocator);
5666+
5667+
struct client_stream_tester stream_tester;
5668+
ASSERT_SUCCESS(client_stream_tester_init(
5669+
&stream_tester,
5670+
allocator,
5671+
&(struct client_stream_tester_options){
5672+
.request = request,
5673+
.connection = tester.connection,
5674+
.use_manual_data_writes = true,
5675+
}));
5676+
5677+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5678+
aws_http_message_destroy(request);
5679+
5680+
/* First write */
5681+
struct aws_byte_cursor data1 = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("write ");
5682+
struct aws_input_stream *stream1 = aws_input_stream_new_from_cursor(allocator, &data1);
5683+
struct aws_http_stream_write_data_options opts1 = {.data = stream1, .end_stream = false};
5684+
ASSERT_SUCCESS(aws_http_stream_write_data(stream_tester.stream, &opts1));
5685+
5686+
/* Second write with end_stream */
5687+
struct aws_byte_cursor data2 = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("more tests");
5688+
struct aws_input_stream *stream2 = aws_input_stream_new_from_cursor(allocator, &data2);
5689+
struct aws_http_stream_write_data_options opts2 = {.data = stream2, .end_stream = true};
5690+
ASSERT_SUCCESS(aws_http_stream_write_data(stream_tester.stream, &opts2));
5691+
5692+
testing_channel_drain_queued_tasks(&tester.testing_channel);
5693+
5694+
const char *expected = "PUT /plan.txt HTTP/1.1\r\n"
5695+
"Transfer-Encoding: chunked\r\n"
5696+
"\r\n"
5697+
"6\r\n"
5698+
"write "
5699+
"\r\n"
5700+
"A\r\n"
5701+
"more tests"
5702+
"\r\n"
5703+
"0\r\n"
5704+
"\r\n";
5705+
ASSERT_SUCCESS(testing_channel_check_written_messages_str(&tester.testing_channel, allocator, expected));
5706+
5707+
aws_input_stream_release(stream1);
5708+
aws_input_stream_release(stream2);
5709+
client_stream_tester_clean_up(&stream_tester);
5710+
ASSERT_SUCCESS(s_tester_clean_up(&tester));
5711+
return AWS_OP_SUCCESS;
5712+
}

0 commit comments

Comments
 (0)