Skip to content

Commit 291b06c

Browse files
authored
Throw better errors for h2 manual write instead of invalid state error (#430)
1 parent ba87712 commit 291b06c

File tree

5 files changed

+78
-12
lines changed

5 files changed

+78
-12
lines changed

include/aws/http/http.h

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ enum aws_http_errors {
5555
AWS_ERROR_HTTP_STREAM_MANAGER_CONNECTION_ACQUIRE_FAILURE,
5656
AWS_ERROR_HTTP_STREAM_MANAGER_UNEXPECTED_HTTP_VERSION,
5757
AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR,
58+
AWS_ERROR_HTTP_MANUAL_WRITE_NOT_ENABLED,
59+
AWS_ERROR_HTTP_MANUAL_WRITE_HAS_COMPLETED,
5860

5961
AWS_ERROR_HTTP_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_HTTP_PACKAGE_ID)
6062
};

source/h2_stream.c

+17-12
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,14 @@ static int s_stream_write_data(
12541254
struct aws_http_stream *stream_base,
12551255
const struct aws_http2_stream_write_data_options *options) {
12561256
struct aws_h2_stream *stream = AWS_CONTAINER_OF(stream_base, struct aws_h2_stream, base);
1257+
if (!stream->manual_write) {
1258+
AWS_H2_STREAM_LOG(
1259+
ERROR,
1260+
stream,
1261+
"Manual writes are not enabled. You need to enable manual writes using by setting "
1262+
"'http2_use_manual_data_writes' to true in 'aws_http_make_request_options'");
1263+
return aws_raise_error(AWS_ERROR_HTTP_MANUAL_WRITE_NOT_ENABLED);
1264+
}
12571265
struct aws_h2_connection *connection = s_get_h2_connection(stream);
12581266

12591267
/* queue this new write into the pending write list for the stream */
@@ -1272,23 +1280,20 @@ static int s_stream_write_data(
12721280
{
12731281
if (stream->synced_data.api_state != AWS_H2_STREAM_API_STATE_ACTIVE) {
12741282
s_unlock_synced_data(stream);
1275-
s_stream_data_write_destroy(stream, pending_write, AWS_ERROR_INVALID_STATE);
1276-
AWS_LOGF_ERROR(
1277-
AWS_LS_HTTP_STREAM,
1278-
"Cannot write DATA frames to an inactive or closed stream, stream=%p",
1279-
(void *)stream_base);
1280-
return aws_raise_error(AWS_ERROR_INVALID_STATE);
1283+
int error_code = stream->synced_data.api_state == AWS_H2_STREAM_API_STATE_INIT
1284+
? AWS_ERROR_HTTP_STREAM_NOT_ACTIVATED
1285+
: AWS_ERROR_HTTP_STREAM_HAS_COMPLETED;
1286+
s_stream_data_write_destroy(stream, pending_write, error_code);
1287+
AWS_H2_STREAM_LOG(ERROR, stream, "Cannot write DATA frames to an inactive or closed stream");
1288+
return aws_raise_error(error_code);
12811289
}
12821290

12831291
if (stream->synced_data.manual_write_ended) {
12841292
s_unlock_synced_data(stream);
1285-
s_stream_data_write_destroy(stream, pending_write, AWS_ERROR_INVALID_STATE);
1286-
AWS_LOGF_ERROR(
1287-
AWS_LS_HTTP_STREAM,
1288-
"Cannot write DATA frames to a stream after end, stream=%p",
1289-
(void *)stream_base);
1293+
s_stream_data_write_destroy(stream, pending_write, AWS_ERROR_HTTP_MANUAL_WRITE_HAS_COMPLETED);
1294+
AWS_H2_STREAM_LOG(ERROR, stream, "Cannot write DATA frames to a stream after manual write ended");
12901295
/* Fail with error, otherwise, people can wait for on_complete callback that will never be invoked. */
1291-
return aws_raise_error(AWS_ERROR_INVALID_STATE);
1296+
return aws_raise_error(AWS_ERROR_HTTP_MANUAL_WRITE_HAS_COMPLETED);
12921297
}
12931298
/* Not setting this until we're sure we succeeded, so that callback doesn't fire on cleanup if we fail */
12941299
if (options->end_stream) {

source/http.c

+6
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ static struct aws_error_info s_errors[] = {
142142
AWS_DEFINE_ERROR_INFO_HTTP(
143143
AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR,
144144
"Websocket protocol rules violated by peer"),
145+
AWS_DEFINE_ERROR_INFO_HTTP(
146+
AWS_ERROR_HTTP_MANUAL_WRITE_NOT_ENABLED,
147+
"Manual write failed because manual writes are not enabled."),
148+
AWS_DEFINE_ERROR_INFO_HTTP(
149+
AWS_ERROR_HTTP_MANUAL_WRITE_HAS_COMPLETED,
150+
"Manual write failed because manual writes are already completed."),
145151
};
146152
/* clang-format on */
147153

tests/CMakeLists.txt

+1
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_not_enabled)
485486
add_test_case(h2_client_manual_data_write_with_body)
486487
add_test_case(h2_client_manual_data_write_no_data)
487488
add_test_case(h2_client_manual_data_write_connection_close)

tests/test_h2_client.c

+52
Original file line numberDiff line numberDiff line change
@@ -5336,6 +5336,58 @@ TEST_CASE(h2_client_manual_data_write) {
53365336
return s_tester_clean_up();
53375337
}
53385338

5339+
TEST_CASE(h2_client_manual_data_write_not_enabled) {
5340+
5341+
ASSERT_SUCCESS(s_tester_init(allocator, ctx));
5342+
5343+
struct aws_http_message *request = aws_http2_message_new_request(allocator);
5344+
ASSERT_NOT_NULL(request);
5345+
5346+
struct aws_http_header request_headers_src[] = {
5347+
DEFINE_HEADER(":method", "GET"),
5348+
DEFINE_HEADER(":scheme", "https"),
5349+
DEFINE_HEADER(":path", "/"),
5350+
};
5351+
aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src));
5352+
struct aws_http_make_request_options request_options = {
5353+
.self_size = sizeof(request_options),
5354+
.request = request,
5355+
.http2_use_manual_data_writes = false,
5356+
};
5357+
struct aws_http_stream *stream = aws_http_connection_make_request(s_tester.connection, &request_options);
5358+
ASSERT_NOT_NULL(stream);
5359+
5360+
aws_http_stream_activate(stream);
5361+
5362+
struct aws_byte_buf payload;
5363+
aws_byte_buf_init(&payload, allocator, 1024);
5364+
5365+
struct h2_client_manual_data_write_ctx test_ctx = {
5366+
.allocator = allocator,
5367+
.data = payload,
5368+
};
5369+
5370+
/* Try writing the data */
5371+
struct aws_input_stream *data_stream = s_h2_client_manual_data_write_generate_data(&test_ctx);
5372+
int64_t stream_length = 0;
5373+
ASSERT_SUCCESS(aws_input_stream_get_length(data_stream, &stream_length));
5374+
struct aws_http2_stream_write_data_options write_options = {
5375+
.data = data_stream,
5376+
};
5377+
ASSERT_ERROR(AWS_ERROR_HTTP_MANUAL_WRITE_NOT_ENABLED, aws_http2_stream_write_data(stream, &write_options));
5378+
aws_input_stream_release(data_stream);
5379+
aws_http_message_release(request);
5380+
aws_http_stream_release(stream);
5381+
5382+
/* close the connection */
5383+
aws_http_connection_close(s_tester.connection);
5384+
5385+
aws_byte_buf_clean_up(&test_ctx.data);
5386+
5387+
/* clean up */
5388+
return s_tester_clean_up();
5389+
}
5390+
53395391
TEST_CASE(h2_client_manual_data_write_with_body) {
53405392

53415393
ASSERT_SUCCESS(s_tester_init(allocator, ctx));

0 commit comments

Comments
 (0)