Skip to content

Commit 0426a06

Browse files
authored
HTTP/2 message stream (#344)
Use HTTP/2 message instead of always translating HTTP/1 message to HTTP/2
1 parent 236ce08 commit 0426a06

File tree

11 files changed

+272
-225
lines changed

11 files changed

+272
-225
lines changed

bin/elasticurl/main.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -390,23 +390,33 @@ static void s_on_stream_complete_fn(struct aws_http_stream *stream, int error_co
390390
}
391391

392392
static struct aws_http_message *s_build_http_request(struct elasticurl_ctx *app_ctx) {
393-
struct aws_http_message *request = aws_http_message_new_request(app_ctx->allocator);
393+
394+
struct aws_http_message *request = app_ctx->required_http_version == AWS_HTTP_VERSION_2
395+
? aws_http2_message_new_request(app_ctx->allocator)
396+
: aws_http_message_new_request(app_ctx->allocator);
394397
if (request == NULL) {
395398
fprintf(stderr, "failed to allocate request\n");
396399
exit(1);
397400
}
398401

399402
aws_http_message_set_request_method(request, aws_byte_cursor_from_c_str(app_ctx->verb));
400403
aws_http_message_set_request_path(request, app_ctx->uri.path_and_query);
404+
if (app_ctx->required_http_version == AWS_HTTP_VERSION_2) {
405+
struct aws_http_headers *h2_headers = aws_http_message_get_headers(request);
406+
aws_http2_headers_set_request_scheme(h2_headers, app_ctx->uri.scheme);
407+
aws_http2_headers_set_request_authority(h2_headers, app_ctx->uri.host_name);
408+
} else {
409+
struct aws_http_header host_header = {
410+
.name = aws_byte_cursor_from_c_str("host"),
411+
.value = app_ctx->uri.host_name,
412+
};
413+
aws_http_message_add_header(request, host_header);
414+
}
401415
struct aws_http_header accept_header = {
402416
.name = aws_byte_cursor_from_c_str("accept"),
403417
.value = aws_byte_cursor_from_c_str("*/*"),
404418
};
405419
aws_http_message_add_header(request, accept_header);
406-
407-
struct aws_http_header host_header = {.name = aws_byte_cursor_from_c_str("host"), .value = app_ctx->uri.host_name};
408-
aws_http_message_add_header(request, host_header);
409-
410420
struct aws_http_header user_agent_header = {
411421
.name = aws_byte_cursor_from_c_str("user-agent"),
412422
.value = aws_byte_cursor_from_c_str("elasticurl 1.0, Powered by the AWS Common Runtime.")};

include/aws/http/private/h2_connection.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,6 @@ struct aws_http_connection *aws_http_connection_new_http2_client(
221221
bool manual_window_management,
222222
const struct aws_http2_connection_options *http2_options);
223223

224-
/* Transform the request to h2 style headers */
225-
AWS_HTTP_API
226-
struct aws_http_headers *aws_h2_create_headers_from_request(
227-
struct aws_http_message *request,
228-
struct aws_allocator *alloc);
229-
230224
AWS_EXTERN_C_END
231225

232226
/* Private functions called from multiple .c files... */

include/aws/http/private/h2_stream.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ struct aws_h2_stream {
9494

9595
/* Store the received reset HTTP/2 error code, set to -1, if none has received so far */
9696
int64_t received_reset_error_code;
97+
98+
/**
99+
* Back up the message if we create a new message from it to keep the underlying input stream alive.
100+
* TODO: remove this once we have input stream refcounted
101+
*/
102+
struct aws_http_message *backup_outgoing_message;
97103
};
98104

99105
const char *aws_h2_stream_state_to_str(enum aws_h2_stream_state state);

include/aws/http/private/request_response_impl.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,20 @@ struct aws_http_stream {
6262
struct aws_http_stream_server_data *server_data;
6363
};
6464

65+
AWS_EXTERN_C_BEGIN
66+
67+
/**
68+
* Create an HTTP/2 message from HTTP/1.1 message.
69+
* pseudo headers will be created from the context and added to the headers of new message.
70+
* Normal headers will be copied to the headers of new message.
71+
* TODO: (Maybe more, connection-specific header will be removed, etc...)
72+
* TODO: REFCOUNT INPUT_STREAMS!!! And make it public.
73+
*/
74+
AWS_HTTP_API
75+
struct aws_http_message *aws_http2_message_new_from_http1(
76+
struct aws_http_message *http1_msg,
77+
struct aws_allocator *alloc);
78+
79+
AWS_EXTERN_C_END
80+
6581
#endif /* AWS_HTTP_REQUEST_RESPONSE_IMPL_H */

source/h2_connection.c

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,110 +1721,6 @@ static void s_stream_complete(struct aws_h2_connection *connection, struct aws_h
17211721
aws_http_stream_release(&stream->base);
17221722
}
17231723

1724-
struct aws_http_headers *aws_h2_create_headers_from_request(
1725-
struct aws_http_message *request,
1726-
struct aws_allocator *alloc) {
1727-
1728-
struct aws_http_headers *old_headers = aws_http_message_get_headers(request);
1729-
bool is_pseudoheader = false;
1730-
struct aws_http_headers *result = aws_http_headers_new(alloc);
1731-
struct aws_http_header header_iter;
1732-
struct aws_byte_buf lower_name_buf;
1733-
AWS_ZERO_STRUCT(lower_name_buf);
1734-
1735-
/* Check whether the old_headers have pseudo header or not */
1736-
if (aws_http_headers_count(old_headers)) {
1737-
if (aws_http_headers_get_index(old_headers, 0, &header_iter)) {
1738-
goto error;
1739-
}
1740-
is_pseudoheader = header_iter.name.ptr[0] == ':';
1741-
}
1742-
if (!is_pseudoheader) {
1743-
/* TODO: Set pseudo headers all from message, which will lead an API change to aws_http_message */
1744-
/* No pseudoheader detected, we set them from the request */
1745-
/* Set pseudo headers */
1746-
struct aws_byte_cursor method;
1747-
if (aws_http_message_get_request_method(request, &method)) {
1748-
/* error will happen when the request is invalid */
1749-
aws_raise_error(AWS_ERROR_HTTP_INVALID_METHOD);
1750-
goto error;
1751-
}
1752-
if (aws_http_headers_add(result, aws_http_header_method, method)) {
1753-
goto error;
1754-
}
1755-
/* we set a default value, "https", for now */
1756-
struct aws_byte_cursor scheme_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("https");
1757-
if (aws_http_headers_add(result, aws_http_header_scheme, scheme_cursor)) {
1758-
goto error;
1759-
}
1760-
/* Set an empty authority for now, if host header field is found, we set it as the value of host */
1761-
struct aws_byte_cursor authority_cursor;
1762-
struct aws_byte_cursor host_cursor = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("host");
1763-
if (!aws_http_headers_get(old_headers, host_cursor, &authority_cursor)) {
1764-
if (aws_http_headers_add(result, aws_http_header_authority, authority_cursor)) {
1765-
goto error;
1766-
}
1767-
}
1768-
struct aws_byte_cursor path_cursor;
1769-
if (aws_http_message_get_request_path(request, &path_cursor)) {
1770-
aws_raise_error(AWS_ERROR_HTTP_INVALID_PATH);
1771-
goto error;
1772-
}
1773-
if (aws_http_headers_add(result, aws_http_header_path, path_cursor)) {
1774-
goto error;
1775-
}
1776-
}
1777-
/* if pseudoheader is included in message, we just convert all the headers from old_headers to result */
1778-
if (aws_byte_buf_init(&lower_name_buf, alloc, 256)) {
1779-
goto error;
1780-
}
1781-
for (size_t iter = 0; iter < aws_http_headers_count(old_headers); iter++) {
1782-
/* name should be converted to lower case */
1783-
if (aws_http_headers_get_index(old_headers, iter, &header_iter)) {
1784-
goto error;
1785-
}
1786-
/* append lower case name to the buffer */
1787-
aws_byte_buf_append_with_lookup(&lower_name_buf, &header_iter.name, aws_lookup_table_to_lower_get());
1788-
struct aws_byte_cursor lower_name_cursor = aws_byte_cursor_from_buf(&lower_name_buf);
1789-
enum aws_http_header_name name_enum = aws_http_lowercase_str_to_header_name(lower_name_cursor);
1790-
switch (name_enum) {
1791-
case AWS_HTTP_HEADER_COOKIE:
1792-
/* split cookie if USE CACHE */
1793-
if (header_iter.compression == AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) {
1794-
struct aws_byte_cursor cookie_chunk;
1795-
AWS_ZERO_STRUCT(cookie_chunk);
1796-
while (aws_byte_cursor_next_split(&header_iter.value, ';', &cookie_chunk)) {
1797-
if (aws_http_headers_add(
1798-
result, lower_name_cursor, aws_strutil_trim_http_whitespace(cookie_chunk))) {
1799-
goto error;
1800-
}
1801-
}
1802-
} else {
1803-
if (aws_http_headers_add(result, lower_name_cursor, header_iter.value)) {
1804-
goto error;
1805-
}
1806-
}
1807-
break;
1808-
case AWS_HTTP_HEADER_HOST:
1809-
/* host header has been converted to :authority, do nothing here */
1810-
break;
1811-
/* TODO: handle connection-specific header field (RFC7540 8.1.2.2) */
1812-
default:
1813-
if (aws_http_headers_add(result, lower_name_cursor, header_iter.value)) {
1814-
goto error;
1815-
}
1816-
break;
1817-
}
1818-
aws_byte_buf_reset(&lower_name_buf, false);
1819-
}
1820-
aws_byte_buf_clean_up(&lower_name_buf);
1821-
return result;
1822-
error:
1823-
aws_http_headers_release(result);
1824-
aws_byte_buf_clean_up(&lower_name_buf);
1825-
return NULL;
1826-
}
1827-
18281724
int aws_h2_connection_on_stream_closed(
18291725
struct aws_h2_connection *connection,
18301726
struct aws_h2_stream *stream,

source/h2_stream.c

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,27 @@ struct aws_h2_stream *aws_h2_stream_new_request(
247247

248248
/* Init H2 specific stuff */
249249
stream->thread_data.state = AWS_H2_STREAM_STATE_IDLE;
250-
stream->thread_data.outgoing_message = options->request;
250+
enum aws_http_version message_version = aws_http_message_get_protocol_version(options->request);
251+
switch (message_version) {
252+
case AWS_HTTP_VERSION_1_1:
253+
stream->thread_data.outgoing_message =
254+
aws_http2_message_new_from_http1(options->request, stream->base.alloc);
255+
if (!stream->thread_data.outgoing_message) {
256+
AWS_H2_STREAM_LOG(ERROR, stream, "Stream failed to create the HTTP/2 message from HTTP/1.1 message");
257+
goto error;
258+
}
259+
stream->backup_outgoing_message = options->request;
260+
aws_http_message_acquire(stream->backup_outgoing_message);
261+
break;
262+
case AWS_HTTP_VERSION_2:
263+
stream->thread_data.outgoing_message = options->request;
264+
aws_http_message_acquire(stream->thread_data.outgoing_message);
265+
break;
266+
default:
267+
/* Not supported */
268+
aws_raise_error(AWS_ERROR_HTTP_UNSUPPORTED_PROTOCOL);
269+
goto error;
270+
}
251271

252272
stream->sent_reset_error_code = -1;
253273
stream->received_reset_error_code = -1;
@@ -257,13 +277,14 @@ struct aws_h2_stream *aws_h2_stream_new_request(
257277
if (aws_mutex_init(&stream->synced_data.lock)) {
258278
AWS_H2_STREAM_LOGF(
259279
ERROR, stream, "Mutex init error %d (%s).", aws_last_error(), aws_error_name(aws_last_error()));
260-
aws_mem_release(stream->base.alloc, stream);
261-
return NULL;
280+
goto error;
262281
}
263-
aws_http_message_acquire(stream->thread_data.outgoing_message);
264282
aws_channel_task_init(
265283
&stream->cross_thread_work_task, s_stream_cross_thread_work_task, stream, "HTTP/2 stream cross-thread work");
266284
return stream;
285+
error:
286+
s_stream_destroy(&stream->base);
287+
return NULL;
267288
}
268289

269290
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) {
335356
AWS_H2_STREAM_LOG(DEBUG, stream, "Destroying stream");
336357
aws_mutex_clean_up(&stream->synced_data.lock);
337358
aws_http_message_release(stream->thread_data.outgoing_message);
359+
aws_http_message_release(stream->backup_outgoing_message);
338360

339361
aws_mem_release(stream->base.alloc, stream);
340362
}
@@ -548,13 +570,11 @@ int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgo
548570

549571
/* Create HEADERS frame */
550572
struct aws_http_message *msg = stream->thread_data.outgoing_message;
573+
/* Should be ensured when the stream is created */
574+
AWS_ASSERT(aws_http_message_get_protocol_version(msg) == AWS_HTTP_VERSION_2);
551575
bool has_body_stream = aws_http_message_get_body_stream(msg) != NULL;
552-
struct aws_http_headers *h2_headers = aws_h2_create_headers_from_request(msg, stream->base.alloc);
553-
if (!h2_headers) {
554-
AWS_H2_STREAM_LOGF(
555-
ERROR, stream, "Failed to create HTTP/2 style headers from request %s", aws_error_name(aws_last_error()));
556-
goto error;
557-
}
576+
struct aws_http_headers *h2_headers = aws_http_message_get_headers(msg);
577+
558578
struct aws_h2_frame *headers_frame = aws_h2_frame_new_headers(
559579
stream->base.alloc,
560580
stream->base.id,
@@ -563,8 +583,6 @@ int aws_h2_stream_on_activated(struct aws_h2_stream *stream, bool *out_has_outgo
563583
0 /* padding - not currently configurable via public API */,
564584
NULL /* priority - not currently configurable via public API */);
565585

566-
/* Release refcount of h2_headers here, let frame take the full ownership of it */
567-
aws_http_headers_release(h2_headers);
568586
if (!headers_frame) {
569587
AWS_H2_STREAM_LOGF(ERROR, stream, "Failed to create HEADERS frame: %s", aws_error_name(aws_last_error()));
570588
goto error;

0 commit comments

Comments
 (0)