From 0426a065b77e99ce8dbbe2e89f6e9fae35840728 Mon Sep 17 00:00:00 2001 From: Dengke Tang <815825145@qq.com> Date: Wed, 3 Nov 2021 15:21:34 -0700 Subject: [PATCH] HTTP/2 message stream (#344) Use HTTP/2 message instead of always translating HTTP/1 message to HTTP/2 --- bin/elasticurl/main.c | 20 +- include/aws/http/private/h2_connection.h | 6 - include/aws/http/private/h2_stream.h | 6 + .../aws/http/private/request_response_impl.h | 16 ++ source/h2_connection.c | 104 ---------- source/h2_stream.c | 42 ++-- source/request_response.c | 105 +++++++++- tests/CMakeLists.txt | 2 +- tests/test_h2_client.c | 191 +++++++++--------- tests/test_h2_decoder.c | 2 +- tests/test_message.c | 3 + 11 files changed, 272 insertions(+), 225 deletions(-) diff --git a/bin/elasticurl/main.c b/bin/elasticurl/main.c index 4760702a2..115252493 100644 --- a/bin/elasticurl/main.c +++ b/bin/elasticurl/main.c @@ -390,7 +390,10 @@ static void s_on_stream_complete_fn(struct aws_http_stream *stream, int error_co } static struct aws_http_message *s_build_http_request(struct elasticurl_ctx *app_ctx) { - struct aws_http_message *request = aws_http_message_new_request(app_ctx->allocator); + + struct aws_http_message *request = app_ctx->required_http_version == AWS_HTTP_VERSION_2 + ? aws_http2_message_new_request(app_ctx->allocator) + : aws_http_message_new_request(app_ctx->allocator); if (request == NULL) { fprintf(stderr, "failed to allocate request\n"); exit(1); @@ -398,15 +401,22 @@ static struct aws_http_message *s_build_http_request(struct elasticurl_ctx *app_ aws_http_message_set_request_method(request, aws_byte_cursor_from_c_str(app_ctx->verb)); aws_http_message_set_request_path(request, app_ctx->uri.path_and_query); + if (app_ctx->required_http_version == AWS_HTTP_VERSION_2) { + struct aws_http_headers *h2_headers = aws_http_message_get_headers(request); + aws_http2_headers_set_request_scheme(h2_headers, app_ctx->uri.scheme); + aws_http2_headers_set_request_authority(h2_headers, app_ctx->uri.host_name); + } else { + struct aws_http_header host_header = { + .name = aws_byte_cursor_from_c_str("host"), + .value = app_ctx->uri.host_name, + }; + aws_http_message_add_header(request, host_header); + } struct aws_http_header accept_header = { .name = aws_byte_cursor_from_c_str("accept"), .value = aws_byte_cursor_from_c_str("*/*"), }; aws_http_message_add_header(request, accept_header); - - struct aws_http_header host_header = {.name = aws_byte_cursor_from_c_str("host"), .value = app_ctx->uri.host_name}; - aws_http_message_add_header(request, host_header); - struct aws_http_header user_agent_header = { .name = aws_byte_cursor_from_c_str("user-agent"), .value = aws_byte_cursor_from_c_str("elasticurl 1.0, Powered by the AWS Common Runtime.")}; diff --git a/include/aws/http/private/h2_connection.h b/include/aws/http/private/h2_connection.h index f0e85618b..9acb4ccbb 100644 --- a/include/aws/http/private/h2_connection.h +++ b/include/aws/http/private/h2_connection.h @@ -221,12 +221,6 @@ struct aws_http_connection *aws_http_connection_new_http2_client( bool manual_window_management, const struct aws_http2_connection_options *http2_options); -/* Transform the request to h2 style headers */ -AWS_HTTP_API -struct aws_http_headers *aws_h2_create_headers_from_request( - struct aws_http_message *request, - struct aws_allocator *alloc); - AWS_EXTERN_C_END /* Private functions called from multiple .c files... */ diff --git a/include/aws/http/private/h2_stream.h b/include/aws/http/private/h2_stream.h index ad2483ddb..f52f98624 100644 --- a/include/aws/http/private/h2_stream.h +++ b/include/aws/http/private/h2_stream.h @@ -94,6 +94,12 @@ struct aws_h2_stream { /* Store the received reset HTTP/2 error code, set to -1, if none has received so far */ int64_t received_reset_error_code; + + /** + * Back up the message if we create a new message from it to keep the underlying input stream alive. + * TODO: remove this once we have input stream refcounted + */ + struct aws_http_message *backup_outgoing_message; }; const char *aws_h2_stream_state_to_str(enum aws_h2_stream_state state); diff --git a/include/aws/http/private/request_response_impl.h b/include/aws/http/private/request_response_impl.h index d84687fcb..a63aa9407 100644 --- a/include/aws/http/private/request_response_impl.h +++ b/include/aws/http/private/request_response_impl.h @@ -62,4 +62,20 @@ struct aws_http_stream { struct aws_http_stream_server_data *server_data; }; +AWS_EXTERN_C_BEGIN + +/** + * Create an HTTP/2 message from HTTP/1.1 message. + * pseudo headers will be created from the context and added to the headers of new message. + * Normal headers will be copied to the headers of new message. + * TODO: (Maybe more, connection-specific header will be removed, etc...) + * TODO: REFCOUNT INPUT_STREAMS!!! And make it public. + */ +AWS_HTTP_API +struct aws_http_message *aws_http2_message_new_from_http1( + struct aws_http_message *http1_msg, + struct aws_allocator *alloc); + +AWS_EXTERN_C_END + #endif /* AWS_HTTP_REQUEST_RESPONSE_IMPL_H */ diff --git a/source/h2_connection.c b/source/h2_connection.c index b8a8cca46..8872425c3 100644 --- a/source/h2_connection.c +++ b/source/h2_connection.c @@ -1721,110 +1721,6 @@ static void s_stream_complete(struct aws_h2_connection *connection, struct aws_h aws_http_stream_release(&stream->base); } -struct aws_http_headers *aws_h2_create_headers_from_request( - struct aws_http_message *request, - struct aws_allocator *alloc) { - - struct aws_http_headers *old_headers = aws_http_message_get_headers(request); - bool is_pseudoheader = false; - struct aws_http_headers *result = aws_http_headers_new(alloc); - struct aws_http_header header_iter; - struct aws_byte_buf lower_name_buf; - AWS_ZERO_STRUCT(lower_name_buf); - - /* Check whether the old_headers have pseudo header or not */ - if (aws_http_headers_count(old_headers)) { - if (aws_http_headers_get_index(old_headers, 0, &header_iter)) { - goto error; - } - is_pseudoheader = header_iter.name.ptr[0] == ':'; - } - if (!is_pseudoheader) { - /* TODO: Set pseudo headers all from message, which will lead an API change to aws_http_message */ - /* No pseudoheader detected, we set them from the request */ - /* Set pseudo headers */ - struct aws_byte_cursor method; - if (aws_http_message_get_request_method(request, &method)) { - /* error will happen when the request is invalid */ - aws_raise_error(AWS_ERROR_HTTP_INVALID_METHOD); - goto error; - } - if (aws_http_headers_add(result, aws_http_header_method, method)) { - goto error; - } - /* we set a default value, "https", for now */ - struct aws_byte_cursor scheme_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("https"); - if (aws_http_headers_add(result, aws_http_header_scheme, scheme_cursor)) { - goto error; - } - /* Set an empty authority for now, if host header field is found, we set it as the value of host */ - struct aws_byte_cursor authority_cursor; - struct aws_byte_cursor host_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("host"); - if (!aws_http_headers_get(old_headers, host_cursor, &authority_cursor)) { - if (aws_http_headers_add(result, aws_http_header_authority, authority_cursor)) { - goto error; - } - } - struct aws_byte_cursor path_cursor; - if (aws_http_message_get_request_path(request, &path_cursor)) { - aws_raise_error(AWS_ERROR_HTTP_INVALID_PATH); - goto error; - } - if (aws_http_headers_add(result, aws_http_header_path, path_cursor)) { - goto error; - } - } - /* if pseudoheader is included in message, we just convert all the headers from old_headers to result */ - if (aws_byte_buf_init(&lower_name_buf, alloc, 256)) { - goto error; - } - for (size_t iter = 0; iter < aws_http_headers_count(old_headers); iter++) { - /* name should be converted to lower case */ - if (aws_http_headers_get_index(old_headers, iter, &header_iter)) { - goto error; - } - /* append lower case name to the buffer */ - aws_byte_buf_append_with_lookup(&lower_name_buf, &header_iter.name, aws_lookup_table_to_lower_get()); - struct aws_byte_cursor lower_name_cursor = aws_byte_cursor_from_buf(&lower_name_buf); - enum aws_http_header_name name_enum = aws_http_lowercase_str_to_header_name(lower_name_cursor); - switch (name_enum) { - case AWS_HTTP_HEADER_COOKIE: - /* split cookie if USE CACHE */ - if (header_iter.compression == AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) { - struct aws_byte_cursor cookie_chunk; - AWS_ZERO_STRUCT(cookie_chunk); - while (aws_byte_cursor_next_split(&header_iter.value, ';', &cookie_chunk)) { - if (aws_http_headers_add( - result, lower_name_cursor, aws_strutil_trim_http_whitespace(cookie_chunk))) { - goto error; - } - } - } else { - if (aws_http_headers_add(result, lower_name_cursor, header_iter.value)) { - goto error; - } - } - break; - case AWS_HTTP_HEADER_HOST: - /* host header has been converted to :authority, do nothing here */ - break; - /* TODO: handle connection-specific header field (RFC7540 8.1.2.2) */ - default: - if (aws_http_headers_add(result, lower_name_cursor, header_iter.value)) { - goto error; - } - break; - } - aws_byte_buf_reset(&lower_name_buf, false); - } - aws_byte_buf_clean_up(&lower_name_buf); - return result; -error: - aws_http_headers_release(result); - aws_byte_buf_clean_up(&lower_name_buf); - return NULL; -} - int aws_h2_connection_on_stream_closed( struct aws_h2_connection *connection, struct aws_h2_stream *stream, diff --git a/source/h2_stream.c b/source/h2_stream.c index e325320c5..8d557d148 100644 --- a/source/h2_stream.c +++ b/source/h2_stream.c @@ -247,7 +247,27 @@ struct aws_h2_stream *aws_h2_stream_new_request( /* Init H2 specific stuff */ stream->thread_data.state = AWS_H2_STREAM_STATE_IDLE; - stream->thread_data.outgoing_message = options->request; + enum aws_http_version message_version = aws_http_message_get_protocol_version(options->request); + switch (message_version) { + case AWS_HTTP_VERSION_1_1: + stream->thread_data.outgoing_message = + aws_http2_message_new_from_http1(options->request, stream->base.alloc); + if (!stream->thread_data.outgoing_message) { + AWS_H2_STREAM_LOG(ERROR, stream, "Stream failed to create the HTTP/2 message from HTTP/1.1 message"); + goto error; + } + stream->backup_outgoing_message = options->request; + aws_http_message_acquire(stream->backup_outgoing_message); + break; + case AWS_HTTP_VERSION_2: + stream->thread_data.outgoing_message = options->request; + aws_http_message_acquire(stream->thread_data.outgoing_message); + break; + default: + /* Not supported */ + aws_raise_error(AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL); + goto error; + } stream->sent_reset_error_code = -1; stream->received_reset_error_code = -1; @@ -257,13 +277,14 @@ struct aws_h2_stream *aws_h2_stream_new_request( if (aws_mutex_init(&stream->synced_data.lock)) { AWS_H2_STREAM_LOGF( ERROR, stream, "Mutex init error %d (%s).", aws_last_error(), aws_error_name(aws_last_error())); - aws_mem_release(stream->base.alloc, stream); - return NULL; + goto error; } - aws_http_message_acquire(stream->thread_data.outgoing_message); aws_channel_task_init( &stream->cross_thread_work_task, s_stream_cross_thread_work_task, stream, "HTTP/2 stream cross-thread work"); return stream; +error: + s_stream_destroy(&stream->base); + return NULL; } static void s_stream_cross_thread_work_task(struct aws_channel_task *task, void *arg, enum aws_task_status status) { @@ -335,6 +356,7 @@ static void s_stream_destroy(struct aws_http_stream *stream_base) { AWS_H2_STREAM_LOG(DEBUG, stream, "Destroying stream"); aws_mutex_clean_up(&stream->synced_data.lock); aws_http_message_release(stream->thread_data.outgoing_message); + aws_http_message_release(stream->backup_outgoing_message); aws_mem_release(stream->base.alloc, stream); } @@ -548,13 +570,11 @@ int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgo /* Create HEADERS frame */ struct aws_http_message *msg = stream->thread_data.outgoing_message; + /* Should be ensured when the stream is created */ + AWS_ASSERT(aws_http_message_get_protocol_version(msg) == AWS_HTTP_VERSION_2); bool has_body_stream = aws_http_message_get_body_stream(msg) != NULL; - struct aws_http_headers *h2_headers = aws_h2_create_headers_from_request(msg, stream->base.alloc); - if (!h2_headers) { - AWS_H2_STREAM_LOGF( - ERROR, stream, "Failed to create HTTP/2 style headers from request %s", aws_error_name(aws_last_error())); - goto error; - } + struct aws_http_headers *h2_headers = aws_http_message_get_headers(msg); + struct aws_h2_frame *headers_frame = aws_h2_frame_new_headers( stream->base.alloc, stream->base.id, @@ -563,8 +583,6 @@ int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgo 0 /* padding - not currently configurable via public API */, NULL /* priority - not currently configurable via public API */); - /* Release refcount of h2_headers here, let frame take the full ownership of it */ - aws_http_headers_release(h2_headers); if (!headers_frame) { AWS_H2_STREAM_LOGF(ERROR, stream, "Failed to create HEADERS frame: %s", aws_error_name(aws_last_error())); goto error; diff --git a/source/request_response.c b/source/request_response.c index 227811d81..e7f18fb05 100644 --- a/source/request_response.c +++ b/source/request_response.c @@ -141,8 +141,8 @@ int aws_http_headers_add_header(struct aws_http_headers *headers, const struct a bool front = false; if (pseudo && aws_http_headers_count(headers)) { struct aws_http_header last_header; - /* TODO: instead if checking the last header, maybe we can add the pesudo headers to the end of the existing - * pesudo headers, which needs to insert to the middle of the array list. */ + /* TODO: instead if checking the last header, maybe we can add the pseudo headers to the end of the existing + * pseudo headers, which needs to insert to the middle of the array list. */ AWS_ZERO_STRUCT(last_header); aws_http_headers_get_index(headers, aws_http_headers_count(headers) - 1, &last_header); front = !aws_strutil_is_http_pseudo_header_name(last_header.name); @@ -454,10 +454,8 @@ static struct aws_http_message *s_message_new_common( struct aws_allocator *allocator, struct aws_http_headers *existing_headers) { + /* allocation cannot fail */ struct aws_http_message *message = aws_mem_calloc(allocator, 1, sizeof(struct aws_http_message)); - if (!message) { - goto error; - } message->allocator = allocator; aws_atomic_init_int(&message->refcount, 1); @@ -831,6 +829,103 @@ struct aws_http_stream *aws_http_connection_make_request( return stream; } +struct aws_http_message *aws_http2_message_new_from_http1( + struct aws_http_message *http1_msg, + struct aws_allocator *alloc) { + + struct aws_http_headers *old_headers = aws_http_message_get_headers(http1_msg); + struct aws_http_header header_iter; + struct aws_byte_buf lower_name_buf; + AWS_ZERO_STRUCT(lower_name_buf); + struct aws_http_message *message = aws_http_message_is_request(http1_msg) ? aws_http2_message_new_request(alloc) + : aws_http2_message_new_response(alloc); + struct aws_http_headers *copied_headers = message->headers; + if (!message) { + return NULL; + } + /* Set pseudo headers from HTTP/1.1 message */ + if (aws_http_message_is_request(http1_msg)) { + struct aws_byte_cursor method; + if (aws_http_message_get_request_method(http1_msg, &method)) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_GENERAL, + "Failed to create HTTP/2 message from HTTP/1 message, ip: %p, due to no method found.", + (void *)http1_msg); + /* error will happen when the request is invalid */ + aws_raise_error(AWS_ERROR_HTTP_INVALID_METHOD); + goto error; + } + /* Use add intead of set method to avoid push front to the array list */ + if (aws_http_headers_add(copied_headers, aws_http_header_method, method)) { + goto error; + } + /** + * we set a default value, "https", for now. + * TODO: as we support prior knowledge, we may also want to support http? + */ + struct aws_byte_cursor scheme_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("https"); + if (aws_http_headers_add(copied_headers, aws_http_header_scheme, scheme_cursor)) { + goto error; + } + /* :authority SHOULD NOT be created when translating HTTP/1 request.(RFC 7540 8.1.2.3) */ + + struct aws_byte_cursor path_cursor; + if (aws_http_message_get_request_path(http1_msg, &path_cursor)) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_GENERAL, + "Failed to create HTTP/2 message from HTTP/1 message, ip: %p, due to no path found.", + (void *)http1_msg); + aws_raise_error(AWS_ERROR_HTTP_INVALID_PATH); + goto error; + } + if (aws_http_headers_add(copied_headers, aws_http_header_path, path_cursor)) { + goto error; + } + } else { + int status = 0; + if (aws_http_message_get_response_status(http1_msg, &status)) { + AWS_LOGF_ERROR( + AWS_LS_HTTP_GENERAL, + "Failed to create HTTP/2 response message from HTTP/1 response message, ip: %p, due to no status " + "found.", + (void *)http1_msg); + /* error will happen when the request is invalid */ + aws_raise_error(AWS_ERROR_HTTP_INVALID_STATUS_CODE); + goto error; + } + if (aws_http2_headers_set_response_status(copied_headers, status)) { + goto error; + } + } + + if (aws_byte_buf_init(&lower_name_buf, alloc, 256)) { + goto error; + } + for (size_t iter = 0; iter < aws_http_headers_count(old_headers); iter++) { + /* name should be converted to lower case */ + if (aws_http_headers_get_index(old_headers, iter, &header_iter)) { + goto error; + } + /* append lower case name to the buffer */ + aws_byte_buf_append_with_lookup(&lower_name_buf, &header_iter.name, aws_lookup_table_to_lower_get()); + struct aws_byte_cursor lower_name_cursor = aws_byte_cursor_from_buf(&lower_name_buf); + + /* TODO: handle connection-specific header field (RFC7540 8.1.2.2) */ + if (aws_http_headers_add(copied_headers, lower_name_cursor, header_iter.value)) { + goto error; + } + aws_byte_buf_reset(&lower_name_buf, false); + } + aws_byte_buf_clean_up(&lower_name_buf); + /* TODO: Refcount the input stream of old message */ + + return message; +error: + aws_http_message_release(message); + aws_byte_buf_clean_up(&lower_name_buf); + return NULL; +} + int aws_http_stream_activate(struct aws_http_stream *stream) { AWS_PRECONDITION(stream); AWS_PRECONDITION(stream->vtable); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3090a8ab1..481c2ec06 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -374,11 +374,11 @@ add_test_case(h2_client_auto_ping_ack) add_test_case(h2_client_auto_ping_ack_higher_priority) #TODO add_test_case(h2_client_auto_ping_ack_higher_priority_not_break_encoding_frame) add_test_case(h2_client_auto_settings_ack) -add_test_case(h2_client_request_cookie_headers) add_test_case(h2_client_stream_complete) add_test_case(h2_client_close) add_test_case(h2_client_connection_init_settings_applied_after_ack_by_peer) add_test_case(h2_client_stream_with_h1_request_message) +add_test_case(h2_client_stream_with_cookies_headers) add_test_case(h2_client_stream_err_malformed_header) add_test_case(h2_client_stream_err_state_forbids_frame) add_test_case(h2_client_conn_err_stream_frames_received_for_idle_stream) diff --git a/tests/test_h2_client.c b/tests/test_h2_client.c index 2665fe74a..b66ca80a2 100644 --- a/tests/test_h2_client.c +++ b/tests/test_h2_client.c @@ -148,7 +148,7 @@ TEST_CASE(h2_client_stream_create) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* create request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header headers[] = { @@ -184,7 +184,7 @@ TEST_CASE(h2_client_unactivated_stream_cleans_up) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* create request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header headers[] = { @@ -275,7 +275,7 @@ TEST_CASE(h2_client_auto_ping_ack_higher_priority) { size_t frames_count = h2_decode_tester_frame_count(&s_tester.peer.decode); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -356,47 +356,6 @@ static int s_compare_headers(const struct aws_http_headers *expected, const stru return AWS_OP_SUCCESS; } -/* Test that h2 can split cookie headers from request, if we need to compress it use cache. */ -TEST_CASE(h2_client_request_cookie_headers) { - (void)ctx; - aws_http_library_init(allocator); - - /* send a request with cookie headers */ - struct aws_http_message *request = aws_http_message_new_request(allocator); - ASSERT_NOT_NULL(request); - struct aws_http_header request_headers_src[] = { - DEFINE_HEADER(":method", "GET"), - DEFINE_HEADER(":scheme", "https"), - DEFINE_HEADER(":path", "/"), - DEFINE_HEADER("cookie", "a=b; c=d; e=f"), - }; - aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); - - struct aws_http_headers *h2_headers = aws_h2_create_headers_from_request(request, allocator); - - /* set expected h2 style headers */ - struct aws_http_header expected_headers_src[] = { - DEFINE_HEADER(":method", "GET"), - DEFINE_HEADER(":scheme", "https"), - DEFINE_HEADER(":path", "/"), - DEFINE_HEADER("cookie", "a=b"), - DEFINE_HEADER("cookie", "c=d"), - DEFINE_HEADER("cookie", "e=f"), - }; - struct aws_http_headers *expected_headers = aws_http_headers_new(allocator); - ASSERT_SUCCESS( - aws_http_headers_add_array(expected_headers, expected_headers_src, AWS_ARRAY_SIZE(expected_headers_src))); - - ASSERT_SUCCESS(s_compare_headers(expected_headers, h2_headers)); - - /* clean up */ - aws_http_headers_release(h2_headers); - aws_http_headers_release(expected_headers); - aws_http_message_release(request); - aws_http_library_clean_up(); - return AWS_OP_SUCCESS; -} - /* Test that a simple request/response can be carried to completion. * The request consists of a single HEADERS frame and the response consists of a single HEADERS frame. */ TEST_CASE(h2_client_stream_complete) { @@ -407,7 +366,7 @@ TEST_CASE(h2_client_stream_complete) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -467,7 +426,7 @@ TEST_CASE(h2_client_close) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -514,7 +473,7 @@ TEST_CASE(h2_client_connection_init_settings_applied_after_ack_by_peer) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -610,9 +569,59 @@ TEST_CASE(h2_client_stream_with_h1_request_message) { struct aws_http_header expected_headers_src[] = { DEFINE_HEADER(":method", "GET"), DEFINE_HEADER(":scheme", "https"), - DEFINE_HEADER(":authority", "example.com"), DEFINE_HEADER(":path", "/"), DEFINE_HEADER("accept", "*/*"), + DEFINE_HEADER("host", "example.com"), + }; + struct aws_http_headers *expected_headers = aws_http_headers_new(allocator); + ASSERT_SUCCESS( + aws_http_headers_add_array(expected_headers, expected_headers_src, AWS_ARRAY_SIZE(expected_headers_src))); + /* validate sent request, */ + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); + + struct h2_decoded_frame *sent_headers_frame = h2_decode_tester_latest_frame(&s_tester.peer.decode); + ASSERT_INT_EQUALS(AWS_H2_FRAME_T_HEADERS, sent_headers_frame->type); + ASSERT_TRUE(sent_headers_frame->end_stream); + ASSERT_SUCCESS(s_compare_headers(expected_headers, sent_headers_frame->headers)); + + /* clean up */ + aws_http_headers_release(expected_headers); + aws_http_message_release(request); + client_stream_tester_clean_up(&stream_tester); + return s_tester_clean_up(); +} + +/* Test that h2 stream can split the cookies header correctly */ +TEST_CASE(h2_client_stream_with_cookies_headers) { + ASSERT_SUCCESS(s_tester_init(allocator, ctx)); + + /* fake peer sends connection preface */ + ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); + testing_channel_drain_queued_tasks(&s_tester.testing_channel); + + /* send an h1 request */ + struct aws_http_message *request = aws_http_message_new_request(allocator); + ASSERT_NOT_NULL(request); + AWS_FATAL_ASSERT(AWS_OP_SUCCESS == aws_http_message_set_request_method(request, aws_http_method_get)); + AWS_FATAL_ASSERT(AWS_OP_SUCCESS == aws_http_message_set_request_path(request, aws_byte_cursor_from_c_str("/"))); + struct aws_http_header request_headers_src[] = { + DEFINE_HEADER("Accept", "*/*"), + DEFINE_HEADER("Host", "example.com"), + DEFINE_HEADER("cookie", "a=b; c=d; e=f"), + }; + aws_http_message_add_header_array(request, request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); + struct client_stream_tester stream_tester; + ASSERT_SUCCESS(s_stream_tester_init(&stream_tester, request)); + + /* set expected h2 style headers */ + struct aws_http_header expected_headers_src[] = { + DEFINE_HEADER(":method", "GET"), + DEFINE_HEADER(":scheme", "https"), + DEFINE_HEADER(":path", "/"), + DEFINE_HEADER("accept", "*/*"), + DEFINE_HEADER("host", "example.com"), + DEFINE_HEADER("cookie", "a=b; c=d; e=f"), }; struct aws_http_headers *expected_headers = aws_http_headers_new(allocator); ASSERT_SUCCESS( @@ -642,7 +651,7 @@ TEST_CASE(h2_client_stream_err_malformed_header) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -697,7 +706,7 @@ TEST_CASE(h2_client_stream_err_state_forbids_frame) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -817,7 +826,7 @@ TEST_CASE(h2_client_stream_ignores_some_frames_received_soon_after_closing) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -878,7 +887,7 @@ TEST_CASE(h2_client_stream_receive_info_headers) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -945,7 +954,7 @@ TEST_CASE(h2_client_stream_err_receive_info_headers_after_main) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1015,7 +1024,7 @@ TEST_CASE(h2_client_stream_receive_trailing_headers) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1079,7 +1088,7 @@ TEST_CASE(h2_client_stream_err_receive_trailing_before_main) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1138,7 +1147,7 @@ TEST_CASE(h2_client_conn_err_stream_frames_received_soon_after_closing) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1200,7 +1209,7 @@ TEST_CASE(h2_client_stream_err_stream_frames_received_soon_after_rst_stream_rece testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1273,7 +1282,7 @@ TEST_CASE(h2_client_conn_err_stream_frames_received_after_removed_from_cache) { /* fill out the cache */ for (size_t i = 0; i < NUM_STREAMS; i++) { - requests[i] = aws_http_message_new_request(allocator); + requests[i] = aws_http2_message_new_request(allocator); aws_http_message_add_header_array(requests[i], request_headers_src, AWS_ARRAY_SIZE(request_headers_src)); ASSERT_SUCCESS(s_stream_tester_init(&stream_tester[i], requests[i])); testing_channel_drain_queued_tasks(&s_tester.testing_channel); @@ -1330,7 +1339,7 @@ TEST_CASE(h2_client_stream_receive_data) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1388,7 +1397,7 @@ TEST_CASE(h2_client_stream_err_receive_data_before_headers) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1434,7 +1443,7 @@ TEST_CASE(h2_client_stream_send_data) { ASSERT_SUCCESS(s_tester_init(allocator, ctx)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1541,7 +1550,7 @@ TEST_CASE(h2_client_stream_send_lots_of_data) { struct aws_input_stream *request_bodies[NUM_STREAMS]; struct client_stream_tester stream_testers[NUM_STREAMS]; for (size_t i = 0; i < NUM_STREAMS; ++i) { - requests[i] = aws_http_message_new_request(allocator); + requests[i] = aws_http2_message_new_request(allocator); aws_http_message_add_header_array(requests[i], request_headers_src[i], AWS_ARRAY_SIZE(request_headers_src[i])); /* fill first body with "aaaa...", second with "bbbb...", etc */ @@ -1662,7 +1671,7 @@ TEST_CASE(h2_client_stream_send_stalled_data) { /* get request ready * the body_stream will stall and provide no data when we try to read from it */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1771,7 +1780,7 @@ TEST_CASE(h2_client_stream_send_data_controlled_by_stream_window_size) { size_t frames_count = h2_decode_tester_frame_count(&s_tester.peer.decode); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1854,7 +1863,7 @@ TEST_CASE(h2_client_stream_send_data_controlled_by_negative_stream_window_size) size_t frames_count = h2_decode_tester_frame_count(&s_tester.peer.decode); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -1977,7 +1986,7 @@ TEST_CASE(h2_client_stream_send_data_controlled_by_connection_window_size) { struct aws_input_stream *request_bodies[NUM_STREAMS]; struct client_stream_tester stream_testers[NUM_STREAMS]; for (size_t i = 0; i < NUM_STREAMS; ++i) { - requests[i] = aws_http_message_new_request(allocator); + requests[i] = aws_http2_message_new_request(allocator); aws_http_message_add_header_array(requests[i], request_headers_src[i], AWS_ARRAY_SIZE(request_headers_src[i])); /* fill first body with "aaaa...", second with "bbbb...", etc */ @@ -2109,7 +2118,7 @@ TEST_CASE(h2_client_stream_send_data_controlled_by_connection_and_stream_window_ struct aws_input_stream *request_bodies[NUM_STREAMS]; struct client_stream_tester stream_testers[NUM_STREAMS]; for (size_t i = 0; i < NUM_STREAMS; ++i) { - requests[i] = aws_http_message_new_request(allocator); + requests[i] = aws_http2_message_new_request(allocator); aws_http_message_add_header_array(requests[i], request_headers_src[i], AWS_ARRAY_SIZE(request_headers_src[i])); /* fill first body with "aaaa...", second with "bbbb...", etc */ @@ -2283,7 +2292,7 @@ TEST_CASE(h2_client_stream_send_window_update) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2367,7 +2376,7 @@ TEST_CASE(h2_client_stream_err_received_data_flow_control) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2477,7 +2486,7 @@ TEST_CASE(h2_client_conn_err_received_data_flow_control) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2559,7 +2568,7 @@ static int s_invalid_window_update( testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2652,7 +2661,7 @@ TEST_CASE(h2_client_conn_err_initial_window_size_settings_cause_window_exceed_ma testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2726,7 +2735,7 @@ TEST_CASE(h2_client_stream_receive_end_stream_before_done_sending) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* get request ready */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2804,7 +2813,7 @@ TEST_CASE(h2_client_stream_receive_end_stream_and_rst_before_done_sending) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* get request ready */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2874,7 +2883,7 @@ TEST_CASE(h2_client_stream_err_input_stream_failure) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* get request ready */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2919,7 +2928,7 @@ TEST_CASE(h2_client_stream_err_receive_rst_stream) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -2970,7 +2979,7 @@ TEST_CASE(h2_client_push_promise_automatically_rejected) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3088,7 +3097,7 @@ TEST_CASE(h2_client_conn_receive_goaway) { }; struct client_stream_tester stream_testers[NUM_STREAMS]; for (size_t i = 0; i < NUM_STREAMS; ++i) { - requests[i] = aws_http_message_new_request(allocator); + requests[i] = aws_http2_message_new_request(allocator); aws_http_message_add_header_array(requests[i], request_headers_src[i], AWS_ARRAY_SIZE(request_headers_src[i])); } /* Send the first two requests */ @@ -3284,7 +3293,7 @@ TEST_CASE(h2_client_change_settings_succeed) { /* Check the callback has fired after the second settings ack frame, the error code we got is NO_ERROR(0) */ ASSERT_INT_EQUALS(0, callback_error_code); - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3392,7 +3401,7 @@ TEST_CASE(h2_client_manual_window_management_disabled_auto_window_update) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3491,7 +3500,7 @@ TEST_CASE(h2_client_manual_window_management_user_send_stream_window_update) { testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3581,7 +3590,7 @@ TEST_CASE(h2_client_manual_window_management_user_send_stream_window_update_over testing_channel_drain_queued_tasks(&s_tester.testing_channel); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3630,7 +3639,7 @@ TEST_CASE(h2_client_manual_window_management_user_send_conn_window_update) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -3993,7 +4002,7 @@ TEST_CASE(h2_client_stream_reset_stream) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4043,7 +4052,7 @@ TEST_CASE(h2_client_stream_reset_ignored_stream_closed) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4091,7 +4100,7 @@ TEST_CASE(h2_client_stream_reset_failed_before_activate_called) { /* get connection preface and acks out of the way */ ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4136,7 +4145,7 @@ TEST_CASE(h2_client_stream_keeps_alive_for_cross_thread_task) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4204,7 +4213,7 @@ TEST_CASE(h2_client_stream_get_received_reset_error_code) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4252,7 +4261,7 @@ TEST_CASE(h2_client_stream_get_sent_reset_error_code) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* send request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { @@ -4307,7 +4316,7 @@ TEST_CASE(h2_client_new_request_allowed) { ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); /* prepare request */ - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header headers[] = { @@ -4589,7 +4598,7 @@ TEST_CASE(h2_client_request_apis_failed_after_connection_begin_shutdown) { /* get connection preface and acks out of the way */ ASSERT_SUCCESS(h2_fake_peer_send_connection_preface_default_settings(&s_tester.peer)); ASSERT_SUCCESS(h2_fake_peer_decode_messages_from_testing_channel(&s_tester.peer)); - struct aws_http_message *request = aws_http_message_new_request(allocator); + struct aws_http_message *request = aws_http2_message_new_request(allocator); ASSERT_NOT_NULL(request); struct aws_http_header request_headers_src[] = { diff --git a/tests/test_h2_decoder.c b/tests/test_h2_decoder.c index 94e9c3bbe..ececc1efc 100644 --- a/tests/test_h2_decoder.c +++ b/tests/test_h2_decoder.c @@ -681,7 +681,7 @@ H2_DECODER_ON_SERVER_TEST(h2_decoder_headers_cookies) { return AWS_OP_SUCCESS; } -/* A trailing header has no psuedo-headers, and always ends the stream */ +/* A trailing header has no pseudo-headers, and always ends the stream */ H2_DECODER_ON_CLIENT_TEST(h2_decoder_headers_trailer) { (void)allocator; struct fixture *fixture = ctx; diff --git a/tests/test_message.c b/tests/test_message.c index dcfeb4c8d..4948818a9 100644 --- a/tests/test_message.c +++ b/tests/test_message.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -11,6 +12,8 @@ #define TEST_CASE(NAME) \ AWS_TEST_CASE(NAME, s_test_##NAME); \ static int s_test_##NAME(struct aws_allocator *allocator, void *ctx) +#define DEFINE_HEADER(NAME, VALUE) \ + { .name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(NAME), .value = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL(VALUE), } TEST_CASE(message_sanity_check) { (void)ctx;