Skip to content

Commit 0d8d673

Browse files
authored
Respond to "Connection: close" header (#157)
Presence of request or response with "Connection: close" header will make that the final stream before the connection shuts down.
1 parent 79ecf04 commit 0d8d673

File tree

16 files changed

+960
-126
lines changed

16 files changed

+960
-126
lines changed

include/aws/http/private/h1_decoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ AWS_HTTP_API void aws_h1_decoder_set_body_headers_ignored(struct aws_h1_decoder
4949
#define AWS_HTTP_TRANSFER_ENCODING_DEPRECATED_COMPRESS (1 << 3)
5050
AWS_HTTP_API int aws_h1_decoder_get_encoding_flags(const struct aws_h1_decoder *decoder);
5151

52-
AWS_HTTP_API size_t aws_h1_decoder_get_content_length(const struct aws_h1_decoder *decoder);
52+
AWS_HTTP_API uint64_t aws_h1_decoder_get_content_length(const struct aws_h1_decoder *decoder);
5353
AWS_HTTP_API bool aws_h1_decoder_get_body_headers_ignored(const struct aws_h1_decoder *decoder);
5454
AWS_HTTP_API enum aws_http_header_block aws_h1_decoder_get_header_block(const struct aws_h1_decoder *decoder);
5555

include/aws/http/private/h1_encoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct aws_h1_encoder_message {
2626
/* Upon creation, the "head" (everything preceding body) is buffered here. */
2727
struct aws_byte_buf outgoing_head_buf;
2828
struct aws_input_stream *body;
29+
bool has_connection_close_header;
2930
};
3031

3132
enum aws_h1_encoder_state {

include/aws/http/private/h1_stream.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ struct aws_h1_stream {
3232
bool is_incoming_message_done;
3333
bool is_incoming_head_done;
3434

35+
/* If true, this is the last stream the connection should process.
36+
* See RFC-7230 Section 6: Connection Management. */
37+
bool is_final_stream;
38+
3539
/* Buffer for incoming data that needs to stick around. */
3640
struct aws_byte_buf incoming_storage_buf;
3741

include/aws/http/private/http_impl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum aws_http_method {
3535
*/
3636
enum aws_http_header_name {
3737
AWS_HTTP_HEADER_UNKNOWN, /* Unrecognized value */
38+
AWS_HTTP_HEADER_CONNECTION,
3839
AWS_HTTP_HEADER_CONTENT_LENGTH,
3940
AWS_HTTP_HEADER_EXPECT,
4041
AWS_HTTP_HEADER_TRANSFER_ENCODING,

include/aws/http/private/strutil.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
#include <aws/http/http.h>
16+
17+
AWS_EXTERN_C_BEGIN
18+
19+
/**
20+
* Read entire cursor as ASCII/UTF-8 unsigned base-10 number.
21+
* Stricter than strtoull(), which allows whitespace and inputs that start with "0x"
22+
*
23+
* Examples:
24+
* "0" -> 0
25+
* "123" -> 123
26+
* "00004" -> 4 // leading zeros ok
27+
*
28+
* Rejects things like:
29+
* "-1" // negative numbers not allowed
30+
* "1,000" // only characters 0-9 allowed
31+
* "" // blank string not allowed
32+
* " 0 " // whitespace not allowed
33+
* "0x0" // hex not allowed
34+
* "FF" // hex not allowed
35+
* "999999999999999999999999999999999999999999" // larger than max u64
36+
*/
37+
AWS_HTTP_API
38+
int aws_strutil_read_unsigned_num(struct aws_byte_cursor cursor, uint64_t *dst);
39+
40+
/**
41+
* Read entire cursor as ASCII/UTF-8 unsigned base-16 number with NO "0x" prefix.
42+
*
43+
* Examples:
44+
* "F" -> 15
45+
* "000000ff" -> 255 // leading zeros ok
46+
* "Ff" -> 255 // mixed case ok
47+
* "123" -> 291
48+
* "FFFFFFFFFFFFFFFF" -> 18446744073709551616 // max u64
49+
*
50+
* Rejects things like:
51+
* "0x0" // 0x prefix not allowed
52+
* "" // blank string not allowed
53+
* " F " // whitespace not allowed
54+
* "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" // larger than max u64
55+
*/
56+
AWS_HTTP_API
57+
int aws_strutil_read_unsigned_hex(struct aws_byte_cursor cursor, uint64_t *dst);
58+
59+
/**
60+
* Return a cursor with all leading and trailing SPACE and TAB characters removed.
61+
* RFC7230 section 3.2.3 Whitespace
62+
* Examples:
63+
* " \t a \t " -> "a"
64+
* "a \t a" -> "a \t a"
65+
*/
66+
AWS_HTTP_API
67+
struct aws_byte_cursor aws_strutil_trim_http_whitespace(struct aws_byte_cursor cursor);
68+
69+
AWS_EXTERN_C_END

source/h1_connection.c

Lines changed: 90 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ struct h1_connection {
164164
bool is_outgoing_stream_task_active;
165165

166166
/* For checking status from outside the event-loop thread. */
167-
bool is_shutting_down;
167+
bool is_open;
168168

169169
/* If non-zero, reason to immediately reject new streams. (ex: closing, switched protocols) */
170170
int new_stream_error_code;
@@ -201,6 +201,8 @@ static void s_stop(
201201
bool schedule_shutdown,
202202
int error_code) {
203203

204+
AWS_ASSERT(stop_reading || stop_writing || schedule_shutdown); /* You are required to stop at least 1 thing */
205+
204206
if (stop_reading) {
205207
AWS_ASSERT(aws_channel_thread_is_callers_thread(connection->base.channel_slot->channel));
206208
connection->thread_data.is_reading_stopped = true;
@@ -213,12 +215,10 @@ static void s_stop(
213215
{ /* BEGIN CRITICAL SECTION */
214216
s_h1_connection_lock_synced_data(connection);
215217

216-
if (connection->synced_data.is_shutting_down) {
217-
schedule_shutdown = false;
218-
} else {
219-
connection->synced_data.is_shutting_down = true;
220-
connection->synced_data.new_stream_error_code = AWS_ERROR_HTTP_CONNECTION_CLOSED;
221-
}
218+
/* Even if we're not scheduling shutdown just yet (ex: sent final request but waiting to read final response)
219+
* we don't consider the connection "open" anymore so user can't create more streams */
220+
connection->synced_data.new_stream_error_code = AWS_ERROR_HTTP_CONNECTION_CLOSED;
221+
connection->synced_data.is_open = false;
222222

223223
s_h1_connection_unlock_synced_data(connection);
224224
} /* END CRITICAL SECTION */
@@ -264,15 +264,15 @@ static void s_connection_close(struct aws_http_connection *connection_base) {
264264

265265
static bool s_connection_is_open(const struct aws_http_connection *connection_base) {
266266
struct h1_connection *connection = AWS_CONTAINER_OF(connection_base, struct h1_connection, base);
267-
bool is_shutting_down;
267+
bool is_open;
268268

269269
{ /* BEGIN CRITICAL SECTION */
270270
s_h1_connection_lock_synced_data(connection);
271-
is_shutting_down = connection->synced_data.is_shutting_down;
271+
is_open = connection->synced_data.is_open;
272272
s_h1_connection_unlock_synced_data(connection);
273273
} /* END CRITICAL SECTION */
274274

275-
return !is_shutting_down;
275+
return is_open;
276276
}
277277

278278
static int s_stream_send_response(struct aws_http_stream *stream, struct aws_http_message *response) {
@@ -303,6 +303,10 @@ static int s_stream_send_response(struct aws_http_stream *stream, struct aws_htt
303303
} else {
304304
h1_stream->synced_data.has_outgoing_response = true;
305305
h1_stream->encoder_message = encoder_message;
306+
if (encoder_message.has_connection_close_header) {
307+
h1_stream->is_final_stream = true;
308+
}
309+
306310
if (!connection->synced_data.is_outgoing_stream_task_active) {
307311
connection->synced_data.is_outgoing_stream_task_active = true;
308312
should_schedule_task = true;
@@ -587,17 +591,28 @@ static void s_stream_complete(struct aws_h1_stream *stream, int error_code) {
587591
AWS_BYTE_CURSOR_PRI(stream->base.server_data->request_method_str));
588592
}
589593

594+
/* If connection must shut down, do it BEFORE invoking stream-complete callback.
595+
* That way, if aws_http_connection_is_open() is called from stream-complete callback, it returns false. */
596+
if (!original_error_code) {
597+
if (error_code) {
598+
s_shutdown_due_to_error(connection, error_code);
599+
600+
} else if (stream->is_final_stream) {
601+
AWS_LOGF_TRACE(
602+
AWS_LS_HTTP_CONNECTION,
603+
"id=%p: Closing connection due to completion of final stream.",
604+
(void *)&connection->base);
605+
606+
s_connection_close(&connection->base);
607+
}
608+
}
609+
590610
/* Invoke callback and clean up stream. */
591611
if (stream->base.on_complete) {
592612
stream->base.on_complete(&stream->base, error_code, stream->base.user_data);
593613
}
594614

595615
aws_http_stream_release(&stream->base);
596-
597-
/* If this function started out ok, but ended badly, shut down the connection. */
598-
if (!original_error_code && error_code) {
599-
s_shutdown_due_to_error(connection, error_code);
600-
}
601616
}
602617

603618
/**
@@ -606,7 +621,9 @@ static void s_stream_complete(struct aws_h1_stream *stream, int error_code) {
606621
static void s_client_update_incoming_stream_ptr(struct h1_connection *connection) {
607622
struct aws_linked_list *list = &connection->thread_data.stream_list;
608623
struct aws_h1_stream *desired;
609-
if (aws_linked_list_empty(list)) {
624+
if (connection->thread_data.is_reading_stopped) {
625+
desired = NULL;
626+
} else if (aws_linked_list_empty(list)) {
610627
desired = NULL;
611628
} else {
612629
desired = AWS_CONTAINER_OF(aws_linked_list_begin(list), struct aws_h1_stream, node);
@@ -635,11 +652,28 @@ static void s_client_update_incoming_stream_ptr(struct h1_connection *connection
635652
static struct aws_h1_stream *s_update_outgoing_stream_ptr(struct h1_connection *connection) {
636653
struct aws_h1_stream *current = connection->thread_data.outgoing_stream;
637654
struct aws_h1_stream *prev = current;
655+
struct aws_linked_list_node *next_node = NULL;
638656
int err;
639657

640658
/* If current stream is done sending data... */
641659
if (current && !aws_h1_encoder_is_message_in_progress(&connection->thread_data.encoder)) {
642-
struct aws_linked_list_node *next_node = aws_linked_list_next(&current->node);
660+
next_node = aws_linked_list_next(&current->node);
661+
662+
/* RFC-7230 section 6.6: Tear-down.
663+
* If this was the final stream, don't allows any further streams to be sent */
664+
if (current->is_final_stream) {
665+
AWS_LOGF_TRACE(
666+
AWS_LS_HTTP_CONNECTION,
667+
"id=%p: Done sending final stream, no further streams will be sent.",
668+
(void *)&connection->base);
669+
670+
s_stop(
671+
connection,
672+
false /*stop_reading*/,
673+
true /*stop_writing*/,
674+
false /*schedule_shutdown*/,
675+
AWS_ERROR_SUCCESS);
676+
}
643677

644678
/* If it's also done receiving data, then it's complete! */
645679
if (current->is_incoming_message_done) {
@@ -650,19 +684,18 @@ static struct aws_h1_stream *s_update_outgoing_stream_ptr(struct h1_connection *
650684
s_stream_complete(current, AWS_ERROR_SUCCESS);
651685
}
652686

653-
/* Iterate current stream to the next item in stream_list. */
654-
if (next_node == aws_linked_list_end(&connection->thread_data.stream_list)) {
655-
current = NULL;
656-
} else {
657-
current = AWS_CONTAINER_OF(next_node, struct aws_h1_stream, node);
658-
}
687+
current = NULL;
659688
}
660689

661-
/* If current stream is NULL,
662-
* Client side: look in synced_data.pending_stream_list for more work
663-
* Server side: look in thread_data.waiting_stream_list for more work */
664-
if (!current) {
665-
if (connection->base.server_data) {
690+
/* If current stream is NULL, do one of:
691+
* - Look for next item in stream_list.
692+
* - Client side: look in synced_data.pending_stream_list for more work
693+
* - Server side: look in thread_data.waiting_stream_list for more work */
694+
if (!current && !connection->thread_data.is_writing_stopped) {
695+
if (next_node && next_node != aws_linked_list_end(&connection->thread_data.stream_list)) {
696+
current = AWS_CONTAINER_OF(next_node, struct aws_h1_stream, node);
697+
698+
} else if (connection->base.server_data) {
666699
/* server side should check the stream already has response or not
667700
* Require a lock to prevent the user makes any change to the stream state */
668701
/* BEGIN CRITICAL SECTION */
@@ -955,6 +988,20 @@ static int s_decoder_on_header(const struct aws_http_decoded_header *header, voi
955988

956989
enum aws_http_header_block header_block =
957990
aws_h1_decoder_get_header_block(connection->thread_data.incoming_stream_decoder);
991+
992+
/* RFC-7230 section 6.1.
993+
* "Connection: close" header signals that a connection will not persist after the current request/response */
994+
if (header->name == AWS_HTTP_HEADER_CONNECTION) {
995+
if (aws_byte_cursor_eq_c_str(&header->value_data, "close")) {
996+
AWS_LOGF_TRACE(
997+
AWS_LS_HTTP_STREAM,
998+
"id=%p: Received 'Connection: close' header. This will be the final stream on this connection.",
999+
(void *)&incoming_stream->base);
1000+
1001+
incoming_stream->is_final_stream = true;
1002+
}
1003+
}
1004+
9581005
if (incoming_stream->base.on_incoming_headers) {
9591006
struct aws_http_header deliver = {
9601007
.name = header->name_data,
@@ -1074,8 +1121,21 @@ static int s_decoder_on_done(void *user_data) {
10741121
return AWS_OP_SUCCESS;
10751122
}
10761123

1077-
/* If it is a main header block, the incoming stream is finished decoding and we will update it if needed */
1124+
/* Otherwise the incoming stream is finished decoding and we will update it if needed */
10781125
incoming_stream->is_incoming_message_done = true;
1126+
1127+
/* RFC-7230 section 6.6
1128+
* After reading the final message, the connection must not read any more */
1129+
if (incoming_stream->is_final_stream) {
1130+
AWS_LOGF_TRACE(
1131+
AWS_LS_HTTP_CONNECTION,
1132+
"id=%p: Done reading final stream, no further streams will be read.",
1133+
(void *)&connection->base);
1134+
1135+
s_stop(
1136+
connection, true /*stop_reading*/, false /*stop_writing*/, false /*schedule_shutdown*/, AWS_ERROR_SUCCESS);
1137+
}
1138+
10791139
if (connection->base.server_data) {
10801140
/* Server side */
10811141
aws_http_on_incoming_request_done_fn *on_request_done = incoming_stream->base.server_data->on_request_done;
@@ -1149,6 +1209,7 @@ static struct h1_connection *s_connection_new(struct aws_allocator *alloc, size_
11491209
}
11501210

11511211
aws_linked_list_init(&connection->synced_data.pending_stream_list);
1212+
connection->synced_data.is_open = true;
11521213

11531214
struct aws_h1_decoder_params options = {
11541215
.alloc = alloc,

0 commit comments

Comments
 (0)