Skip to content

Commit 72c7241

Browse files
authored
Cutomized alpn (#329)
You can customize the alpn map now
1 parent 3a06e71 commit 72c7241

File tree

8 files changed

+306
-23
lines changed

8 files changed

+306
-23
lines changed

include/aws/http/connection.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,16 @@ struct aws_http_client_connection_options {
329329
*/
330330
bool prior_knowledge_http2;
331331

332+
/**
333+
* Optional.
334+
* Pointer to the hash map containing the ALPN string to protocol to use.
335+
* Hash from `struct aws_string *` to `enum aws_http_version`.
336+
* If not set, only the predefined string `h2` and `http/1.1` will be recognized. Other negotiated ALPN string will
337+
* result in a HTTP1/1 connection
338+
* Note: Connection will keep a deep copy of the table and the strings.
339+
*/
340+
struct aws_hash_table *alpn_string_map;
341+
332342
/**
333343
* Options specific to HTTP/1.x connections.
334344
* Optional.
@@ -452,6 +462,22 @@ enum aws_http_version aws_http_connection_get_version(const struct aws_http_conn
452462
AWS_HTTP_API
453463
struct aws_channel *aws_http_connection_get_channel(struct aws_http_connection *connection);
454464

465+
/**
466+
* Initialize an map copied from the *src map, which maps `struct aws_string *` to `enum aws_http_version`.
467+
*/
468+
AWS_HTTP_API
469+
int aws_http_alpn_map_init_copy(
470+
struct aws_allocator *allocator,
471+
struct aws_hash_table *dest,
472+
struct aws_hash_table *src);
473+
474+
/**
475+
* Initialize an empty hash-table that maps `struct aws_string *` to `enum aws_http_version`.
476+
* This map can used in aws_http_client_connections_options.alpn_string_map.
477+
*/
478+
AWS_HTTP_API
479+
int aws_http_alpn_map_init(struct aws_allocator *allocator, struct aws_hash_table *map);
480+
455481
/**
456482
* Checks http proxy options for correctness
457483
*/

include/aws/http/private/connection_impl.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,14 @@ struct aws_http_client_bootstrap {
134134
aws_http_proxy_request_transform_fn *proxy_request_transform;
135135

136136
struct aws_http1_connection_options http1_options;
137-
struct aws_http2_connection_options http2_options;
137+
struct aws_http2_connection_options http2_options; /* allocated with bootstrap */
138+
struct aws_hash_table *alpn_string_map; /* allocated with bootstrap */
138139
struct aws_http_connection *connection;
139140
};
140141

141142
AWS_EXTERN_C_BEGIN
143+
AWS_HTTP_API
144+
void aws_http_client_bootstrap_destroy(struct aws_http_client_bootstrap *bootstrap);
142145

143146
AWS_HTTP_API
144147
void aws_http_connection_set_system_vtable(const struct aws_http_connection_system_vtable *system_vtable);
@@ -182,6 +185,7 @@ uint32_t aws_http_connection_get_next_stream_id(struct aws_http_connection *conn
182185
* @param manual_window_management is manual window management enabled
183186
* @param prior_knowledge_http2 prior knowledge about http2 connection to be used
184187
* @param initial_window_size what should the initial window size be
188+
* @param alpn_string_map the customized ALPN string map from `struct aws_string *` to `enum aws_http_version`.
185189
* @param http1_options http1 options
186190
* @param http2_options http2 options
187191
* @return a new http connection or NULL on failure
@@ -195,6 +199,7 @@ struct aws_http_connection *aws_http_connection_new_channel_handler(
195199
bool manual_window_management,
196200
bool prior_knowledge_http2,
197201
size_t initial_window_size,
202+
const struct aws_hash_table *alpn_string_map,
198203
const struct aws_http1_connection_options *http1_options,
199204
const struct aws_http2_connection_options *http2_options);
200205

include/aws/http/request_response.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,7 @@ struct aws_http_stream *aws_http_connection_make_request(
715715
AWS_HTTP_API
716716
struct aws_http_stream *aws_http_stream_new_server_request_handler(
717717
const struct aws_http_request_handler_options *options);
718+
718719
/**
719720
* Users must release the stream when they are done with it, or its memory will never be cleaned up.
720721
* This will not cancel the stream, its callbacks will still fire if the stream is still in progress.

source/connection.c

Lines changed: 135 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ static struct aws_http_connection_system_vtable s_default_system_vtable = {
3131

3232
static const struct aws_http_connection_system_vtable *s_system_vtable_ptr = &s_default_system_vtable;
3333

34+
void aws_http_client_bootstrap_destroy(struct aws_http_client_bootstrap *bootstrap) {
35+
/* During allocating, the underlying stuctures should be allocated with the bootstrap by aws_mem_acquire_many. Thus,
36+
* we only need to clean up the first pointer which is the bootstrap */
37+
if (bootstrap->alpn_string_map) {
38+
aws_hash_table_clean_up(bootstrap->alpn_string_map);
39+
}
40+
aws_mem_release(bootstrap->alloc, bootstrap);
41+
}
42+
3443
void aws_http_connection_set_system_vtable(const struct aws_http_connection_system_vtable *system_vtable) {
3544
s_system_vtable_ptr = system_vtable;
3645
}
@@ -78,6 +87,7 @@ struct aws_http_connection *aws_http_connection_new_channel_handler(
7887
bool manual_window_management,
7988
bool prior_knowledge_http2,
8089
size_t initial_window_size,
90+
const struct aws_hash_table *alpn_string_map,
8191
const struct aws_http1_connection_options *http1_options,
8292
const struct aws_http2_connection_options *http2_options) {
8393

@@ -124,7 +134,32 @@ struct aws_http_connection *aws_http_connection_new_channel_handler(
124134
struct aws_channel_handler *tls_handler = tls_slot->handler;
125135
struct aws_byte_buf protocol = aws_tls_handler_protocol(tls_handler);
126136
if (protocol.len) {
127-
if (aws_string_eq_byte_buf(s_alpn_protocol_http_1_1, &protocol)) {
137+
bool customized = false;
138+
if (alpn_string_map) {
139+
customized = true;
140+
struct aws_string *negotiated_result = aws_string_new_from_buf(alloc, &protocol);
141+
struct aws_hash_element *found = NULL;
142+
aws_hash_table_find(alpn_string_map, (void *)negotiated_result, &found);
143+
if (found) {
144+
version = (enum aws_http_version)(size_t)found->value;
145+
AWS_LOGF_DEBUG(
146+
AWS_LS_HTTP_CONNECTION,
147+
"static: Customized ALPN protocol " PRInSTR " used. " PRInSTR " client connection established.",
148+
AWS_BYTE_BUF_PRI(protocol),
149+
AWS_BYTE_CURSOR_PRI(aws_http_version_to_str(version)));
150+
} else {
151+
AWS_LOGF_ERROR(
152+
AWS_LS_HTTP_CONNECTION,
153+
"static: Customized ALPN protocol " PRInSTR
154+
" used. However the it's not found in the ALPN map provided.",
155+
AWS_BYTE_BUF_PRI(protocol));
156+
version = AWS_HTTP_VERSION_UNKNOWN;
157+
}
158+
aws_string_destroy(negotiated_result);
159+
}
160+
if (customized) {
161+
/* Do nothing */
162+
} else if (aws_string_eq_byte_buf(s_alpn_protocol_http_1_1, &protocol)) {
128163
version = AWS_HTTP_VERSION_1_1;
129164
} else if (aws_string_eq_byte_buf(s_alpn_protocol_http_2, &protocol)) {
130165
version = AWS_HTTP_VERSION_2;
@@ -350,6 +385,29 @@ struct aws_channel *aws_http_connection_get_channel(struct aws_http_connection *
350385
return connection->channel_slot->channel;
351386
}
352387

388+
int aws_http_alpn_map_init(struct aws_allocator *allocator, struct aws_hash_table *map) {
389+
AWS_ASSERT(allocator);
390+
AWS_ASSERT(map);
391+
int result = aws_hash_table_init(
392+
map,
393+
allocator,
394+
5 /* initial size */,
395+
aws_hash_string,
396+
aws_hash_callback_string_eq,
397+
aws_hash_callback_string_destroy,
398+
NULL);
399+
if (result) {
400+
/* OOM will crash */
401+
int error_code = aws_last_error();
402+
AWS_LOGF_ERROR(
403+
AWS_LS_HTTP_CONNECTION,
404+
"Failed to initialize ALPN map with error code %d (%s)",
405+
error_code,
406+
aws_error_name(error_code));
407+
}
408+
return result;
409+
}
410+
353411
void aws_http_connection_acquire(struct aws_http_connection *connection) {
354412
AWS_ASSERT(connection);
355413
aws_atomic_fetch_add(&connection->refcount, 1);
@@ -417,6 +475,7 @@ static void s_server_bootstrap_on_accept_channel_setup(
417475
server->manual_window_management,
418476
false, /* prior_knowledge_http2 */
419477
server->initial_window_size,
478+
NULL, /* alpn_string_map */
420479
&http1_options,
421480
&http2_options);
422481
if (!connection) {
@@ -735,7 +794,7 @@ static void s_client_bootstrap_on_channel_setup(
735794
http_bootstrap->on_setup(NULL, error_code, http_bootstrap->user_data);
736795

737796
/* Clean up the http_bootstrap, it has no more work to do. */
738-
aws_mem_release(http_bootstrap->alloc, http_bootstrap);
797+
aws_http_client_bootstrap_destroy(http_bootstrap);
739798
return;
740799
}
741800

@@ -749,6 +808,7 @@ static void s_client_bootstrap_on_channel_setup(
749808
http_bootstrap->manual_window_management,
750809
http_bootstrap->prior_knowledge_http2,
751810
http_bootstrap->initial_window_size,
811+
http_bootstrap->alpn_string_map,
752812
&http_bootstrap->http1_options,
753813
&http_bootstrap->http2_options);
754814
if (!http_bootstrap->connection) {
@@ -840,7 +900,7 @@ static void s_client_bootstrap_on_channel_shutdown(
840900
}
841901

842902
/* Clean up bootstrapper */
843-
aws_mem_release(http_bootstrap->alloc, http_bootstrap);
903+
aws_http_client_bootstrap_destroy(http_bootstrap);
844904
}
845905

846906
static int s_validate_http_client_connection_options(const struct aws_http_client_connection_options *options) {
@@ -885,6 +945,61 @@ static int s_validate_http_client_connection_options(const struct aws_http_clien
885945
return AWS_OP_SUCCESS;
886946
}
887947

948+
struct s_copy_alpn_string_map_context {
949+
struct aws_hash_table *map;
950+
struct aws_allocator *allocator;
951+
};
952+
953+
/* put every item into the source to make a deep copy of the map */
954+
static int s_copy_alpn_string_map(void *context, struct aws_hash_element *item) {
955+
struct s_copy_alpn_string_map_context *func_context = context;
956+
struct aws_hash_table *dest = func_context->map;
957+
/* make a deep copy of the string and hash map will own the copy */
958+
struct aws_string *key_copy = aws_string_new_from_string(func_context->allocator, item->key);
959+
int was_created;
960+
if (aws_hash_table_put(dest, key_copy, item->value, &was_created)) {
961+
int error_code = aws_last_error();
962+
AWS_LOGF_ERROR(
963+
AWS_LS_HTTP_CONNECTION,
964+
"Failed to copy ALPN map with error code %d (%s)",
965+
error_code,
966+
aws_error_name(error_code));
967+
/* failed to put into the table, we need to clean up the copy ourselves */
968+
aws_string_destroy(key_copy);
969+
/* return error to stop iteration */
970+
return AWS_COMMON_HASH_TABLE_ITER_ERROR;
971+
}
972+
if (!was_created) {
973+
/* no new entry created, clean up the copy ourselves */
974+
aws_string_destroy(key_copy);
975+
}
976+
return AWS_COMMON_HASH_TABLE_ITER_CONTINUE;
977+
}
978+
979+
int aws_http_alpn_map_init_copy(
980+
struct aws_allocator *allocator,
981+
struct aws_hash_table *dest,
982+
struct aws_hash_table *src) {
983+
if (aws_http_alpn_map_init(allocator, dest)) {
984+
return AWS_OP_ERR;
985+
}
986+
struct s_copy_alpn_string_map_context context;
987+
context.allocator = allocator;
988+
context.map = dest;
989+
/* make a deep copy of the map */
990+
if (aws_hash_table_foreach(src, s_copy_alpn_string_map, &context)) {
991+
int error_code = aws_last_error();
992+
AWS_LOGF_ERROR(
993+
AWS_LS_HTTP_CONNECTION,
994+
"Failed to copy ALPN map with error code %d (%s)",
995+
error_code,
996+
aws_error_name(error_code));
997+
aws_hash_table_clean_up(dest);
998+
return AWS_OP_ERR;
999+
}
1000+
return AWS_OP_SUCCESS;
1001+
}
1002+
8881003
int aws_http_client_connect_internal(
8891004
const struct aws_http_client_connection_options *orig_options,
8901005
aws_http_proxy_request_transform_fn *proxy_request_transform) {
@@ -899,6 +1014,10 @@ int aws_http_client_connect_internal(
8991014

9001015
/* make copy of options, and add defaults for missing optional structs */
9011016
struct aws_http_client_connection_options options = *orig_options;
1017+
if (options.prior_knowledge_http2 && options.tls_options) {
1018+
AWS_LOGF_ERROR(AWS_LS_HTTP_CONNECTION, "static: HTTP/2 prior knowledge only works with cleartext TCP.");
1019+
return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
1020+
}
9021021

9031022
struct aws_http1_connection_options default_http1_options;
9041023
AWS_ZERO_STRUCT(default_http1_options);
@@ -926,13 +1045,16 @@ int aws_http_client_connect_internal(
9261045
}
9271046

9281047
struct aws_http2_setting *setting_array = NULL;
1048+
struct aws_hash_table *alpn_string_map = NULL;
9291049
if (!aws_mem_acquire_many(
9301050
options.allocator,
931-
2,
1051+
3,
9321052
&http_bootstrap,
9331053
sizeof(struct aws_http_client_bootstrap),
9341054
&setting_array,
935-
options.http2_options->num_initial_settings * sizeof(struct aws_http2_setting))) {
1055+
options.http2_options->num_initial_settings * sizeof(struct aws_http2_setting),
1056+
&alpn_string_map,
1057+
sizeof(struct aws_hash_table))) {
9361058
goto error;
9371059
}
9381060

@@ -959,6 +1081,13 @@ int aws_http_client_connect_internal(
9591081
http_bootstrap->http2_options.initial_settings_array = setting_array;
9601082
}
9611083

1084+
if (options.alpn_string_map) {
1085+
if (aws_http_alpn_map_init_copy(options.allocator, alpn_string_map, options.alpn_string_map)) {
1086+
goto error;
1087+
}
1088+
http_bootstrap->alpn_string_map = alpn_string_map;
1089+
}
1090+
9621091
if (options.monitoring_options) {
9631092
http_bootstrap->monitoring_options = *options.monitoring_options;
9641093
}
@@ -998,7 +1127,7 @@ int aws_http_client_connect_internal(
9981127

9991128
error:
10001129
if (http_bootstrap) {
1001-
aws_mem_release(http_bootstrap->alloc, http_bootstrap);
1130+
aws_http_client_bootstrap_destroy(http_bootstrap);
10021131
}
10031132

10041133
if (host_name) {
@@ -1070,6 +1199,5 @@ uint32_t aws_http_connection_get_next_stream_id(struct aws_http_connection *conn
10701199
} else {
10711200
connection->next_stream_id += 2;
10721201
}
1073-
10741202
return next_id;
10751203
}

source/proxy_connection.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ static int s_aws_http_apply_http_connection_to_proxied_channel(struct aws_http_p
536536
context->original_manual_window_management,
537537
false, /* prior_knowledge_http2 */
538538
context->original_initial_window_size,
539+
NULL, /* alpn_string_map */
539540
&context->original_http1_options,
540541
NULL); /* TODO: support http2 options */
541542
if (connection == NULL) {

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ add_test_case(connection_setup_shutdown)
444444
add_test_case(connection_setup_shutdown_tls)
445445
add_test_case(connection_h2_prior_knowledge)
446446
add_test_case(connection_h2_prior_knowledge_not_work_with_tls)
447+
add_test_case(connection_customized_alpn)
448+
add_test_case(connection_customized_alpn_error_with_unknow_return_string)
447449
# These server tests occasionally fail. Resurrect if/when we get back to work on HTTP server.
448450
#add_test_case(connection_destroy_server_with_connection_existing)
449451
#add_test_case(connection_destroy_server_with_multiple_connections_existing)

tests/proxy_test_helper.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ int proxy_tester_clean_up(struct proxy_tester *tester) {
235235
if (i + 1 < channel_count) {
236236
wrapper.bootstrap->on_shutdown(tester->client_connection, 0, wrapper.bootstrap->user_data);
237237
}
238-
aws_mem_release(tester->alloc, wrapper.bootstrap);
238+
239+
aws_http_client_bootstrap_destroy(wrapper.bootstrap);
239240
}
240241
}
241242

0 commit comments

Comments
 (0)