| /** @file | |
| A brief file description | |
| @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. | |
| */ | |
| /**************************************************************************** | |
| Http1ClientSession.cc | |
| Description: | |
| ****************************************************************************/ | |
| #include "iocore/net/NetVConnection.h" | |
| #include "tscore/ink_resolver.h" | |
| #include "proxy/http/Http1ClientSession.h" | |
| #include "proxy/http/Http1Transaction.h" | |
| #include "proxy/http/HttpSM.h" | |
| #include "proxy/http/HttpDebugNames.h" | |
| #include "proxy/Plugin.h" | |
| #include "proxy/PoolableSession.h" | |
| #include "iocore/net/TLSBasicSupport.h" | |
| #include "iocore/net/TLSEarlyDataSupport.h" | |
| #define HttpSsnDbg(fmt, ...) SsnDbg(this, dbg_ctl_http_cs, fmt, __VA_ARGS__) | |
| #define STATE_ENTER(state_name, event, vio) \ | |
| do { \ | |
| /*ink_assert (magic == HttpSmMagic_t::ALIVE); REMEMBER (event, NULL, reentrancy_count); */ \ | |
| HttpSsnDbg("[%" PRId64 "] [%s, %s]", con_id, #state_name, HttpDebugNames::get_event_name(event)); \ | |
| } while (0) | |
| #ifdef USE_HTTP_DEBUG_LISTS | |
| // We have debugging list that we can use to find stuck | |
| // client sessions | |
| DList(Http1ClientSession, debug_link) debug_cs_list; | |
| ink_mutex debug_cs_list_mutex; | |
| #endif /* USE_HTTP_DEBUG_LISTS */ | |
| ClassAllocator<Http1ClientSession> http1ClientSessionAllocator("http1ClientSessionAllocator"); | |
| namespace | |
| { | |
| DbgCtl dbg_ctl_http_cs{"http_cs"}; | |
| DbgCtl dbg_ctl_ssl_early_data{"ssl_early_data"}; | |
| } // end anonymous namespace | |
| Http1ClientSession::Http1ClientSession() : super(), trans(this) {} | |
| // | |
| // Will only close the connection if do_io_close has been called previously (to set read_state to C_Read_State::CLOSED | |
| void | |
| Http1ClientSession::destroy() | |
| { | |
| if (read_state != C_Read_State::CLOSED) { | |
| return; | |
| } | |
| if (!in_destroy) { | |
| in_destroy = true; | |
| HttpSsnDbg("[%" PRId64 "] session destroy", con_id); | |
| ink_assert(read_buffer); | |
| ink_release_assert(transact_count == released_transactions); | |
| do_api_callout(TS_HTTP_SSN_CLOSE_HOOK); | |
| } else { | |
| Warning("http1: Attempt to double ssn close"); | |
| } | |
| } | |
| void | |
| Http1ClientSession::release_transaction() | |
| { | |
| released_transactions++; | |
| if (transact_count == released_transactions) { | |
| // Make sure we previously called release() or do_io_close() on the session | |
| ink_release_assert(read_state != C_Read_State::INIT); | |
| if (is_active()) { | |
| // (in)active timeout | |
| do_io_close(HTTP_ERRNO); | |
| } else if (read_state == C_Read_State::ACTIVE_READER) { | |
| release(&trans); // Put back to keep-alive state | |
| } else { | |
| destroy(); | |
| } | |
| } else { | |
| ink_release_assert(transact_count == released_transactions); | |
| } | |
| } | |
| void | |
| Http1ClientSession::free() | |
| { | |
| magic = Magic::DEAD; | |
| if (read_buffer) { | |
| free_MIOBuffer(read_buffer); | |
| read_buffer = nullptr; | |
| } | |
| #ifdef USE_HTTP_DEBUG_LISTS | |
| ink_mutex_acquire(&debug_cs_list_mutex); | |
| debug_cs_list.remove(this); | |
| ink_mutex_release(&debug_cs_list_mutex); | |
| #endif | |
| if (conn_decrease) { | |
| Metrics::Gauge::decrement(http_rsb.current_client_connections); | |
| conn_decrease = false; | |
| } | |
| if (_vc) { | |
| _vc->do_io_close(); | |
| _vc = nullptr; | |
| } | |
| THREAD_FREE(this, http1ClientSessionAllocator, this_thread()); | |
| } | |
| void | |
| Http1ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOBufferReader *reader) | |
| { | |
| ink_assert(new_vc != nullptr); | |
| ink_assert(_vc == nullptr); | |
| _vc = new_vc; | |
| magic = Magic::ALIVE; | |
| mutex = new_vc->mutex; | |
| trans.mutex = mutex; // Share this mutex with the transaction | |
| in_destroy = false; | |
| if (TLSEarlyDataSupport *eds = new_vc->get_service<TLSEarlyDataSupport>()) { | |
| read_from_early_data = eds->get_early_data_len(); | |
| Dbg(dbg_ctl_ssl_early_data, "read_from_early_data = %" PRId64, read_from_early_data); | |
| } | |
| MUTEX_TRY_LOCK(lock, mutex, this_ethread()); | |
| ink_assert(lock.is_locked()); | |
| // Unique client session identifier. | |
| con_id = ProxySession::next_connection_id(); | |
| schedule_event = nullptr; | |
| Metrics::Gauge::increment(http_rsb.current_client_connections); | |
| conn_decrease = true; | |
| Metrics::Counter::increment(http_rsb.total_client_connections); | |
| if (static_cast<HttpProxyPort::TransportType>(new_vc->attributes) == HttpProxyPort::TRANSPORT_SSL) { | |
| Metrics::Counter::increment(http_rsb.https_total_client_connections); | |
| } | |
| /* inbound requests stat should be incremented here, not after the | |
| * header has been read */ | |
| Metrics::Counter::increment(http_rsb.total_incoming_connections); | |
| this->_increment_total_client_connections_stat(new_vc); | |
| #ifdef USE_HTTP_DEBUG_LISTS | |
| ink_mutex_acquire(&debug_cs_list_mutex); | |
| debug_cs_list.push(this); | |
| ink_mutex_release(&debug_cs_list_mutex); | |
| #endif | |
| HttpSsnDbg("[%" PRId64 "] session born, netvc %p", con_id, new_vc); | |
| _vc->set_tcp_congestion_control(NetVConnection::tcp_congestion_control_side::CLIENT_SIDE); | |
| read_buffer = iobuf ? iobuf : new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); | |
| _reader = reader ? reader : read_buffer->alloc_reader(); | |
| trans.set_reader(_reader); | |
| _handle_if_ssl(new_vc); | |
| // INKqa11186: Use a local pointer to the mutex as | |
| // when we return from do_api_callout, the ClientSession may | |
| // have already been deallocated. | |
| EThread *ethis = this_ethread(); | |
| Ptr<ProxyMutex> lmutex = this->mutex; | |
| MUTEX_TAKE_LOCK(lmutex, ethis); | |
| if (has_session_hook(TS_HTTP_SSN_START_HOOK)) { | |
| _vc->cancel_inactivity_timeout(); | |
| } | |
| do_api_callout(TS_HTTP_SSN_START_HOOK); | |
| MUTEX_UNTAKE_LOCK(lmutex, ethis); | |
| lmutex.clear(); | |
| } | |
| void | |
| Http1ClientSession::do_io_close(int alerrno) | |
| { | |
| if (read_state == C_Read_State::CLOSED) { | |
| if (transact_count == released_transactions) { | |
| this->destroy(); | |
| } | |
| return; // Don't double call session close | |
| } | |
| if (read_state == C_Read_State::ACTIVE_READER) { | |
| clear_session_active(); | |
| } | |
| // Prevent double closing | |
| ink_release_assert(read_state != C_Read_State::CLOSED); | |
| // If we have an attached server session, release | |
| // it back to our shared pool | |
| if (bound_ss) { | |
| bound_ss->release(nullptr); | |
| bound_ss = nullptr; | |
| slave_ka_vio = nullptr; | |
| } | |
| // Completed the last transaction. Just shutdown already | |
| // Or the do_io_close is due to a network error | |
| if (transact_count == released_transactions || alerrno == HTTP_ERRNO) { | |
| half_close = false; | |
| } | |
| if (half_close && this->trans.get_sm()) { | |
| read_state = C_Read_State::HALF_CLOSED; | |
| SET_HANDLER(&Http1ClientSession::state_wait_for_close); | |
| HttpSsnDbg("[%" PRId64 "] session half close", con_id); | |
| if (_vc) { | |
| _vc->do_io_shutdown(IO_SHUTDOWN_WRITE); | |
| ka_vio = _vc->do_io_read(this, INT64_MAX, read_buffer); | |
| ink_assert(slave_ka_vio != ka_vio); | |
| // Set the active timeout to the same as the inactive time so | |
| // that this connection does not hang around forever if | |
| // the ua hasn't closed | |
| _vc->set_active_timeout(HRTIME_SECONDS(trans.get_sm()->t_state.txn_conf->keep_alive_no_activity_timeout_in)); | |
| } | |
| // [bug 2610799] Drain any data read. | |
| // If the buffer is full and the client writes again, we will not receive a | |
| // READ_READY event. | |
| _reader->consume(_reader->read_avail()); | |
| } else { | |
| HttpSsnDbg("[%" PRId64 "] session closed", con_id); | |
| read_state = C_Read_State::CLOSED; | |
| if (_vc) { | |
| _vc->do_io_close(); | |
| _vc = nullptr; | |
| } | |
| } | |
| if (transact_count == released_transactions) { | |
| this->destroy(); | |
| } | |
| } | |
| int | |
| Http1ClientSession::state_wait_for_close(int event, void *data) | |
| { | |
| STATE_ENTER(&Http1ClientSession::state_wait_for_close, event, data); | |
| ink_assert(data == ka_vio); | |
| ink_assert(read_state == C_Read_State::HALF_CLOSED); | |
| Event *e = static_cast<Event *>(data); | |
| if (e == schedule_event) { | |
| schedule_event = nullptr; | |
| } | |
| switch (event) { | |
| case VC_EVENT_EOS: | |
| case VC_EVENT_ERROR: | |
| case VC_EVENT_ACTIVE_TIMEOUT: | |
| case VC_EVENT_INACTIVITY_TIMEOUT: | |
| half_close = false; | |
| this->do_io_close(EHTTP_ERROR); | |
| break; | |
| case VC_EVENT_READ_READY: | |
| // Drain any data read | |
| _reader->consume(_reader->read_avail()); | |
| break; | |
| default: | |
| ink_release_assert(0); | |
| break; | |
| } | |
| return 0; | |
| } | |
| int | |
| Http1ClientSession::state_slave_keep_alive(int event, void *data) | |
| { | |
| STATE_ENTER(&Http1ClientSession::state_slave_keep_alive, event, data); | |
| ink_assert(data == slave_ka_vio); | |
| Event *e = static_cast<Event *>(data); | |
| if (e == schedule_event) { | |
| schedule_event = nullptr; | |
| } | |
| switch (event) { | |
| default: | |
| case VC_EVENT_READ_COMPLETE: | |
| // These events are bogus | |
| ink_assert(0); | |
| /* Fall Through */ | |
| case VC_EVENT_ERROR: | |
| case VC_EVENT_READ_READY: | |
| case VC_EVENT_EOS: | |
| // The server session closed or something is amiss | |
| bound_ss->do_io_close(); | |
| bound_ss = nullptr; | |
| slave_ka_vio = nullptr; | |
| break; | |
| case VC_EVENT_ACTIVE_TIMEOUT: | |
| case VC_EVENT_INACTIVITY_TIMEOUT: | |
| // Timeout - place the session on the shared pool | |
| bound_ss->release(nullptr); | |
| bound_ss = nullptr; | |
| slave_ka_vio = nullptr; | |
| break; | |
| } | |
| return 0; | |
| } | |
| int | |
| Http1ClientSession::state_keep_alive(int event, void *data) | |
| { | |
| // Route the event. It is either for vc or | |
| // the origin server slave vc | |
| if (data) { | |
| if (data == slave_ka_vio) { | |
| return state_slave_keep_alive(event, data); | |
| } else if (data == schedule_event) { | |
| schedule_event = nullptr; | |
| } else { | |
| ink_assert(data && data == ka_vio); | |
| ink_assert(read_state == C_Read_State::KEEP_ALIVE); | |
| } | |
| } | |
| // If we got here due to a network I/O event directly, go ahead and cancel any remaining schedule events | |
| if (schedule_event) { | |
| schedule_event->cancel(); | |
| schedule_event = nullptr; | |
| } | |
| STATE_ENTER(&Http1ClientSession::state_keep_alive, event, data); | |
| switch (event) { | |
| case VC_EVENT_READ_READY: | |
| // New transaction, need to spawn of new sm to process | |
| // request | |
| new_transaction(); | |
| break; | |
| case VC_EVENT_EOS: | |
| this->do_io_close(EHTTP_ERROR); | |
| break; | |
| case VC_EVENT_READ_COMPLETE: | |
| default: | |
| // These events are bogus | |
| ink_assert(0); | |
| // Fall through | |
| case VC_EVENT_ERROR: | |
| case VC_EVENT_ACTIVE_TIMEOUT: | |
| case VC_EVENT_INACTIVITY_TIMEOUT: | |
| // Keep-alive timed out | |
| this->do_io_close(EHTTP_ERROR); | |
| break; | |
| } | |
| return 0; | |
| } | |
| // Called from the Http1Transaction::release or at the start of a session for | |
| // the very first transaction from Http1ClientSession::new_connection. | |
| void | |
| Http1ClientSession::release(ProxyTransaction *trans) | |
| { | |
| // When release is called from start() to read the first transaction, get_sm() | |
| // will return null. | |
| HttpSM *sm = trans->get_sm(); | |
| Http1Transaction *h1trans = static_cast<Http1Transaction *>(trans); | |
| if (sm) { | |
| MgmtInt ka_in = trans->get_sm()->t_state.txn_conf->keep_alive_no_activity_timeout_in; | |
| set_inactivity_timeout(HRTIME_SECONDS(ka_in)); | |
| this->clear_session_active(); | |
| // Timeout events should be delivered to the session | |
| this->do_io_write(this, 0, nullptr); | |
| } else { | |
| HttpConfigParams *params = HttpConfig::acquire(); | |
| set_inactivity_timeout(HRTIME_SECONDS(params->accept_no_activity_timeout)); | |
| HttpConfig::release(params); | |
| } | |
| h1trans->reset(); | |
| // Check to see there is remaining data in the | |
| // buffer. If there is, spin up a new state | |
| // machine to process it. Otherwise, issue an | |
| // IO to wait for new data | |
| /* Start the new transaction once we finish completely the current transaction and unroll the stack */ | |
| bool more_to_read = this->_reader->is_read_avail_more_than(0); | |
| if (more_to_read) { | |
| HttpSsnDbg("[%" PRId64 "] data already in buffer, starting new transaction", con_id); | |
| new_transaction(); | |
| } else { | |
| HttpSsnDbg("[%" PRId64 "] initiating io for next header", con_id); | |
| read_state = C_Read_State::KEEP_ALIVE; | |
| SET_HANDLER(&Http1ClientSession::state_keep_alive); | |
| ka_vio = this->do_io_read(this, INT64_MAX, read_buffer); | |
| ink_assert(slave_ka_vio != ka_vio); | |
| if (_vc) { | |
| // Under heavy traffic ( - e.g. hitting proxy.config.net.max_connections_in limit), calling add_to_keep_alive_queue() | |
| // could free this _vc, session, and transaction. | |
| _vc->cancel_active_timeout(); | |
| _vc->add_to_keep_alive_queue(); | |
| } | |
| } | |
| } | |
| ProxyTransaction * | |
| Http1ClientSession::new_transaction() | |
| { | |
| // If the client connection terminated during API callouts we're done. | |
| if (nullptr == _vc) { | |
| this->do_io_close(); // calls the SSN_CLOSE hooks to match the SSN_START hooks. | |
| return nullptr; | |
| } | |
| if (!_vc->add_to_active_queue()) { | |
| // no room in the active queue close the connection | |
| this->do_io_close(); | |
| return nullptr; | |
| } | |
| // Defensive programming, make sure nothing persists across | |
| // connection re-use | |
| half_close = false; | |
| read_state = C_Read_State::ACTIVE_READER; | |
| transact_count++; | |
| trans.new_transaction(read_from_early_data > 0 ? true : false); | |
| return &trans; | |
| } | |
| bool | |
| Http1ClientSession::attach_server_session(PoolableSession *ssession, bool transaction_done) | |
| { | |
| if (ssession) { | |
| ink_assert(bound_ss == nullptr); | |
| ssession->state = PoolableSession::PooledState::KA_RESERVED; | |
| bound_ss = ssession; | |
| HttpSsnDbg("[%" PRId64 "] attaching server session [%" PRId64 "] as slave", con_id, ssession->connection_id()); | |
| ink_assert(ssession->get_netvc() != this->get_netvc()); | |
| // handling potential keep-alive here | |
| clear_session_active(); | |
| // Since this our slave, issue an IO to detect a close and | |
| // have it call the client session back. This IO also prevent | |
| // the server net connection from calling back a dead sm | |
| SET_HANDLER(&Http1ClientSession::state_keep_alive); | |
| slave_ka_vio = ssession->do_io_read(this, INT64_MAX, ssession->get_remote_reader()->mbuf); | |
| ink_assert(slave_ka_vio != ka_vio); | |
| // Transfer control of the write side as well | |
| ssession->do_io_write(this, 0, nullptr); | |
| if (transaction_done) { | |
| ssession->set_inactivity_timeout(HRTIME_SECONDS(trans.get_sm()->t_state.txn_conf->keep_alive_no_activity_timeout_out)); | |
| ssession->cancel_active_timeout(); | |
| } else { | |
| // we are serving from the cache - this could take a while. | |
| ssession->cancel_inactivity_timeout(); | |
| ssession->cancel_active_timeout(); | |
| } | |
| } else { | |
| ink_assert(bound_ss != nullptr); | |
| bound_ss = nullptr; | |
| slave_ka_vio = nullptr; | |
| } | |
| return true; | |
| } | |
| void | |
| Http1ClientSession::increment_current_active_connections_stat() | |
| { | |
| Metrics::Gauge::increment(http_rsb.current_active_client_connections); | |
| } | |
| void | |
| Http1ClientSession::decrement_current_active_connections_stat() | |
| { | |
| Metrics::Gauge::decrement(http_rsb.current_active_client_connections); | |
| } | |
| void | |
| Http1ClientSession::start() | |
| { | |
| // Troll for data to get a new transaction | |
| this->release(&trans); | |
| } | |
| bool | |
| Http1ClientSession::allow_half_open() const | |
| { | |
| // Only allow half open connections if the not over TLS | |
| return (_vc && _vc->get_service<TLSBasicSupport>() == nullptr); | |
| } | |
| void | |
| Http1ClientSession::set_half_close_flag(bool flag) | |
| { | |
| half_close = flag; | |
| } | |
| bool | |
| Http1ClientSession::get_half_close_flag() const | |
| { | |
| return half_close; | |
| } | |
| bool | |
| Http1ClientSession::is_chunked_encoding_supported() const | |
| { | |
| return true; | |
| } | |
| int | |
| Http1ClientSession::get_transact_count() const | |
| { | |
| return transact_count; | |
| } | |
| bool | |
| Http1ClientSession::is_outbound_transparent() const | |
| { | |
| return f_outbound_transparent; | |
| } | |
| PoolableSession * | |
| Http1ClientSession::get_server_session() const | |
| { | |
| return bound_ss; | |
| } | |
| const char * | |
| Http1ClientSession::get_protocol_string() const | |
| { | |
| return "http"; | |
| } |