diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index 610e363612e..548f11c5f2f 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -110,6 +110,7 @@ rcv_data_frame(Http2ConnectionState &cstate, const Http2Frame &frame) stream->increment_data_length(payload_length - pad_length - nbytes); if (frame.header().flags & HTTP2_FLAGS_DATA_END_STREAM) { + stream->recv_end_stream = true; if (!stream->change_state(frame.header().type, frame.header().flags)) { cstate.send_rst_stream_frame(id, HTTP2_ERROR_STREAM_CLOSED); return Http2Error(HTTP2_ERROR_CLASS_NONE); @@ -222,7 +223,7 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame) uint32_t header_block_fragment_length = payload_length; if (frame.header().flags & HTTP2_FLAGS_HEADERS_END_STREAM) { - stream->end_stream = true; + stream->recv_end_stream = true; } // NOTE: Strip padding if exists @@ -966,6 +967,10 @@ Http2ConnectionState::delete_stream(Http2Stream *stream) } } + if (stream->get_state() == HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) { + send_rst_stream_frame(stream->get_id(), HTTP2_ERROR_NO_ERROR); + } + stream_list.remove(stream); stream->initiating_close(); @@ -1035,28 +1040,29 @@ Http2ConnectionState::send_data_frames_depends_on_priority() size_t len = 0; Http2SendADataFrameResult result = send_a_data_frame(stream, len); - if (result != HTTP2_SEND_A_DATA_FRAME_NO_ERROR) { - // When no stream level window left, deactivate node once and wait window_update frame - dependency_tree->deactivate(node, len); - this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_XMIT); - return; + switch (result) { + case HTTP2_SEND_A_DATA_FRAME_NO_ERROR: { + // No response body to send + if (len == 0 && !stream->is_body_done()) { + dependency_tree->deactivate(node, len); + } else { + dependency_tree->update(node, len); + } + break; } - - // No response body to send - if (len == 0 && !stream->is_body_done()) { + case HTTP2_SEND_A_DATA_FRAME_DONE: { dependency_tree->deactivate(node, len); - this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_XMIT); - return; + delete_stream(stream); + break; } - - if (stream->get_state() == HTTP2_STREAM_STATE_CLOSED) { + default: + // When no stream level window left, deactivate node once and wait window_update frame dependency_tree->deactivate(node, len); - delete_stream(stream); - } else { - dependency_tree->update(node, len); + break; } this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_XMIT); + return; } Http2SendADataFrameResult @@ -1115,16 +1121,18 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len stream->update_sent_count(payload_length); - // Change state to 'closed' if its end of DATAs. + // xmit event + SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); + this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &data); + if (flags & HTTP2_FLAGS_DATA_END_STREAM) { DebugHttp2Stream(ua_session, stream->get_id(), "End of DATA frame"); + stream->send_end_stream = true; // Setting to the same state shouldn't be erroneous stream->change_state(data.header().type, data.header().flags); - } - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &data); + return HTTP2_SEND_A_DATA_FRAME_DONE; + } return HTTP2_SEND_A_DATA_FRAME_NO_ERROR; } @@ -1132,13 +1140,15 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len void Http2ConnectionState::send_data_frames(Http2Stream *stream) { - if (stream->get_state() == HTTP2_STREAM_STATE_CLOSED) { + if (stream->get_state() == HTTP2_STREAM_STATE_CLOSED || stream->get_state() == HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL) { return; } size_t len = 0; - while (send_a_data_frame(stream, len) == HTTP2_SEND_A_DATA_FRAME_NO_ERROR) { - if (stream->get_state() == HTTP2_STREAM_STATE_CLOSED) { + while (true) { + Http2SendADataFrameResult result = send_a_data_frame(stream, len); + + if (result == HTTP2_SEND_A_DATA_FRAME_DONE) { // Delete a stream immediately // TODO its should not be deleted for a several time to handling // RST_STREAM and WINDOW_UPDATE. @@ -1146,6 +1156,10 @@ Http2ConnectionState::send_data_frames(Http2Stream *stream) DebugSsn(this->ua_session, "http2_cs", "Shutdown stream %d", stream->get_id()); this->delete_stream(stream); break; + } else if (result == HTTP2_SEND_A_DATA_FRAME_NO_ERROR) { + continue; + } else { + break; } } } @@ -1186,6 +1200,7 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) flags |= HTTP2_FLAGS_HEADERS_END_HEADERS; if (h2_hdr.presence(MIME_PRESENCE_CONTENT_LENGTH) && h2_hdr.get_content_length() == 0) { flags |= HTTP2_FLAGS_HEADERS_END_STREAM; + stream->send_end_stream = true; } } else { payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); diff --git a/proxy/http2/Http2ConnectionState.h b/proxy/http2/Http2ConnectionState.h index 6ed0eaa1110..626567db687 100644 --- a/proxy/http2/Http2ConnectionState.h +++ b/proxy/http2/Http2ConnectionState.h @@ -35,6 +35,7 @@ enum Http2SendADataFrameResult { HTTP2_SEND_A_DATA_FRAME_NO_ERROR = 0, HTTP2_SEND_A_DATA_FRAME_NO_WINDOW = 1, HTTP2_SEND_A_DATA_FRAME_NO_PAYLOAD = 2, + HTTP2_SEND_A_DATA_FRAME_DONE = 3, }; class Http2ConnectionSettings diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index 0cc10e0305f..742a96b0bf8 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -173,14 +173,18 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) switch (_state) { case HTTP2_STREAM_STATE_IDLE: if (type == HTTP2_FRAME_TYPE_HEADERS) { - if (end_stream && flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { + if (recv_end_stream) { _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } else if (send_end_stream) { + _state = HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; } else { _state = HTTP2_STREAM_STATE_OPEN; } } else if (type == HTTP2_FRAME_TYPE_CONTINUATION) { - if (end_stream && flags & HTTP2_FLAGS_CONTINUATION_END_HEADERS) { + if (recv_end_stream) { _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } else if (send_end_stream) { + _state = HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; } else { _state = HTTP2_STREAM_STATE_OPEN; } @@ -194,46 +198,63 @@ Http2Stream::change_state(uint8_t type, uint8_t flags) case HTTP2_STREAM_STATE_OPEN: if (type == HTTP2_FRAME_TYPE_RST_STREAM) { _state = HTTP2_STREAM_STATE_CLOSED; - } else if (type == HTTP2_FRAME_TYPE_DATA && flags & HTTP2_FLAGS_DATA_END_STREAM) { - _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } else if (type == HTTP2_FRAME_TYPE_DATA) { + if (recv_end_stream) { + _state = HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; + } else if (send_end_stream) { + _state = HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; + } else { + // Do not change state + } } else { - // Currently ATS supports only HTTP/2 server features - return false; + // A stream in the "open" state may be used by both peers to send frames of any type. + return true; } break; case HTTP2_STREAM_STATE_RESERVED_LOCAL: // Currently ATS supports only HTTP/2 server features + ink_assert(false); return false; case HTTP2_STREAM_STATE_RESERVED_REMOTE: // XXX Server Push have been supported yet. + ink_assert(false); return false; case HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: - // Currently ATS supports only HTTP/2 server features - return false; + if (type == HTTP2_FRAME_TYPE_RST_STREAM || recv_end_stream) { + _state = HTTP2_STREAM_STATE_CLOSED; + } else { + // Error, set state closed + _state = HTTP2_STREAM_STATE_CLOSED; + return false; + } + break; case HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: - if (type == HTTP2_FRAME_TYPE_RST_STREAM || (type == HTTP2_FRAME_TYPE_HEADERS && flags & HTTP2_FLAGS_HEADERS_END_STREAM) || - (type == HTTP2_FRAME_TYPE_DATA && flags & HTTP2_FLAGS_DATA_END_STREAM)) { + if (type == HTTP2_FRAME_TYPE_RST_STREAM || send_end_stream) { _state = HTTP2_STREAM_STATE_CLOSED; } else if (type == HTTP2_FRAME_TYPE_HEADERS) { // w/o END_STREAM flag // No state change here. Expect a following DATA frame with END_STREAM flag. return true; } else { + // Error, set state closed + _state = HTTP2_STREAM_STATE_CLOSED; return false; } break; case HTTP2_STREAM_STATE_CLOSED: // No state changing - return false; + return true; default: return false; } + Debug("http2_stream", "%s", Http2DebugNames::get_state_name(_state)); + return true; } @@ -740,7 +761,6 @@ Http2Stream::cancel_inactivity_timeout() { set_inactivity_timeout(0); } - void Http2Stream::clear_inactive_timer() { diff --git a/proxy/http2/Http2Stream.h b/proxy/http2/Http2Stream.h index 74abba8b384..4dc68e2f676 100644 --- a/proxy/http2/Http2Stream.h +++ b/proxy/http2/Http2Stream.h @@ -45,7 +45,8 @@ class Http2Stream : public ProxyClientTransaction header_blocks(NULL), header_blocks_length(0), request_header_length(0), - end_stream(false), + recv_end_stream(false), + send_end_stream(false), sent_request_header(false), response_header_done(false), request_sent(false), @@ -190,7 +191,8 @@ class Http2Stream : public ProxyClientTransaction // Padding or other fields) uint32_t request_header_length; // total length of payload (include Padding // and other fields) - bool end_stream; + bool recv_end_stream; + bool send_end_stream; bool sent_request_header; bool response_header_done;