@@ -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
265265static 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
278278static 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) {
606621static 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
635652static 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