| /** @file | |
| Http2Stream.cc | |
| @section license License | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include "proxy/http2/Http2Stream.h" | |
| #include "proxy/http2/HTTP2.h" | |
| #include "proxy/http2/Http2ClientSession.h" | |
| #include "proxy/http2/Http2ServerSession.h" | |
| #include "proxy/http/HttpDebugNames.h" | |
| #include "proxy/http/HttpSM.h" | |
| #include "tscore/Diags.h" | |
| #include "tscore/HTTPVersion.h" | |
| #include "tscore/ink_assert.h" | |
| #include "tsutil/DbgCtl.h" | |
| #include <numeric> | |
| #define REMEMBER(e, r) \ | |
| { \ | |
| this->_history.push_back(MakeSourceLocation(), e, r); \ | |
| } | |
| namespace | |
| { | |
| DbgCtl dbg_ctl_http2_stream{"http2_stream"}; | |
| } // end anonymous namespace | |
| #define Http2StreamDebug(fmt, ...) \ | |
| SsnDbg(_proxy_ssn, dbg_ctl_http2_stream, "[%" PRId64 "] [%u] " fmt, _proxy_ssn->connection_id(), this->get_id(), ##__VA_ARGS__); | |
| ClassAllocator<Http2Stream> http2StreamAllocator("http2StreamAllocator"); | |
| Http2Stream::Http2Stream(ProxySession *session, Http2StreamId sid, ssize_t initial_peer_rwnd, ssize_t initial_local_rwnd, | |
| bool registered_stream) | |
| : super(session), _id(sid), _registered_stream(registered_stream), _peer_rwnd(initial_peer_rwnd), _local_rwnd(initial_local_rwnd) | |
| { | |
| SET_HANDLER(&Http2Stream::main_event_handler); | |
| this->mark_milestone(Http2StreamMilestone::OPEN); | |
| this->_sm = nullptr; | |
| this->_thread = this_ethread(); | |
| this->_state = Http2StreamState::HTTP2_STREAM_STATE_IDLE; | |
| auto const *proxy_session = get_proxy_ssn(); | |
| ink_assert(proxy_session != nullptr); | |
| auto const *h2_session = dynamic_cast<Http2CommonSession const *>(proxy_session); | |
| ink_assert(h2_session != nullptr); | |
| this->_is_outbound = h2_session->is_outbound(); | |
| this->_reader = this->_receive_buffer.alloc_reader(); | |
| if (this->is_outbound_connection()) { // Flip the sense of the expected headers. Fix naming later | |
| _receive_header.create(HTTPType::RESPONSE); | |
| _send_header.create(HTTPType::REQUEST, HTTP_2_0); | |
| } else { | |
| _receive_header.create(HTTPType::REQUEST); | |
| _send_header.create(HTTPType::RESPONSE, HTTP_2_0); | |
| } | |
| http_parser_init(&http_parser); | |
| } | |
| Http2Stream::~Http2Stream() | |
| { | |
| REMEMBER(NO_EVENT, this->reentrancy_count); | |
| Http2StreamDebug("Destroy stream, sent %" PRIu64 " bytes, registered: %s", this->bytes_sent, | |
| (_registered_stream ? "true" : "false")); | |
| // In the case of a temporary stream used to parse the header to keep the HPACK | |
| // up to date, there may not be a mutex. Nothing was set up, so nothing to | |
| // clean up in the destructor | |
| if (this->mutex == nullptr) { | |
| return; | |
| } | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| // Clean up after yourself if this was an EOS | |
| ink_release_assert(this->closed); | |
| ink_release_assert(reentrancy_count == 0); | |
| uint64_t cid = 0; | |
| if (_registered_stream) { | |
| // Safe to initiate SSN_CLOSE if this is the last stream | |
| if (_proxy_ssn) { | |
| cid = _proxy_ssn->connection_id(); | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| Http2ConnectionState &connection_state = this->get_connection_state(); | |
| // Make sure the stream is removed from the stream list and priority tree | |
| // In many cases, this has been called earlier, so this call is a no-op | |
| connection_state.delete_stream(this); | |
| connection_state.decrement_peer_stream_count(); | |
| // Update session's stream counts, so it accurately goes into keep-alive state | |
| connection_state.release_stream(); | |
| // Do not access `_proxy_ssn` in below. It might be freed by `release_stream`. | |
| } | |
| } // Otherwise, not registered with the connection_state (i.e. a temporary stream used for HPACK header processing) | |
| // Clean up the write VIO in case of inactivity timeout | |
| this->do_io_write(nullptr, 0, nullptr); | |
| this->_milestones.mark(Http2StreamMilestone::CLOSE); | |
| ink_hrtime total_time = this->_milestones.elapsed(Http2StreamMilestone::OPEN, Http2StreamMilestone::CLOSE); | |
| Metrics::Counter::increment(http2_rsb.total_transactions_time, total_time); | |
| // Slow Log | |
| if (Http2::stream_slow_log_threshold != 0 && ink_hrtime_from_msec(Http2::stream_slow_log_threshold) < total_time) { | |
| Error("[%" PRIu64 "] [%" PRIu32 "] [%" PRId64 "] Slow H2 Stream: " | |
| "open: %" PRIu64 " " | |
| "dec_hdrs: %.3f " | |
| "txn: %.3f " | |
| "enc_hdrs: %.3f " | |
| "tx_hdrs: %.3f " | |
| "tx_data: %.3f " | |
| "close: %.3f", | |
| cid, static_cast<uint32_t>(this->_id), this->_http_sm_id, | |
| ink_hrtime_to_msec(this->_milestones[Http2StreamMilestone::OPEN]), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::START_DECODE_HEADERS), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::START_TXN), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::START_ENCODE_HEADERS), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::START_TX_HEADERS_FRAMES), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::START_TX_DATA_FRAMES), | |
| this->_milestones.difference_sec(Http2StreamMilestone::OPEN, Http2StreamMilestone::CLOSE)); | |
| } | |
| _receive_header.destroy(); | |
| _send_header.destroy(); | |
| // Drop references to all buffer data | |
| this->_receive_buffer.clear(); | |
| // Free the mutexes in the VIO | |
| read_vio.mutex.clear(); | |
| write_vio.mutex.clear(); | |
| ats_free(header_blocks); | |
| _clear_timers(); | |
| clear_io_events(); | |
| http_parser_clear(&http_parser); | |
| } | |
| int | |
| Http2Stream::main_event_handler(int event, void *edata) | |
| { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| REMEMBER(event, this->reentrancy_count); | |
| if (!this->_switch_thread_if_not_on_right_thread(event, edata)) { | |
| // Not on the right thread | |
| return 0; | |
| } | |
| ink_release_assert(this->_thread == this_ethread()); | |
| Event *e = static_cast<Event *>(edata); | |
| reentrancy_count++; | |
| if (e == _read_vio_event) { | |
| _read_vio_event = nullptr; | |
| // A final event retried after a mutex miss must survive a throttle that started meanwhile. | |
| if (_is_final_read_event(e->callback_event)) { | |
| this->signal_final_read_event(e->callback_event); | |
| } else { | |
| this->signal_read_event(e->callback_event); | |
| } | |
| reentrancy_count--; | |
| return 0; | |
| } else if (e == _write_vio_event) { | |
| _write_vio_event = nullptr; | |
| this->signal_write_event(e->callback_event); | |
| reentrancy_count--; | |
| return 0; | |
| } else if (e == cross_thread_event) { | |
| cross_thread_event = nullptr; | |
| } else if (e == read_event) { | |
| read_event = nullptr; | |
| } else if (e == write_event) { | |
| write_event = nullptr; | |
| } | |
| switch (event) { | |
| case VC_EVENT_ACTIVE_TIMEOUT: | |
| case VC_EVENT_INACTIVITY_TIMEOUT: | |
| if (_sm == nullptr && closed != true) { | |
| // TIMEOUT without HttpSM - assuming incomplete header timeout | |
| Http2StreamDebug("timeout event=%d", event); | |
| ip_port_text_buffer ipb; | |
| const char *remote_ip = ats_ip_ntop(this->_proxy_ssn->get_remote_addr(), ipb, sizeof(ipb)); | |
| Error("HTTP/2 stream error timeout remote_ip=%s session_id=%" PRId64 " stream_id=%u event=%d", remote_ip, | |
| this->_proxy_ssn->connection_id(), this->_id, event); | |
| // Close stream | |
| do_io_close(); | |
| terminate_stream = true; | |
| // Close connection because this stream doesn't read HEADERS/CONTINUATION frames anymore that makes HPACK dynamic table | |
| // out of sync. | |
| Http2ConnectionState &connection_state = get_connection_state(); | |
| { | |
| SCOPED_MUTEX_LOCK(lock, connection_state.mutex, this_ethread()); | |
| Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_CONNECTION, Http2ErrorCode::HTTP2_ERROR_COMPRESSION_ERROR, | |
| "stream timeout"); | |
| connection_state.handleEvent(HTTP2_SESSION_EVENT_ERROR, &error); | |
| } | |
| } else if (_sm && read_vio.ntodo() > 0) { | |
| this->signal_read_event(event); | |
| } else if (_sm && write_vio.ntodo() > 0) { | |
| this->signal_write_event(event); | |
| } else { | |
| Warning("HTTP/2 unknown case of %d event - session_id=%" PRId64 " stream_id=%u", event, this->_proxy_ssn->connection_id(), | |
| this->_id); | |
| } | |
| break; | |
| case VC_EVENT_WRITE_READY: | |
| case VC_EVENT_WRITE_COMPLETE: | |
| _timeout.update_inactivity(); | |
| if (e->cookie == &write_vio) { | |
| if (write_vio.mutex && write_vio.cont && this->_sm) { | |
| this->signal_write_event(event); | |
| } | |
| } else { | |
| this->update_write_request(true); | |
| } | |
| break; | |
| case VC_EVENT_READ_COMPLETE: | |
| if (!this->_read_event_paused) { | |
| read_vio.nbytes = read_vio.ndone; | |
| } | |
| /* fall through */ | |
| case VC_EVENT_READ_READY: | |
| _timeout.update_inactivity(); | |
| if (e->cookie == &read_vio) { | |
| if (read_vio.mutex && read_vio.cont && this->_sm) { | |
| // A completion scheduled by reenable() must survive the consumer throttling again before it is dispatched. | |
| if (_is_final_read_event(event)) { | |
| this->signal_final_read_event(event); | |
| } else { | |
| this->signal_read_event(event); | |
| } | |
| } | |
| } else { | |
| this->update_read_request(true); | |
| } | |
| break; | |
| case VC_EVENT_EOS: | |
| if (e->cookie == &read_vio) { | |
| SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); | |
| if (read_vio.cont) { | |
| read_vio.cont->handleEvent(VC_EVENT_EOS, &read_vio); | |
| } | |
| } else if (e->cookie == &write_vio) { | |
| SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); | |
| if (write_vio.cont) { | |
| write_vio.cont->handleEvent(VC_EVENT_EOS, &write_vio); | |
| } | |
| } | |
| break; | |
| } | |
| reentrancy_count--; | |
| // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack | |
| terminate_if_possible(); | |
| return 0; | |
| } | |
| Http2ErrorCode | |
| Http2Stream::decode_header_blocks(HpackHandle &hpack_handle, uint32_t maximum_table_size, uint32_t header_field_max_size) | |
| { | |
| Http2ErrorCode error = http2_decode_header_blocks(&_receive_header, (const uint8_t *)header_blocks, header_blocks_length, nullptr, | |
| hpack_handle, _trailing_header_is_possible, maximum_table_size, | |
| header_field_max_size, this->is_outbound_connection()); | |
| if (error != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) { | |
| Http2StreamDebug("Error decoding header blocks: %u", static_cast<uint32_t>(error)); | |
| } | |
| return error; | |
| } | |
| void | |
| Http2Stream::send_headers(Http2ConnectionState & /* cstate ATS_UNUSED */) | |
| { | |
| if (closed) { | |
| return; | |
| } | |
| REMEMBER(NO_EVENT, this->reentrancy_count); | |
| // Convert header to HTTP/1.1 format. Trailing headers need no conversion | |
| // because they, by definition, do not contain pseudo headers. | |
| if (this->trailing_header_is_possible()) { | |
| Http2StreamDebug("trailing header: Skipping send_headers initialization."); | |
| } else { | |
| if (http2_convert_header_from_2_to_1_1(&_receive_header) == ParseResult::ERROR) { | |
| Http2StreamDebug("Error converting HTTP/2 headers to HTTP/1.1."); | |
| if (_receive_header.type_get() == HTTPType::REQUEST) { | |
| // There's no way to cause Bad Request directly at this time. | |
| // Set an invalid method so it causes an error later. | |
| _receive_header.method_set(std::string_view{"\xffVOID", 1}); | |
| } | |
| } | |
| if (_receive_header.type_get() == HTTPType::REQUEST) { | |
| // Check whether the request uses CONNECT method | |
| auto method{_receive_header.method_get()}; | |
| if (method == static_cast<std::string_view>(HTTP_METHOD_CONNECT)) { | |
| this->_is_tunneling = true; | |
| } | |
| } | |
| ink_release_assert(this->_sm != nullptr); | |
| this->_http_sm_id = this->_sm->sm_id; | |
| } | |
| // Write header to a buffer. Borrowing logic from HttpSM::write_header_into_buffer. | |
| // Seems like a function like this ought to be in HTTPHdr directly | |
| int bufindex; | |
| int dumpoffset = 0; | |
| // The name dumpoffset is used here for parity with | |
| // HttpSM::write_header_into_buffer, but create an alias for clarity in the | |
| // use of this variable below this loop. | |
| int &num_header_bytes = dumpoffset; | |
| int done, tmp; | |
| do { | |
| bufindex = 0; | |
| tmp = dumpoffset; | |
| IOBufferBlock *block = this->_receive_buffer.get_current_block(); | |
| if (!block) { | |
| this->_receive_buffer.add_block(); | |
| block = this->_receive_buffer.get_current_block(); | |
| } | |
| done = _receive_header.print(block->end(), block->write_avail(), &bufindex, &tmp); | |
| dumpoffset += bufindex; | |
| this->_receive_buffer.fill(bufindex); | |
| if (!done) { | |
| this->_receive_buffer.add_block(); | |
| } | |
| } while (!done); | |
| if (num_header_bytes == 0) { | |
| // No data to signal read event | |
| return; | |
| } | |
| if (!this->receive_end_stream && this->_receive_header.type_get() == HTTPType::REQUEST) { | |
| this->has_body = true; | |
| } | |
| // Is the _sm ready to process the header? | |
| if (this->read_vio.nbytes > 0) { | |
| if (this->receive_end_stream) { | |
| // These headers may be standard or trailer headers: | |
| // | |
| // * If they are standard, then there is no body (note again that the | |
| // END_STREAM flag was sent with them), data_length will be 0, and | |
| // num_header_bytes will simply be the length of the headers. | |
| // | |
| // * If they are trailers, then the tunnel behind the SM was set up after | |
| // the original headers were sent, and thus nbytes should not include the | |
| // size of the original standard headers. Rather, for trailers, nbytes | |
| // only needs to include the body length (i.e., DATA frame payload | |
| // length), and the length of these current trailer headers calculated in | |
| // num_header_bytes. | |
| this->read_vio.nbytes = this->data_length + num_header_bytes; | |
| Http2StreamDebug("nbytes: %" PRId64 ", ndone: %" PRId64 ", num_header_bytes: %d, data_length: %" PRId64, | |
| this->read_vio.nbytes, this->read_vio.ndone, num_header_bytes, this->data_length); | |
| if (this->is_outbound_connection()) { | |
| // This is a response header. | |
| // We don't set ndone because the VC_EVENT_EOS will | |
| // first flush the remaining content to consumers, | |
| // after which the TUNNEL_EVENT_DONE will be fired | |
| // and the header handler will be set up. | |
| // The header handler will read the buffer, and not | |
| // get its content from the VIO | |
| // This can break if the implementation | |
| // changes. | |
| // | |
| // If the consumer has throttled the read VIO, the EOS waits for | |
| // reenable(), so nothing is flushed until the consumer drains. | |
| this->signal_final_read_event(VC_EVENT_EOS); | |
| } else { | |
| // Request headers. | |
| this->read_vio.ndone = this->read_vio.nbytes; | |
| this->signal_final_read_event(VC_EVENT_READ_COMPLETE); | |
| } | |
| } else { | |
| // End of header but not end of stream, must have some body frames coming | |
| this->signal_read_event(VC_EVENT_READ_READY); | |
| } | |
| } | |
| } | |
| bool | |
| Http2Stream::change_state(uint8_t type, uint8_t flags) | |
| { | |
| auto const initial_state = _state; | |
| switch (_state) { | |
| case Http2StreamState::HTTP2_STREAM_STATE_IDLE: | |
| if (type == HTTP2_FRAME_TYPE_HEADERS) { | |
| if (receive_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; | |
| } else if (send_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; | |
| } else { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_OPEN; | |
| } | |
| } else if (type == HTTP2_FRAME_TYPE_CONTINUATION) { | |
| if (receive_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; | |
| } else if (send_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; | |
| } else { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_OPEN; | |
| } | |
| } else if (type == HTTP2_FRAME_TYPE_PUSH_PROMISE) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_RESERVED_LOCAL; | |
| } else if (type == HTTP2_FRAME_TYPE_RST_STREAM) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| } else { | |
| return false; | |
| } | |
| break; | |
| case Http2StreamState::HTTP2_STREAM_STATE_OPEN: | |
| if (type == HTTP2_FRAME_TYPE_RST_STREAM) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| } else if (type == HTTP2_FRAME_TYPE_HEADERS || type == HTTP2_FRAME_TYPE_DATA) { | |
| if (receive_end_stream) { | |
| if (send_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| } else { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; | |
| } | |
| } else if (send_end_stream) { | |
| if (receive_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| } else { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL; | |
| } | |
| } else { | |
| // Do not change state | |
| } | |
| } else { | |
| // A stream in the "open" state may be used by both peers to send frames of any type. | |
| return true; | |
| } | |
| break; | |
| case Http2StreamState::HTTP2_STREAM_STATE_RESERVED_LOCAL: | |
| if (type == HTTP2_FRAME_TYPE_HEADERS) { | |
| if (flags & HTTP2_FLAGS_HEADERS_END_HEADERS) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; | |
| } | |
| } else if (type == HTTP2_FRAME_TYPE_CONTINUATION) { | |
| if (flags & HTTP2_FLAGS_CONTINUATION_END_HEADERS) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE; | |
| } | |
| } else { | |
| return false; | |
| } | |
| break; | |
| case Http2StreamState::HTTP2_STREAM_STATE_RESERVED_REMOTE: | |
| // Currently ATS supports only HTTP/2 server features | |
| return false; | |
| case Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: | |
| if (type == HTTP2_FRAME_TYPE_RST_STREAM || receive_end_stream) { | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| } | |
| break; | |
| case Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: | |
| if (type == HTTP2_FRAME_TYPE_RST_STREAM || send_end_stream) { | |
| _state = Http2StreamState::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 if (type == HTTP2_FRAME_TYPE_CONTINUATION) { // w/o END_STREAM flag | |
| // No state change here. Expect a following DATA frame with END_STREAM flag. | |
| return true; | |
| } | |
| break; | |
| case Http2StreamState::HTTP2_STREAM_STATE_CLOSED: | |
| // No state changing | |
| return true; | |
| default: | |
| return false; | |
| } | |
| Http2StreamDebug("%s -> %s", Http2DebugNames::get_state_name(initial_state), Http2DebugNames::get_state_name(_state)); | |
| return true; | |
| } | |
| VIO * | |
| Http2Stream::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) | |
| { | |
| // DATA can arrive while HttpSM has installed a zero-byte read to pause TXN_START. | |
| // Keep that byte count when the state machine resumes reading the buffered request. | |
| int64_t const gated_ndone = (this->_read_event_paused && nbytes != 0) ? read_vio.ndone : 0; | |
| if (buf) { | |
| read_vio.set_writer(buf); | |
| } else { | |
| read_vio.buffer.clear(); | |
| } | |
| read_vio.mutex = c ? c->mutex : this->mutex; | |
| read_vio.cont = c; | |
| read_vio.nbytes = nbytes; | |
| read_vio.ndone = gated_ndone; | |
| read_vio.vc_server = this; | |
| read_vio.op = VIO::READ; | |
| this->_read_event_paused = nbytes == 0; | |
| this->_deferred_read_event = VC_EVENT_NONE; | |
| if (this->_read_event_paused) { | |
| if (this->_read_vio_event) { | |
| this->_read_vio_event->cancel(); | |
| this->_read_vio_event = nullptr; | |
| } | |
| if (this->read_event) { | |
| this->read_event->cancel(); | |
| this->read_event = nullptr; | |
| } | |
| } | |
| // TODO: re-enable read_vio | |
| return &read_vio; | |
| } | |
| VIO * | |
| Http2Stream::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *abuffer, bool /* owner ATS_UNUSED */) | |
| { | |
| if (abuffer) { | |
| write_vio.set_reader(abuffer); | |
| } else { | |
| write_vio.buffer.clear(); | |
| } | |
| write_vio.mutex = c ? c->mutex : this->mutex; | |
| write_vio.cont = c; | |
| write_vio.nbytes = nbytes; | |
| write_vio.ndone = 0; | |
| write_vio.vc_server = this; | |
| write_vio.op = VIO::WRITE; | |
| _send_reader = abuffer; | |
| if (c != nullptr && nbytes > 0 && this->is_state_writeable()) { | |
| update_write_request(false); | |
| } else if (!this->is_state_writeable()) { | |
| // Cannot start a write on a closed stream | |
| return nullptr; | |
| } | |
| return &write_vio; | |
| } | |
| // Initiated from SM | |
| void | |
| Http2Stream::do_io_close(int /* flags */) | |
| { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| if (!closed) { | |
| REMEMBER(NO_EVENT, this->reentrancy_count); | |
| Http2StreamDebug("do_io_close"); | |
| // Let the other end know we are going away. | |
| // We only need to do this for the client side since we only need to pass through RST_STREAM | |
| // from the server. If a client sends a RST_STREAM, we need to keep the server side alive so | |
| // the background fill can function as intended. | |
| // | |
| // In the half-closed(local) state, server can close stream actively by sending RST_STREAM frame | |
| if (!this->is_outbound_connection() && | |
| (this->is_state_writeable() || _state == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL)) { | |
| this->get_connection_state().send_rst_stream_frame(_id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); | |
| } | |
| // When we get here, the SM has initiated the shutdown. Either it received a WRITE_COMPLETE, or it is shutting down. Any | |
| // remaining IO operations back to client should be abandoned. The SM-side buffers backing these operations will be deleted | |
| // by the time this is called from transaction_done. | |
| closed = true; | |
| // Adjust state, so we don't process any more data | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| _clear_timers(); | |
| clear_io_events(); | |
| // Otherwise, Wait until transaction_done is called from HttpSM to signal that the TXN_CLOSE hook has been executed | |
| } | |
| } | |
| /* | |
| * HttpSM has called TXN_close hooks. | |
| */ | |
| void | |
| Http2Stream::transaction_done() | |
| { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| super::transaction_done(); | |
| if (!closed && _state == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { | |
| do_io_close(); // Make sure we've been closed. If we didn't close the _proxy_ssn session better still be open | |
| } | |
| Http2ConnectionState &state = this->get_connection_state(); | |
| ink_release_assert(closed || !state.is_state_closed()); | |
| // HttpSM is gone, no more reading or writing | |
| _sm = nullptr; | |
| read_vio.disable(); | |
| write_vio.disable(); | |
| if (closed) { | |
| // Safe to initiate SSN_CLOSE if this is the last stream | |
| ink_assert(cross_thread_event == nullptr); | |
| // Schedule the destroy to occur after we unwind here. IF we call directly, may delete with reference on the stack. | |
| terminate_stream = true; | |
| terminate_if_possible(); | |
| } | |
| } | |
| void | |
| Http2Stream::terminate_if_possible() | |
| { | |
| // if (terminate_stream && reentrancy_count == 0) { | |
| if (reentrancy_count == 0 && closed && terminate_stream) { | |
| REMEMBER(NO_EVENT, this->reentrancy_count); | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| THREAD_FREE(this, http2StreamAllocator, this_ethread()); | |
| } | |
| } | |
| // Initiated from the Http2 side | |
| void | |
| Http2Stream::initiating_close() | |
| { | |
| if (!closed) { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| REMEMBER(NO_EVENT, this->reentrancy_count); | |
| Http2StreamDebug("initiating_close client_window=%zd session_window=%zd", _peer_rwnd, | |
| this->get_connection_state().get_peer_rwnd()); | |
| if (!this->is_outbound_connection() && this->is_state_writeable()) { // Let the other end know we are going away | |
| this->get_connection_state().send_rst_stream_frame(_id, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); | |
| } | |
| // Set the state of the connection to closed | |
| // TODO - these states should be combined | |
| closed = true; | |
| _state = Http2StreamState::HTTP2_STREAM_STATE_CLOSED; | |
| // leaving the reference to the SM, so we can detach from the SM when we actually destroy | |
| // _sm = NULL; | |
| // Leaving reference to client session as well, so we can signal once the | |
| // TXN_CLOSE has been sent | |
| // _proxy_ssn = NULL; | |
| _clear_timers(); | |
| clear_io_events(); | |
| // This should result in do_io_close or release being called. That will schedule the final | |
| // kill yourself signal | |
| // We are sending signals rather than calling the handlers directly to avoid the case where | |
| // the HttpTunnel handler causes the HttpSM to be deleted on the stack. | |
| bool sent_write_complete = false; | |
| if (_sm) { | |
| // Push out any last IO events | |
| // First look for active write or read | |
| if (write_vio.cont && write_vio.nbytes > 0 && write_vio.ndone == write_vio.nbytes && | |
| (!is_outbound_connection() || get_state() == Http2StreamState::HTTP2_STREAM_STATE_OPEN)) { | |
| SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); | |
| Http2StreamDebug("Send tracked event VC_EVENT_WRITE_COMPLETE on write_vio. sm_id: %" PRId64, _sm->sm_id); | |
| write_event = send_tracked_event(write_event, VC_EVENT_WRITE_COMPLETE, &write_vio); | |
| sent_write_complete = true; | |
| } | |
| if (!sent_write_complete) { | |
| if (write_vio.cont && write_vio.get_writer() && | |
| (!is_outbound_connection() || get_state() == Http2StreamState::HTTP2_STREAM_STATE_OPEN || | |
| get_state() == Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL)) { | |
| SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); | |
| Http2StreamDebug("Send tracked event VC_EVENT_EOS on write_vio. sm_id: %" PRId64, _sm->sm_id); | |
| write_event = send_tracked_event(write_event, VC_EVENT_EOS, &write_vio); | |
| } else if (read_vio.cont && read_vio.get_writer()) { | |
| SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); | |
| Http2StreamDebug("Send tracked event VC_EVENT_EOS on read_vio. sm_id: %" PRId64, _sm->sm_id); | |
| read_event = send_tracked_event(read_event, VC_EVENT_EOS, &read_vio); | |
| } else { | |
| Http2StreamDebug("send EOS to SM"); | |
| // Just send EOS to the _sm | |
| _sm->handleEvent(VC_EVENT_EOS, nullptr); | |
| } | |
| } | |
| } else { | |
| Http2StreamDebug("No SM to signal"); | |
| // Transaction is already gone or not started. Kill yourself | |
| terminate_stream = true; | |
| terminate_if_possible(); | |
| } | |
| } | |
| } | |
| bool | |
| Http2Stream::is_outbound_connection() const | |
| { | |
| return _is_outbound; | |
| } | |
| bool | |
| Http2Stream::is_tunneling() const | |
| { | |
| return _is_tunneling; | |
| } | |
| /* Replace existing event only if the new event is different than the inprogress event */ | |
| Event * | |
| Http2Stream::send_tracked_event(Event *event, int send_event, VIO *vio) | |
| { | |
| if (event != nullptr) { | |
| if (event->callback_event != send_event) { | |
| event->cancel(); | |
| event = nullptr; | |
| } | |
| } | |
| if (event == nullptr) { | |
| REMEMBER(send_event, this->reentrancy_count); | |
| event = this_ethread()->schedule_imm(this, send_event, vio); | |
| } | |
| return event; | |
| } | |
| void | |
| Http2Stream::update_read_request(bool call_update) | |
| { | |
| if (closed || _proxy_ssn == nullptr || _sm == nullptr || read_vio.mutex == nullptr) { | |
| return; | |
| } | |
| if (!this->_switch_thread_if_not_on_right_thread(VC_EVENT_READ_READY, nullptr)) { | |
| // Not on the right thread | |
| return; | |
| } | |
| ink_release_assert(this->_thread == this_ethread()); | |
| SCOPED_MUTEX_LOCK(lock, read_vio.mutex, this_ethread()); | |
| if (this->_is_read_gated() || read_vio.is_disabled()) { | |
| return; | |
| } | |
| // Try to be smart and only signal if there was additional data | |
| int send_event = VC_EVENT_READ_READY; | |
| if (read_vio.ntodo() == 0 || (this->receive_end_stream && this->read_vio.nbytes != INT64_MAX)) { | |
| send_event = VC_EVENT_READ_COMPLETE; | |
| } | |
| int64_t read_avail = this->read_vio.get_writer()->max_read_avail(); | |
| if (read_avail > 0 || send_event == VC_EVENT_READ_COMPLETE) { | |
| if (call_update) { // Safe to call vio handler directly | |
| _timeout.update_inactivity(); | |
| if (read_vio.cont && this->_sm) { | |
| read_vio.cont->handleEvent(send_event, &read_vio); | |
| } | |
| } else { // Called from do_io_read. Still setting things up. Send event | |
| // to handle this after the dust settles | |
| read_event = send_tracked_event(read_event, send_event, &read_vio); | |
| } | |
| } | |
| } | |
| void | |
| Http2Stream::restart_sending() | |
| { | |
| // Make sure the stream is in a good state to be sending | |
| if (this->is_closed()) { | |
| return; | |
| } | |
| if (!this->parsing_header_done) { | |
| this->update_write_request(true); | |
| return; | |
| } | |
| if (this->is_outbound_connection()) { | |
| if (this->get_state() != Http2StreamState::HTTP2_STREAM_STATE_OPEN || write_vio.ntodo() == 0) { | |
| return; | |
| } | |
| } else { | |
| if (this->get_state() != Http2StreamState::HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE) { | |
| return; | |
| } | |
| } | |
| if (this->write_vio.mutex && this->write_vio.ntodo() <= 0) { | |
| return; | |
| } | |
| IOBufferReader *reader = this->get_data_reader_for_send(); | |
| if (reader && !reader->is_read_avail_more_than(0)) { | |
| return; | |
| } | |
| this->send_body(true); | |
| } | |
| void | |
| Http2Stream::update_write_request(bool call_update) | |
| { | |
| if (!this->is_state_writeable() || closed || _proxy_ssn == nullptr || write_vio.mutex == nullptr || | |
| write_vio.get_reader() == nullptr || this->_send_reader == nullptr) { | |
| return; | |
| } | |
| if (!this->_switch_thread_if_not_on_right_thread(VC_EVENT_WRITE_READY, nullptr)) { | |
| // Not on the right thread | |
| return; | |
| } | |
| ink_release_assert(this->_thread == this_ethread()); | |
| Http2StreamDebug("update_write_request parse_done=%d", parsing_header_done); | |
| Http2ConnectionState &connection_state = this->get_connection_state(); | |
| SCOPED_MUTEX_LOCK(lock, write_vio.mutex, this_ethread()); | |
| IOBufferReader *vio_reader = write_vio.get_reader(); | |
| if (write_vio.ntodo() > 0 && (!vio_reader->is_read_avail_more_than(0))) { | |
| Http2StreamDebug("update_write_request give up without doing anything ntodo=%" PRId64 " is_read_avail=%d client_window=%zd" | |
| " session_window=%zd", | |
| write_vio.ntodo(), vio_reader->is_read_avail_more_than(0), _peer_rwnd, | |
| this->get_connection_state().get_peer_rwnd()); | |
| return; | |
| } | |
| // Process the new data | |
| if (!this->parsing_header_done) { | |
| // Still parsing the request or response header | |
| int bytes_used = 0; | |
| ParseResult state; | |
| if (this->is_outbound_connection()) { | |
| state = this->_send_header.parse_req(&http_parser, this->_send_reader, &bytes_used, false); | |
| } else { | |
| state = this->_send_header.parse_resp(&http_parser, this->_send_reader, &bytes_used, false); | |
| } | |
| // HTTPHdr::parse_resp() consumed the send_reader in above | |
| write_vio.ndone += bytes_used; | |
| switch (state) { | |
| case ParseResult::DONE: { | |
| this->parsing_header_done = true; | |
| Http2StreamDebug("update_write_request parsing done, read %d bytes", bytes_used); | |
| // A client request must not drain a shared origin session. | |
| if (!this->is_outbound_connection()) { | |
| MIMEField *field = this->_send_header.field_find(static_cast<std::string_view>(MIME_FIELD_CONNECTION)); | |
| if (field) { | |
| auto value{field->value_get()}; | |
| if (value == static_cast<std::string_view>(HTTP_VALUE_CLOSE)) { | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| if (connection_state.get_shutdown_state() == HTTP2_SHUTDOWN_NONE) { | |
| connection_state.set_shutdown_state(HTTP2_SHUTDOWN_NOT_INITIATED, Http2ErrorCode::HTTP2_ERROR_NO_ERROR); | |
| } | |
| } | |
| } | |
| } | |
| { | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| // Send the response header back | |
| connection_state.send_headers_frame(this); | |
| } | |
| // Roll back states of response header to read final response | |
| if (!this->is_outbound_connection() && this->_send_header.expect_final_response()) { | |
| this->parsing_header_done = false; | |
| } | |
| if (this->is_outbound_connection() || this->_send_header.expect_final_response()) { | |
| // The send-side request header is about to be torn down on outbound | |
| // streams, so snapshot the request method and any conditional-header | |
| // presence first. The snapshot is needed later by | |
| // `payload_length_is_valid` to apply [RFC 9110] 8.6 payload preclusion | |
| // for HEAD responses and 304 responses to conditional GETs. | |
| this->cache_send_request_for_response_validation(); | |
| _send_header.destroy(); | |
| _send_header.create(this->is_outbound_connection() ? HTTPType::REQUEST : HTTPType::RESPONSE, HTTP_2_0); | |
| http_parser_clear(&http_parser); | |
| http_parser_init(&http_parser); | |
| } | |
| bool final_write = this->write_vio.ntodo() == 0; | |
| if (final_write) { | |
| this->signal_write_event(VC_EVENT_WRITE_COMPLETE, !CALL_UPDATE); | |
| } | |
| if (!final_write && this->_send_reader->is_read_avail_more_than(0)) { | |
| Http2StreamDebug("update_write_request done parsing, still more to send"); | |
| this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); | |
| this->send_body(call_update); | |
| } | |
| break; | |
| } | |
| case ParseResult::CONT: | |
| // Let it ride for next time | |
| Http2StreamDebug("update_write_request still parsing, read %d bytes", bytes_used); | |
| break; | |
| default: | |
| Http2StreamDebug("update_write_request state %d, read %d bytes", static_cast<int>(state), bytes_used); | |
| break; | |
| } | |
| } else { | |
| this->_milestones.mark(Http2StreamMilestone::START_TX_DATA_FRAMES); | |
| this->send_body(call_update); | |
| } | |
| return; | |
| } | |
| void | |
| Http2Stream::signal_read_event(int event) | |
| { | |
| if (this->_sm == nullptr || this->read_vio.cont == nullptr || this->read_vio.cont->mutex == nullptr || | |
| this->read_vio.op == VIO::NONE || this->_is_read_gated() || this->read_vio.is_disabled() || this->terminate_stream) { | |
| return; | |
| } | |
| reentrancy_count++; | |
| MUTEX_TRY_LOCK(lock, read_vio.cont->mutex, this_ethread()); | |
| if (lock.is_locked()) { | |
| if (read_event) { | |
| read_event->cancel(); | |
| read_event = nullptr; | |
| } | |
| _timeout.update_inactivity(); | |
| this->read_vio.cont->handleEvent(event, &this->read_vio); | |
| } else { | |
| if (this->_read_vio_event) { | |
| this->_read_vio_event->cancel(); | |
| } | |
| this->_read_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &read_vio); | |
| } | |
| reentrancy_count--; | |
| // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack | |
| terminate_if_possible(); | |
| } | |
| /** Signal the event that ends the read side, or hold it until reenable() if the consumer has the read VIO disabled. | |
| A consumer that throttles its producer disables the read VIO. The END_STREAM that arrives during that | |
| window is the only end-of-read signal the stream will see, so it must not be dropped. | |
| */ | |
| void | |
| Http2Stream::signal_final_read_event(int event) | |
| { | |
| if (this->_is_read_gated()) { | |
| return; | |
| } | |
| if (this->read_vio.is_disabled()) { | |
| Http2StreamDebug("defer %s until the read VIO is re-enabled", get_vc_event_name(event)); | |
| this->_deferred_read_event = event; | |
| return; | |
| } | |
| this->signal_read_event(event); | |
| } | |
| void | |
| Http2Stream::signal_write_event(int event, bool call_update) | |
| { | |
| // Don't signal a write event if in fact nothing was written | |
| if (this->_sm == nullptr || this->write_vio.cont == nullptr || this->write_vio.cont->mutex == nullptr || | |
| this->write_vio.op == VIO::NONE || this->terminate_stream) { | |
| return; | |
| } | |
| reentrancy_count++; | |
| if (call_update) { | |
| MUTEX_TRY_LOCK(lock, write_vio.cont->mutex, this_ethread()); | |
| if (lock.is_locked()) { | |
| if (write_event) { | |
| write_event->cancel(); | |
| write_event = nullptr; | |
| } | |
| _timeout.update_inactivity(); | |
| if (event == VC_EVENT_WRITE_COMPLETE) { | |
| // HttpSM owns the write buffer and may release it while handling this | |
| // event. Drop the unowned alias before transferring control. | |
| _send_reader = nullptr; | |
| } | |
| this->write_vio.cont->handleEvent(event, &this->write_vio); | |
| } else { | |
| if (this->_write_vio_event) { | |
| this->_write_vio_event->cancel(); | |
| } | |
| this->_write_vio_event = this_ethread()->schedule_in(this, retry_delay, event, &write_vio); | |
| } | |
| } else { | |
| // Called from do_io_write. Might still be setting up state. Send an event to let the dust settle | |
| write_event = send_tracked_event(write_event, event, &write_vio); | |
| } | |
| reentrancy_count--; | |
| // Clean stream up if the terminate flag is set and we are at the bottom of the handler stack | |
| terminate_if_possible(); | |
| } | |
| bool | |
| Http2Stream::push_promise(URL &url, const MIMEField *accept_encoding) | |
| { | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| return this->get_connection_state().send_push_promise_frame(this, url, accept_encoding); | |
| } | |
| void | |
| Http2Stream::send_body(bool /* call_update ATS_UNUSED */) | |
| { | |
| Http2ConnectionState &connection_state = this->get_connection_state(); | |
| reentrancy_count++; | |
| SCOPED_MUTEX_LOCK(lock, _proxy_ssn->mutex, this_ethread()); | |
| if (Http2::stream_priority_enabled) { | |
| connection_state.schedule_stream_to_send_priority_frames(this); | |
| // signal_write_event() will be called from `Http2ConnectionState::send_data_frames_depends_on_priority()` | |
| // when write_vio is consumed | |
| } else { | |
| connection_state.send_data_frames(this); | |
| // XXX The call to signal_write_event can destroy/free the Http2Stream. | |
| // Don't modify the Http2Stream after calling this method. | |
| } | |
| reentrancy_count--; | |
| terminate_if_possible(); | |
| } | |
| void | |
| Http2Stream::reenable_write() | |
| { | |
| if (this->_proxy_ssn) { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| update_write_request(true); | |
| } | |
| } | |
| void | |
| Http2Stream::reenable(VIO *vio) | |
| { | |
| if (this->_proxy_ssn) { | |
| if (vio->op == VIO::WRITE) { | |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); | |
| update_write_request(true); | |
| } else if (vio->op == VIO::READ) { | |
| SCOPED_MUTEX_LOCK(ssn_lock, _proxy_ssn->mutex, this_ethread()); | |
| Http2ConnectionState &connection_state = this->get_connection_state(); | |
| connection_state.restart_receiving(this); | |
| if (this->_deferred_read_event != VC_EVENT_NONE && this->_sm != nullptr && this->read_vio.cont != nullptr) { | |
| // Schedule rather than call back: the consumer re-enabling us is still on the stack. If it throttles | |
| // again before the event is dispatched, main_event_handler() defers the event once more. | |
| this->read_event = this->send_tracked_event(this->read_event, this->_deferred_read_event, &this->read_vio); | |
| Http2StreamDebug("rescheduled deferred %s", get_vc_event_name(this->_deferred_read_event)); | |
| this->_deferred_read_event = VC_EVENT_NONE; | |
| } | |
| } | |
| } | |
| } | |
| IOBufferReader * | |
| Http2Stream::get_data_reader_for_send() const | |
| { | |
| return this->_send_reader; | |
| } | |
| void | |
| Http2Stream::set_active_timeout(ink_hrtime timeout_in) | |
| { | |
| _timeout.set_active_timeout(timeout_in); | |
| } | |
| void | |
| Http2Stream::set_inactivity_timeout(ink_hrtime timeout_in) | |
| { | |
| _timeout.set_inactive_timeout(timeout_in); | |
| } | |
| void | |
| Http2Stream::cancel_active_timeout() | |
| { | |
| _timeout.cancel_active_timeout(); | |
| } | |
| void | |
| Http2Stream::cancel_inactivity_timeout() | |
| { | |
| _timeout.cancel_inactive_timeout(); | |
| } | |
| bool | |
| Http2Stream::is_active_timeout_expired(ink_hrtime now) | |
| { | |
| return _timeout.is_active_timeout_expired(now); | |
| } | |
| bool | |
| Http2Stream::is_inactive_timeout_expired(ink_hrtime now) | |
| { | |
| return _timeout.is_inactive_timeout_expired(now); | |
| } | |
| void | |
| Http2Stream::clear_io_events() | |
| { | |
| if (cross_thread_event) { | |
| cross_thread_event->cancel(); | |
| cross_thread_event = nullptr; | |
| } | |
| if (read_event) { | |
| read_event->cancel(); | |
| read_event = nullptr; | |
| } | |
| if (write_event) { | |
| write_event->cancel(); | |
| write_event = nullptr; | |
| } | |
| if (this->_read_vio_event) { | |
| this->_read_vio_event->cancel(); | |
| this->_read_vio_event = nullptr; | |
| } | |
| if (this->_write_vio_event) { | |
| this->_write_vio_event->cancel(); | |
| this->_write_vio_event = nullptr; | |
| } | |
| } | |
| /** | |
| Callback from HttpSM | |
| release and do_io_close are the same for the HTTP/2 protocol | |
| */ | |
| void | |
| Http2Stream::release() | |
| { | |
| if (_state == Http2StreamState::HTTP2_STREAM_STATE_CLOSED) { | |
| this->do_io_close(); | |
| return; | |
| } | |
| Http2StreamDebug("Delaying do_io_close() until stream is in the closed state"); | |
| } | |
| void | |
| Http2Stream::increment_transactions_stat() | |
| { | |
| if (this->is_outbound_connection()) { | |
| Metrics::Gauge::increment(http2_rsb.current_server_stream_count); | |
| Metrics::Counter::increment(http2_rsb.total_server_stream_count); | |
| } else { | |
| Metrics::Gauge::increment(http2_rsb.current_client_stream_count); | |
| Metrics::Counter::increment(http2_rsb.total_client_stream_count); | |
| } | |
| } | |
| void | |
| Http2Stream::decrement_transactions_stat() | |
| { | |
| if (this->is_outbound_connection()) { | |
| Metrics::Gauge::decrement(http2_rsb.current_server_stream_count); | |
| } else { | |
| Metrics::Gauge::decrement(http2_rsb.current_client_stream_count); | |
| } | |
| } | |
| ssize_t | |
| Http2Stream::get_peer_rwnd() const | |
| { | |
| return this->_peer_rwnd; | |
| } | |
| Http2ErrorCode | |
| Http2Stream::increment_peer_rwnd(size_t amount) | |
| { | |
| this->_peer_rwnd += amount; | |
| this->_recent_rwnd_increment[this->_recent_rwnd_increment_index] = amount; | |
| ++this->_recent_rwnd_increment_index; | |
| this->_recent_rwnd_increment_index %= this->_recent_rwnd_increment.size(); | |
| double sum = std::accumulate(this->_recent_rwnd_increment.begin(), this->_recent_rwnd_increment.end(), 0.0); | |
| double avg = sum / this->_recent_rwnd_increment.size(); | |
| if (avg < Http2::min_avg_window_update) { | |
| return Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM; | |
| } | |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; | |
| } | |
| Http2ErrorCode | |
| Http2Stream::decrement_peer_rwnd(size_t amount) | |
| { | |
| this->_peer_rwnd -= amount; | |
| if (this->_peer_rwnd < 0) { | |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; | |
| } else { | |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; | |
| } | |
| } | |
| ssize_t | |
| Http2Stream::get_local_rwnd() const | |
| { | |
| return this->_local_rwnd; | |
| } | |
| Http2ErrorCode | |
| Http2Stream::increment_local_rwnd(size_t amount) | |
| { | |
| this->_local_rwnd += amount; | |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; | |
| } | |
| Http2ErrorCode | |
| Http2Stream::decrement_local_rwnd(size_t amount) | |
| { | |
| this->_local_rwnd -= amount; | |
| if (this->_local_rwnd < 0) { | |
| return Http2ErrorCode::HTTP2_ERROR_PROTOCOL_ERROR; | |
| } else { | |
| return Http2ErrorCode::HTTP2_ERROR_NO_ERROR; | |
| } | |
| } | |
| bool | |
| Http2Stream::_switch_thread_if_not_on_right_thread(int event, void *edata) | |
| { | |
| if (this->_thread != this_ethread()) { | |
| SCOPED_MUTEX_LOCK(stream_lock, this->mutex, this_ethread()); | |
| if (cross_thread_event == nullptr) { | |
| // Send to the right thread | |
| cross_thread_event = this->_thread->schedule_imm(this, event, edata); | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| int | |
| Http2Stream::get_transaction_priority_weight() const | |
| { | |
| return priority_node ? priority_node->weight : 0; | |
| } | |
| int | |
| Http2Stream::get_transaction_priority_dependence() const | |
| { | |
| if (!priority_node) { | |
| return -1; | |
| } else { | |
| return priority_node->parent ? priority_node->parent->id : 0; | |
| } | |
| } | |
| int64_t | |
| Http2Stream::read_vio_read_avail() | |
| { | |
| MIOBuffer *writer = this->read_vio.get_writer(); | |
| if (writer) { | |
| return writer->max_read_avail(); | |
| } | |
| return 0; | |
| } | |
| bool | |
| Http2Stream::has_request_body(int64_t /* content_length ATS_UNUSED */, bool /* is_chunked_set ATS_UNUSED */) const | |
| { | |
| return has_body; | |
| } | |
| Http2ConnectionState & | |
| Http2Stream::get_connection_state() | |
| { | |
| if (this->is_outbound_connection()) { | |
| Http2ServerSession *session = static_cast<Http2ServerSession *>(_proxy_ssn); | |
| return session->connection_state; | |
| } else { | |
| Http2ClientSession *session = static_cast<Http2ClientSession *>(_proxy_ssn); | |
| return session->connection_state; | |
| } | |
| } | |
| bool | |
| Http2Stream::is_read_closed() const | |
| { | |
| return this->receive_end_stream; | |
| } | |
| bool | |
| Http2Stream::expect_send_trailer() const | |
| { | |
| return this->_expect_send_trailer; | |
| } | |
| bool | |
| Http2Stream::can_send_h2_trailer() const | |
| { | |
| return !send_end_stream; | |
| } | |
| void | |
| Http2Stream::set_expect_send_trailer() | |
| { | |
| _expect_send_trailer = true; | |
| parsing_header_done = false; | |
| reset_send_headers(); | |
| } | |
| bool | |
| Http2Stream::expect_receive_trailer() const | |
| { | |
| return this->_expect_receive_trailer; | |
| } | |
| void | |
| Http2Stream::set_expect_receive_trailer() | |
| { | |
| _expect_receive_trailer = true; | |
| } | |
| void | |
| Http2Stream::set_rx_error_code(ProxyError e) | |
| { | |
| if (!this->is_outbound_connection() && this->_sm) { | |
| this->_sm->t_state.client_info.rx_error_code = e; | |
| } | |
| } | |
| void | |
| Http2Stream::set_tx_error_code(ProxyError e) | |
| { | |
| if (!this->is_outbound_connection() && this->_sm) { | |
| if (!(this->_sm->t_state.client_info.tx_error_code.cls == ProxyErrorClass::SSN && e.cls == ProxyErrorClass::TXN)) { | |
| // if there is not an error already set for the session set the transaction level error | |
| this->_sm->t_state.client_info.tx_error_code = e; | |
| } | |
| } | |
| } | |
| bool | |
| Http2Stream::is_safe_to_retry() const | |
| { | |
| // Remember even informational, partial, or malformed response headers. | |
| // A later peer assertion cannot undo evidence that it handled this request. | |
| return _safe_to_retry && this->is_outbound_connection() && !_response_received && data_length == 0; | |
| } | |
| void | |
| Http2Stream::set_safe_to_retry() | |
| { | |
| _safe_to_retry = true; | |
| } | |
| HTTPVersion | |
| Http2Stream::get_version(HTTPHdr & /* hdr ATS_UNUSED */) const | |
| { | |
| return HTTP_2_0; | |
| } |