diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc index 7285787aa7b..4a89628e355 100644 --- a/proxy/http/HttpDebugNames.cc +++ b/proxy/http/HttpDebugNames.cc @@ -367,11 +367,6 @@ HttpDebugNames::get_action_name(HttpTransact::StateMachineAction_t e) case HttpTransact::SM_ACTION_TRANSFORM_READ: return ("SM_ACTION_TRANSFORM_READ"); -#ifdef PROXY_DRAIN - case HttpTransact::SM_ACTION_DRAIN_REQUEST_BODY: - return ("SM_ACTION_DRAIN_REQUEST_BODY"); -#endif /* PROXY_DRAIN */ - case HttpTransact::SM_ACTION_API_SM_START: return ("SM_ACTION_API_SM_START"); case HttpTransact::SM_ACTION_REDIRECT_READ: diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index a3ccd79978c..220ee8accd3 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -823,63 +823,6 @@ HttpSM::state_read_client_request_header(int event, void *data) return 0; } -#ifdef PROXY_DRAIN -int -HttpSM::state_drain_client_request_body(int event, void *data) -{ - STATE_ENTER(&HttpSM::state_drain_client_request_body, event); - - ink_assert(ua_entry->read_vio == (VIO *)data); - ink_assert(ua_entry->vc == ua_session); - - NetVConnection *netvc = ua_session->get_netvc(); - if (!netvc && event != VC_EVENT_EOS) - return 0; - - switch (event) { - case VC_EVENT_EOS: - case VC_EVENT_ERROR: - case VC_EVENT_ACTIVE_TIMEOUT: - case VC_EVENT_INACTIVITY_TIMEOUT: { - // Nothing we can do - terminate_sm = true; - break; - } - case VC_EVENT_READ_READY: { - int64_t avail = ua_buffer_reader->read_avail(); - int64_t left = t_state.hdr_info.request_content_length - client_request_body_bytes; - - // Since we are only reading what's needed to complete - // the post, there must be something left to do - ink_assert(avail < left); - - client_request_body_bytes += avail; - ua_buffer_reader->consume(avail); - ua_entry->read_vio->reenable_re(); - break; - } - case VC_EVENT_READ_COMPLETE: { - // We've finished draing the POST body - int64_t avail = ua_buffer_reader->read_avail(); - - ua_buffer_reader->consume(avail); - client_request_body_bytes += avail; - ink_assert(client_request_body_bytes == t_state.hdr_info.request_content_length); - - ua_buffer_reader->mbuf->size_index = HTTP_HEADER_BUFFER_SIZE_INDEX; - ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; - ua_entry->read_vio = ua_entry->vc->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); - call_transact_and_set_next_state(NULL); - break; - } - default: - ink_release_assert(0); - } - - return EVENT_DONE; -} -#endif /* PROXY_DRAIN */ - int HttpSM::state_watch_for_client_abort(int event, void *data) { @@ -5505,29 +5448,34 @@ HttpSM::setup_transform_to_server_transfer() tunnel.tunnel_run(p); } -#ifdef PROXY_DRAIN void HttpSM::do_drain_request_body() { - int64_t post_bytes = t_state.hdr_info.request_content_length; - int64_t avail = ua_buffer_reader->read_avail(); + int64_t content_length = t_state.hdr_info.client_request.get_content_length(); + int64_t avail = ua_buffer_reader->read_avail(); - int64_t act_on = (avail < post_bytes) ? avail : post_bytes; - - client_request_body_bytes = act_on; - ua_buffer_reader->consume(act_on); - - ink_assert(client_request_body_bytes <= post_bytes); + if (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) { + Debug("http", "Chunked body, setting the response to non-keepalive"); + goto close_connection; + } - if (client_request_body_bytes < post_bytes) { - ua_buffer_reader->mbuf->size_index = buffer_size_to_index(t_state.hdr_info.request_content_length); - ua_entry->vc_handler = &HttpSM::state_drain_client_request_body; - ua_entry->read_vio = ua_entry->vc->do_io_read(this, post_bytes - client_request_body_bytes, ua_buffer_reader->mbuf); - } else { - call_transact_and_set_next_state(NULL); + if (content_length > 0) { + if (avail >= content_length) { + Debug("http", "entire body is in the buffer, consuming"); + int64_t act_on = (avail < content_length) ? avail : content_length; + client_request_body_bytes = act_on; + ua_buffer_reader->consume(act_on); + } else { + Debug("http", "entire body is not in the buffer, setting the response to non-keepalive"); + goto close_connection; + } } + return; + +close_connection: + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION, "close", 5); } -#endif /* PROXY_DRAIN */ void HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) @@ -7316,6 +7264,8 @@ HttpSM::set_next_state() release_server_session(true); t_state.source = HttpTransact::SOURCE_CACHE; + do_drain_request_body(); + if (transform_info.vc) { ink_assert(t_state.hdr_info.client_response.valid() == 0); ink_assert((t_state.hdr_info.transform_response.valid() ? true : false) == true); @@ -7506,13 +7456,6 @@ HttpSM::set_next_state() break; } -#ifdef PROXY_DRAIN - case HttpTransact::SM_ACTION_DRAIN_REQUEST_BODY: { - do_drain_request_body(); - break; - } -#endif /* PROXY_DRAIN */ - case HttpTransact::SM_ACTION_CONTINUE: { ink_release_assert(!"Not implemented"); } diff --git a/proxy/http/HttpSM.cc.orig b/proxy/http/HttpSM.cc.orig new file mode 100644 index 00000000000..a3ccd79978c --- /dev/null +++ b/proxy/http/HttpSM.cc.orig @@ -0,0 +1,7906 @@ +/** @file + + HTTP state machine + + @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 "../ProxyClientTransaction.h" +#include "HttpSM.h" +#include "ProxyConfig.h" +#include "HttpServerSession.h" +#include "HttpDebugNames.h" +#include "HttpSessionManager.h" +#include "P_Cache.h" +#include "P_Net.h" +#include "StatPages.h" +#include "Log.h" +#include "LogAccessHttp.h" +#include "ICP.h" +#include "PluginVC.h" +#include "ReverseProxy.h" +#include "RemapProcessor.h" +#include "Transform.h" +#include "P_SSLConfig.h" + +#include "HttpPages.h" + +//#include "I_Auth.h" +//#include "HttpAuthParams.h" +#include "congest/Congestion.h" + +#define DEFAULT_RESPONSE_BUFFER_SIZE_INDEX 6 // 8K +#define DEFAULT_REQUEST_BUFFER_SIZE_INDEX 6 // 8K +#define MIN_CONFIG_BUFFER_SIZE_INDEX 5 // 4K + +#define hsm_release_assert(EX) \ + { \ + if (!(EX)) { \ + this->dump_state_on_assert(); \ + _ink_assert(#EX, __FILE__, __LINE__); \ + } \ + } + +/* + * Comment this off if you dont + * want httpSM to use new_empty_MIOBuffer(..) call + */ + +#define USE_NEW_EMPTY_MIOBUFFER + +extern int cache_config_read_while_writer; + +// We have a debugging list that can use to find stuck +// state machines +DList(HttpSM, debug_link) debug_sm_list; +ink_mutex debug_sm_list_mutex; + +static const int sub_header_size = sizeof("Content-type: ") - 1 + 2 + sizeof("Content-range: bytes ") - 1 + 4; +static const int boundary_size = 2 + sizeof("RANGE_SEPARATOR") - 1 + 2; + +static const char *str_100_continue_response = "HTTP/1.1 100 Continue\r\n\r\n"; +static const int len_100_continue_response = strlen(str_100_continue_response); + +static const char *str_408_request_timeout_response = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n"; +static const int len_408_request_timeout_response = strlen(str_408_request_timeout_response); + +namespace +{ +/// Update the milestone state given the milestones and timer. +inline void +milestone_update_api_time(TransactionMilestones &milestones, ink_hrtime &api_timer) +{ + // Bit of funkiness - we set @a api_timer to be the negative value when we're tracking + // non-active API time. In that case we need to make a note of it and flip the value back + // to positive. + if (api_timer) { + ink_hrtime delta; + bool active = api_timer >= 0; + if (!active) + api_timer = -api_timer; + delta = Thread::get_hrtime_updated() - api_timer; + api_timer = 0; + // Zero or negative time is a problem because we want to signal *something* happened + // vs. no API activity at all. This can happen due to graininess or real time + // clock adjustment. + if (delta <= 0) + delta = 1; + + if (0 == milestones[TS_MILESTONE_PLUGIN_TOTAL]) + milestones[TS_MILESTONE_PLUGIN_TOTAL] = milestones[TS_MILESTONE_SM_START]; + milestones[TS_MILESTONE_PLUGIN_TOTAL] += delta; + if (active) { + if (0 == milestones[TS_MILESTONE_PLUGIN_ACTIVE]) + milestones[TS_MILESTONE_PLUGIN_ACTIVE] = milestones[TS_MILESTONE_SM_START]; + milestones[TS_MILESTONE_PLUGIN_ACTIVE] += delta; + } + } +} +} + +ClassAllocator httpSMAllocator("httpSMAllocator"); + +HttpVCTable::HttpVCTable() +{ + memset(&vc_table, 0, sizeof(vc_table)); +} + +HttpVCTableEntry * +HttpVCTable::new_entry() +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc == NULL) { + return vc_table + i; + } + } + + ink_release_assert(0); + return NULL; +} + +HttpVCTableEntry * +HttpVCTable::find_entry(VConnection *vc) +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc == vc) { + return vc_table + i; + } + } + + return NULL; +} + +HttpVCTableEntry * +HttpVCTable::find_entry(VIO *vio) +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].read_vio == vio || vc_table[i].write_vio == vio) { + ink_assert(vc_table[i].vc != NULL); + return vc_table + i; + } + } + + return NULL; +} + +// bool HttpVCTable::remove_entry(HttpVCEntry* e) +// +// Deallocates all buffers from the associated +// entry and re-initializes it's other fields +// for reuse +// +void +HttpVCTable::remove_entry(HttpVCTableEntry *e) +{ + ink_assert(e->vc == NULL || e->in_tunnel); + e->vc = NULL; + e->eos = false; + if (e->read_buffer) { + free_MIOBuffer(e->read_buffer); + e->read_buffer = NULL; + } + if (e->write_buffer) { + free_MIOBuffer(e->write_buffer); + e->write_buffer = NULL; + } + e->read_vio = NULL; + e->write_vio = NULL; + e->vc_handler = NULL; + e->vc_type = HTTP_UNKNOWN; + e->in_tunnel = false; +} + +// bool HttpVCTable::cleanup_entry(HttpVCEntry* e) +// +// Closes the associate vc for the entry, +// and the call remove_entry +// +void +HttpVCTable::cleanup_entry(HttpVCTableEntry *e) +{ + ink_assert(e->vc); + if (e->in_tunnel == false) { + // Update stats + switch (e->vc_type) { + case HTTP_UA_VC: + // HTTP_DECREMENT_DYN_STAT(http_current_client_transactions_stat); + break; + default: + // This covers: + // HTTP_UNKNOWN, HTTP_SERVER_VC, HTTP_TRANSFORM_VC, HTTP_CACHE_READ_VC, + // HTTP_CACHE_WRITE_VC, HTTP_RAW_SERVER_VC + break; + } + + e->vc->do_io_close(); + e->vc = NULL; + } + remove_entry(e); +} + +void +HttpVCTable::cleanup_all() +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc != NULL) { + cleanup_entry(vc_table + i); + } + } +} + +#define REMEMBER_EVENT_FILTER(e) 1 + +#define __REMEMBER(x) #x +#define _REMEMBER(x) __REMEMBER(x) + +#define RECORD_FILE_LINE() history[pos].fileline = __FILE__ ":" _REMEMBER(__LINE__); + +#define REMEMBER(e, r) \ + { \ + if (REMEMBER_EVENT_FILTER(e)) { \ + add_history_entry(__FILE__ ":" _REMEMBER(__LINE__), e, r); \ + } \ + } + +#define DebugSM(tag, ...) DebugSpecific(debug_on, tag, __VA_ARGS__) + +#ifdef STATE_ENTER +#undef STATE_ENTER +#endif +#define STATE_ENTER(state_name, event) \ + { \ + /*ink_assert (magic == HTTP_SM_MAGIC_ALIVE); */ REMEMBER(event, reentrancy_count); \ + DebugSM("http", "[%" PRId64 "] [%s, %s]", sm_id, #state_name, HttpDebugNames::get_event_name(event)); \ + } + +#define HTTP_SM_SET_DEFAULT_HANDLER(_h) \ + { \ + REMEMBER(-1, reentrancy_count); \ + default_handler = _h; \ + } + +static int next_sm_id = 0; + +HttpSM::HttpSM() + : Continuation(NULL), + sm_id(-1), + magic(HTTP_SM_MAGIC_DEAD), + // YTS Team, yamsat Plugin + enable_redirection(false), + redirect_url(NULL), + redirect_url_len(0), + redirection_tries(0), + transfered_bytes(0), + post_failed(false), + debug_on(false), + plugin_tunnel_type(HTTP_NO_PLUGIN_TUNNEL), + plugin_tunnel(NULL), + reentrancy_count(0), + history_pos(0), + tunnel(), + ua_entry(NULL), + ua_session(NULL), + background_fill(BACKGROUND_FILL_NONE), + ua_raw_buffer_reader(NULL), + server_entry(NULL), + server_session(NULL), + will_be_private_ss(false), + shared_session_retries(0), + server_buffer_reader(NULL), + transform_info(), + post_transform_info(), + has_active_plugin_agents(false), + second_cache_sm(NULL), + default_handler(NULL), + pending_action(NULL), + historical_action(NULL), + last_action(HttpTransact::SM_ACTION_UNDEFINED), + // TODO: Now that bodies can be empty, should the body counters be set to -1 ? TS-2213 + client_request_hdr_bytes(0), + client_request_body_bytes(0), + server_request_hdr_bytes(0), + server_request_body_bytes(0), + server_response_hdr_bytes(0), + server_response_body_bytes(0), + client_response_hdr_bytes(0), + client_response_body_bytes(0), + cache_response_hdr_bytes(0), + cache_response_body_bytes(0), + pushed_response_hdr_bytes(0), + pushed_response_body_bytes(0), + client_tcp_reused(false), + client_ssl_reused(false), + client_connection_is_ssl(false), + client_sec_protocol("-"), + client_cipher_suite("-"), + server_transact_count(0), + server_connection_is_ssl(false), + plugin_tag(0), + plugin_id(0), + hooks_set(false), + cur_hook_id(TS_HTTP_LAST_HOOK), + cur_hook(NULL), + cur_hooks(0), + callout_state(HTTP_API_NO_CALLOUT), + terminate_sm(false), + kill_this_async_done(false), + parse_range_done(false) +{ + memset(&history, 0, sizeof(history)); + memset(&vc_table, 0, sizeof(vc_table)); + memset(&http_parser, 0, sizeof(http_parser)); +} + +void +HttpSM::cleanup() +{ + t_state.destroy(); + api_hooks.clear(); + http_parser_clear(&http_parser); + + // t_state.content_control.cleanup(); + + HttpConfig::release(t_state.http_config_param); + + mutex.clear(); + tunnel.mutex.clear(); + cache_sm.mutex.clear(); + transform_cache_sm.mutex.clear(); + if (second_cache_sm) { + second_cache_sm->mutex.clear(); + delete second_cache_sm; + } + magic = HTTP_SM_MAGIC_DEAD; + debug_on = false; +} + +void +HttpSM::destroy() +{ + cleanup(); + httpSMAllocator.free(this); +} + +void +HttpSM::init() +{ + milestones[TS_MILESTONE_SM_START] = Thread::get_hrtime(); + + magic = HTTP_SM_MAGIC_ALIVE; + sm_id = 0; + + // Unique state machine identifier. + // changed next_sm_id from int64_t to int because + // atomic(32) is faster than atomic64. The id is just + // for debugging, so it's OK if it wraps every few days, + // as long as the http_info bucket hash still works. + // (To test this, initialize next_sm_id to 0x7ffffff0) + // Leaving sm_id as int64_t to minimize code changes. + + sm_id = (int64_t)ink_atomic_increment((&next_sm_id), 1); + t_state.state_machine_id = sm_id; + t_state.state_machine = this; + + t_state.http_config_param = HttpConfig::acquire(); + + // Simply point to the global config for the time being, no need to copy this + // entire struct if nothing is going to change it. + t_state.txn_conf = &t_state.http_config_param->oride; + + // update the cache info config structure so that + // selection from alternates happens correctly. + t_state.cache_info.config.cache_global_user_agent_header = t_state.txn_conf->global_user_agent_header ? true : false; + t_state.cache_info.config.ignore_accept_mismatch = t_state.http_config_param->ignore_accept_mismatch; + t_state.cache_info.config.ignore_accept_language_mismatch = t_state.http_config_param->ignore_accept_language_mismatch; + t_state.cache_info.config.ignore_accept_encoding_mismatch = t_state.http_config_param->ignore_accept_encoding_mismatch; + t_state.cache_info.config.ignore_accept_charset_mismatch = t_state.http_config_param->ignore_accept_charset_mismatch; + t_state.cache_info.config.cache_enable_default_vary_headers = + t_state.http_config_param->cache_enable_default_vary_headers ? true : false; + + t_state.cache_info.config.cache_vary_default_text = t_state.http_config_param->cache_vary_default_text; + t_state.cache_info.config.cache_vary_default_images = t_state.http_config_param->cache_vary_default_images; + t_state.cache_info.config.cache_vary_default_other = t_state.http_config_param->cache_vary_default_other; + + t_state.init(); + t_state.srv_lookup = hostdb_srv_enabled; + + // Added to skip dns if the document is in cache. DNS will be forced if there is a ip based ACL in + // cache control or parent.config or if the doc_in_cache_skip_dns is disabled or if http caching is disabled + // TODO: This probably doesn't honor this as a per-transaction overridable config. + t_state.force_dns = (ip_rule_in_CacheControlTable() || t_state.parent_params->parent_table->ipMatch || + !(t_state.txn_conf->doc_in_cache_skip_dns) || !(t_state.txn_conf->cache_http)); + + http_parser.m_allow_non_http = t_state.http_config_param->parser_allow_non_http; + http_parser_init(&http_parser); + + SET_HANDLER(&HttpSM::main_handler); + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_sm_list_mutex); + debug_sm_list.push(this); + ink_mutex_release(&debug_sm_list_mutex); +#endif +} + +void +HttpSM::set_ua_half_close_flag() +{ + ua_session->set_half_close_flag(true); +} + +inline void +HttpSM::do_api_callout() +{ + if (hooks_set) { + do_api_callout_internal(); + } else { + handle_api_return(); + } +} + +int +HttpSM::state_add_to_list(int event, void * /* data ATS_UNUSED */) +{ + // The list if for stat pages and general debugging + // The config variable exists mostly to allow us to + // measure an performance drop during benchmark runs + if (t_state.http_config_param->enable_http_info) { + STATE_ENTER(&HttpSM::state_add_to_list, event); + ink_assert(event == EVENT_NONE || event == EVENT_INTERVAL); + + int bucket = ((unsigned int)sm_id % HTTP_LIST_BUCKETS); + + MUTEX_TRY_LOCK(lock, HttpSMList[bucket].mutex, mutex->thread_holding); + // the client_vc`s timeout events can be triggered, so we should not + // reschedule the http_sm when the lock is not acquired. + // FIXME: the sm_list may miss some http_sms when the lock contention + if (lock.is_locked()) + HttpSMList[bucket].sm_list.push(this); + } + + t_state.api_next_action = HttpTransact::SM_ACTION_API_SM_START; + do_api_callout(); + return EVENT_DONE; +} + +int +HttpSM::state_remove_from_list(int event, void * /* data ATS_UNUSED */) +{ + // The config parameters are guaranteed not change + // across the life of a transaction so it safe to + // check the config here and use it detrmine + // whether we need to strip ourselves off of the + // state page list + if (t_state.http_config_param->enable_http_info) { + STATE_ENTER(&HttpSM::state_remove_from_list, event); + ink_assert(event == EVENT_NONE || event == EVENT_INTERVAL); + + int bucket = ((unsigned int)sm_id % HTTP_LIST_BUCKETS); + + MUTEX_TRY_LOCK(lock, HttpSMList[bucket].mutex, mutex->thread_holding); + if (!lock.is_locked()) { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_remove_from_list); + mutex->thread_holding->schedule_in(this, HTTP_LIST_RETRY); + return EVENT_DONE; + } + + HttpSMList[bucket].sm_list.remove(this); + } + + return this->kill_this_async_hook(EVENT_NONE, NULL); +} + +int +HttpSM::kill_this_async_hook(int /* event ATS_UNUSED */, void * /* data ATS_UNUSED */) +{ + // In the base HttpSM, we don't have anything to + // do here. subclasses can override this function + // to do their own asynchronous cleanup + // So We're now ready to finish off the state machine + terminate_sm = true; + kill_this_async_done = true; + + return EVENT_DONE; +} + +void +HttpSM::start_sub_sm() +{ + tunnel.init(this, mutex); + cache_sm.init(this, mutex); + transform_cache_sm.init(this, mutex); +} + +void +HttpSM::attach_client_session(ProxyClientTransaction *client_vc, IOBufferReader *buffer_reader) +{ + milestones[TS_MILESTONE_UA_BEGIN] = Thread::get_hrtime(); + ink_assert(client_vc != NULL); + + NetVConnection *netvc = client_vc->get_netvc(); + if (!netvc) + return; + ua_session = client_vc; + + // Collect log & stats information + client_tcp_reused = (1 < ua_session->get_transact_count()); + SSLNetVConnection *ssl_vc = dynamic_cast(netvc); + if (ssl_vc != NULL) { + client_connection_is_ssl = true; + client_ssl_reused = ssl_vc->getSSLSessionCacheHit(); + const char *protocol = ssl_vc->getSSLProtocol(); + client_sec_protocol = protocol ? protocol : "-"; + const char *cipher = ssl_vc->getSSLCipherSuite(); + client_cipher_suite = cipher ? cipher : "-"; + } + + ink_release_assert(ua_session->get_half_close_flag() == false); + mutex = client_vc->mutex; + if (ua_session->debug()) + debug_on = true; + + start_sub_sm(); + + // Allocate a user agent entry in the state machine's + // vc table + ua_entry = vc_table.new_entry(); + ua_entry->vc = client_vc; + ua_entry->vc_type = HTTP_UA_VC; + + ats_ip_copy(&t_state.client_info.src_addr, netvc->get_remote_addr()); + ats_ip_copy(&t_state.client_info.dst_addr, netvc->get_local_addr()); + t_state.client_info.dst_addr.port() = netvc->get_local_port(); + t_state.client_info.is_transparent = netvc->get_is_transparent(); + t_state.backdoor_request = !client_vc->hooks_enabled(); + t_state.client_info.port_attribute = static_cast(netvc->attributes); + + HTTP_INCREMENT_DYN_STAT(http_current_client_transactions_stat); + + // Record api hook set state + hooks_set = client_vc->has_hooks(); + + // Setup for parsing the header + ua_buffer_reader = buffer_reader; + ua_entry->vc_handler = &HttpSM::state_read_client_request_header; + t_state.hdr_info.client_request.destroy(); + t_state.hdr_info.client_request.create(HTTP_TYPE_REQUEST); + http_parser_init(&http_parser); + + // Prepare raw reader which will live until we are sure this is HTTP indeed + if (is_transparent_passthrough_allowed()) { + ua_raw_buffer_reader = buffer_reader->clone(); + } + + // We first need to run the transaction start hook. Since + // this hook maybe asynchronous, we need to disable IO on + // client but set the continuation to be the state machine + // so if we get an timeout events the sm handles them + ua_entry->read_vio = client_vc->do_io_read(this, 0, buffer_reader->mbuf); + + ///////////////////////// + // set up timeouts // + ///////////////////////// + client_vc->set_inactivity_timeout(HRTIME_SECONDS(t_state.http_config_param->accept_no_activity_timeout)); + client_vc->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_in)); + + ++reentrancy_count; + // Add our state sm to the sm list + state_add_to_list(EVENT_NONE, NULL); + // This is another external entry point and it is possible for the state machine to get terminated + // while down the call chain from @c state_add_to_list. So we need to use the reentrancy_count to + // prevent cleanup there and do it here as we return to the external caller. + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + --reentrancy_count; + ink_assert(reentrancy_count >= 0); + } +} + +void +HttpSM::setup_client_read_request_header() +{ + ink_assert(ua_entry->vc_handler == &HttpSM::state_read_client_request_header); + + ua_entry->read_vio = ua_session->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + // The header may already be in the buffer if this + // a request from a keep-alive connection + handleEvent(VC_EVENT_READ_READY, ua_entry->read_vio); +} + +void +HttpSM::setup_blind_tunnel_port() +{ + // We gotten a connect on a port for blind tunneling so + // call transact figure out where it is going + call_transact_and_set_next_state(HttpTransact::HandleBlindTunnel); +} + +int +HttpSM::state_read_client_request_header(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_read_client_request_header, event); + + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(server_entry == NULL); + ink_assert(server_session == NULL); + + int bytes_used = 0; + ink_assert(ua_entry->eos == false); + + NetVConnection *netvc = ua_session->get_netvc(); + if (!netvc && event != VC_EVENT_EOS) + return 0; + + // check to see if there was an EOS received on the SSL connection + SSLNetVConnection *ssl_vc = dynamic_cast(netvc); + if (ssl_vc && ssl_vc->isEosRcvd()) { + DebugSM("http", "EOS for ssl vc %p at read_first_btye state", netvc); + event = VC_EVENT_EOS; + } + switch (event) { + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_EOS: + ua_entry->eos = true; + if ((client_request_hdr_bytes > 0) && is_transparent_passthrough_allowed() && (ua_raw_buffer_reader != NULL)) { + break; + } + // Fall through + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The user agent is hosed. Close it & + // bail on the state machine + vc_table.cleanup_entry(ua_entry); + ua_entry = NULL; + t_state.client_info.abort = HttpTransact::ABORTED; + terminate_sm = true; + return 0; + } + + // Reset the inactivity timeout if this is the first + // time we've been called. The timeout had been set to + // the accept timeout by the ProxyClientTransaction + // + if ((ua_buffer_reader->read_avail() > 0) && (client_request_hdr_bytes == 0)) { + milestones[TS_MILESTONE_UA_FIRST_READ] = Thread::get_hrtime(); + ua_session->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + } + ///////////////////// + // tokenize header // + ///////////////////// + + MIMEParseResult state = t_state.hdr_info.client_request.parse_req(&http_parser, ua_buffer_reader, &bytes_used, ua_entry->eos); + + client_request_hdr_bytes += bytes_used; + + // Check to see if we are over the hdr size limit + if (client_request_hdr_bytes > t_state.txn_conf->request_hdr_max_size) { + DebugSM("http", "client header bytes were over max header size; treating as a bad request"); + state = PARSE_ERROR; + } + + // We need to handle EOS as well as READ_READY because the client + // may have sent all of the data already followed by a fIN and that + // should be OK. + if (is_transparent_passthrough_allowed() && ua_raw_buffer_reader != NULL) { + bool do_blind_tunnel = false; + // If we had a parse error and we're done reading data + // blind tunnel + if ((event == VC_EVENT_READ_READY || event == VC_EVENT_EOS) && state == PARSE_ERROR) { + do_blind_tunnel = true; + + // If we had a GET request that has data after the + // get request, do blind tunnel + } else if (state == PARSE_DONE && t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_GET && + ua_raw_buffer_reader->read_avail() > 0 && !t_state.hdr_info.client_request.is_keep_alive_set()) { + do_blind_tunnel = true; + } + if (do_blind_tunnel) { + DebugSM("http", "[%" PRId64 "] first request on connection failed parsing, switching to passthrough.", sm_id); + + t_state.transparent_passthrough = true; + http_parser_clear(&http_parser); + + // Turn off read eventing until we get the + // blind tunnel infrastructure set up + if (netvc) + netvc->do_io_read(this, 0, NULL); + + /* establish blind tunnel */ + setup_blind_tunnel_port(); + + // Setting half close means we will send the FIN when we've written all of the data. + if (event == VC_EVENT_EOS) { + this->set_ua_half_close_flag(); + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + } + return 0; + } + } + + // Check to see if we are done parsing the header + if (state != PARSE_CONT || ua_entry->eos || (state == PARSE_CONT && event == VC_EVENT_READ_COMPLETE)) { + if (ua_raw_buffer_reader != NULL) { + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = NULL; + } + http_parser_clear(&http_parser); + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_ERROR: + DebugSM("http", "[%" PRId64 "] error parsing client request header", sm_id); + + // Disable further I/O on the client + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + + case PARSE_CONT: + if (ua_entry->eos) { + DebugSM("http_seq", "[%" PRId64 "] EOS before client request parsing finished", sm_id); + set_ua_abort(HttpTransact::ABORTED, event); + + // Disable further I/O on the client + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + + call_transact_and_set_next_state(HttpTransact::BadRequest); + break; + } else if (event == VC_EVENT_READ_COMPLETE) { + DebugSM("http_parse", "[%" PRId64 "] VC_EVENT_READ_COMPLETE and PARSE CONT state", sm_id); + break; + } else { + if (is_transparent_passthrough_allowed() && ua_raw_buffer_reader != NULL && + ua_raw_buffer_reader->get_current_block()->write_avail() <= 0) { + // Disable passthrough regardless of eventual parsing failure or success -- otherwise + // we either have to consume some data or risk blocking the writer. + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = NULL; + } + ua_entry->read_vio->reenable(); + return VC_EVENT_CONT; + } + case PARSE_DONE: + DebugSM("http", "[%" PRId64 "] done parsing client request header", sm_id); + + if (ua_session->m_active == false) { + ua_session->m_active = true; + HTTP_INCREMENT_DYN_STAT(http_current_active_client_connections_stat); + } + + if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1) && + (t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_POST || + t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_PUT) && + t_state.http_config_param->send_100_continue_response) { + int len = 0; + const char *expect = t_state.hdr_info.client_request.value_get(MIME_FIELD_EXPECT, MIME_LEN_EXPECT, &len); + // When receive an "Expect: 100-continue" request from client, ATS sends a "100 Continue" response to client + // imediately, before receive the real response from original server. + if ((len == HTTP_LEN_100_CONTINUE) && (strncasecmp(expect, HTTP_VALUE_100_CONTINUE, HTTP_LEN_100_CONTINUE) == 0)) { + int64_t alloc_index = buffer_size_to_index(len_100_continue_response); + if (ua_entry->write_buffer) { + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = NULL; + } + ua_entry->write_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = ua_entry->write_buffer->alloc_reader(); + + DebugSM("http_seq", "send 100 Continue response to client"); + int64_t nbytes = ua_entry->write_buffer->write(str_100_continue_response, len_100_continue_response); + ua_session->do_io_write(netvc, nbytes, buf_start); + } + } + + if (t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_TRACE || + (t_state.hdr_info.request_content_length == 0 && t_state.client_info.transfer_encoding != HttpTransact::CHUNKED_ENCODING)) { + // Enable further IO to watch for client aborts + ua_entry->read_vio->reenable(); + } else { + // Disable further I/O on the client since there could + // be body that we are tunneling POST/PUT/CONNECT or + // extension methods and we can't issue another + // another IO later for the body with a different buffer + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + } + // YTS Team, yamsat Plugin + // Setting enable_redirection according to HttpConfig master + if ((t_state.txn_conf->number_of_redirections > 0) || + (t_state.method == HTTP_WKSIDX_POST && HttpConfig::m_master.post_copy_size)) + enable_redirection = t_state.txn_conf->redirection_enabled; + + call_transact_and_set_next_state(HttpTransact::ModifyRequest); + + break; + default: + ink_assert(!"not reached"); + } + + return 0; +} + +#ifdef PROXY_DRAIN +int +HttpSM::state_drain_client_request_body(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_drain_client_request_body, event); + + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(ua_entry->vc == ua_session); + + NetVConnection *netvc = ua_session->get_netvc(); + if (!netvc && event != VC_EVENT_EOS) + return 0; + + switch (event) { + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: { + // Nothing we can do + terminate_sm = true; + break; + } + case VC_EVENT_READ_READY: { + int64_t avail = ua_buffer_reader->read_avail(); + int64_t left = t_state.hdr_info.request_content_length - client_request_body_bytes; + + // Since we are only reading what's needed to complete + // the post, there must be something left to do + ink_assert(avail < left); + + client_request_body_bytes += avail; + ua_buffer_reader->consume(avail); + ua_entry->read_vio->reenable_re(); + break; + } + case VC_EVENT_READ_COMPLETE: { + // We've finished draing the POST body + int64_t avail = ua_buffer_reader->read_avail(); + + ua_buffer_reader->consume(avail); + client_request_body_bytes += avail; + ink_assert(client_request_body_bytes == t_state.hdr_info.request_content_length); + + ua_buffer_reader->mbuf->size_index = HTTP_HEADER_BUFFER_SIZE_INDEX; + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + ua_entry->read_vio = ua_entry->vc->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + call_transact_and_set_next_state(NULL); + break; + } + default: + ink_release_assert(0); + } + + return EVENT_DONE; +} +#endif /* PROXY_DRAIN */ + +int +HttpSM::state_watch_for_client_abort(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_watch_for_client_abort, event); + + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(ua_entry->vc == ua_session); + + switch (event) { + /* EOS means that the client has initiated the connection shut down. + * Only half close the client connection so ATS can read additional + * data that may still be sent from the server and send it to the + * client. + */ + case VC_EVENT_EOS: { + // We got an early EOS. + NetVConnection *netvc = ua_session->get_netvc(); + if (ua_session->allow_half_open()) { + if (netvc) + netvc->do_io_shutdown(IO_SHUTDOWN_READ); + ua_entry->eos = true; + } else { + if (netvc) + netvc->do_io_close(); + ua_session->do_io_close(); + ua_session = NULL; + ua_buffer_reader = NULL; + vc_table.cleanup_entry(ua_entry); + ua_entry = NULL; + tunnel.kill_tunnel(); + terminate_sm = true; // Just die already, the requester is gone + } + break; + } + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: { + if (tunnel.is_tunnel_active()) { + // Check to see if the user agent is part of the tunnel. + // If so forward the event to the tunnel. Otherwise, + // kill the tunnel and fallthrough to the case + // where the tunnel is not active + HttpTunnelConsumer *c = tunnel.get_consumer(ua_session); + if (c && c->alive) { + DebugSM("http", "[%" PRId64 "] [watch_for_client_abort] " + "forwarding event %s to tunnel", + sm_id, HttpDebugNames::get_event_name(event)); + tunnel.handleEvent(event, c->write_vio); + return 0; + } else { + tunnel.kill_tunnel(); + } + } + // Disable further I/O on the client + if (ua_entry->read_vio) { + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + } + mark_server_down_on_client_abort(); + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + set_ua_abort(HttpTransact::ABORTED, event); + + terminate_sm = true; + break; + } + case VC_EVENT_READ_COMPLETE: + // XXX Work around for TS-1233. + case VC_EVENT_READ_READY: + // Ignore. Could be a pipelined request. We'll get to it + // when we finish the current transaction + break; + default: + ink_release_assert(0); + break; + } + + return 0; +} + +void +HttpSM::setup_push_read_response_header() +{ + ink_assert(server_session == NULL); + ink_assert(server_entry == NULL); + ink_assert(ua_session != NULL); + ink_assert(t_state.method == HTTP_WKSIDX_PUSH); + + // Set the handler to read the pushed response hdr + ua_entry->vc_handler = &HttpSM::state_read_push_response_header; + + // We record both the total payload size as + // client_request_body_bytes and the bytes for the individual + // pushed hdr and body components + pushed_response_hdr_bytes = 0; + client_request_body_bytes = 0; + + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + http_parser_clear(&http_parser); + + // We already done the READ when we read the client + // request header + ink_assert(ua_entry->read_vio); + + // If there is anything in the buffer call the parsing routines + // since if the response is finished, we won't get any + // additional callbacks + int resp_hdr_state = VC_EVENT_CONT; + if (ua_buffer_reader->read_avail() > 0) { + if (ua_entry->eos) { + resp_hdr_state = state_read_push_response_header(VC_EVENT_EOS, ua_entry->read_vio); + } else { + resp_hdr_state = state_read_push_response_header(VC_EVENT_READ_READY, ua_entry->read_vio); + } + } + // It is possible that the entire PUSHed response header was already + // in the buffer. In this case we don't want to fire off any more + // IO since we are going to switch buffers when we go to tunnel to + // the cache + if (resp_hdr_state == VC_EVENT_CONT) { + ink_assert(ua_entry->eos == false); + ua_entry->read_vio = ua_session->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + } +} + +int +HttpSM::state_read_push_response_header(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_read_push_response_header, event); + ink_assert(ua_entry->read_vio == (VIO *)data); + ink_assert(t_state.current.server == NULL); + + int64_t data_size = 0; + int64_t bytes_used = 0; + + // Not used here. + // bool parse_error = false; + // VIO* vio = (VIO*) data; + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + // Fall through + + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The user agent is hosed. Send an error + t_state.client_info.abort = HttpTransact::ABORTED; + call_transact_and_set_next_state(HttpTransact::HandleBadPushRespHdr); + return 0; + } + + int state = PARSE_CONT; + while (ua_buffer_reader->read_avail() && state == PARSE_CONT) { + const char *start = ua_buffer_reader->start(); + const char *tmp = start; + data_size = ua_buffer_reader->block_read_avail(); + ink_assert(data_size >= 0); + + ///////////////////// + // tokenize header // + ///////////////////// + state = + t_state.hdr_info.server_response.parse_resp(&http_parser, &tmp, tmp + data_size, false // Only call w/ eof when data exhausted + ); + + bytes_used = tmp - start; + + ink_release_assert(bytes_used <= data_size); + ua_buffer_reader->consume(bytes_used); + pushed_response_hdr_bytes += bytes_used; + client_request_body_bytes += bytes_used; + } + + // We are out of data. If we've received an EOS we need to + // call the parser with (eof == true) so it can determine + // whether to use the response as is or declare a parse error + if (ua_entry->eos) { + const char *end = ua_buffer_reader->start(); + state = t_state.hdr_info.server_response.parse_resp(&http_parser, &end, end, true // We are out of data after server eos + ); + ink_release_assert(state == PARSE_DONE || state == PARSE_ERROR); + } + // Don't allow 0.9 (unparsable headers) since TS doesn't + // cache 0.9 responses + if (state == PARSE_DONE && t_state.hdr_info.server_response.version_get() == HTTPVersion(0, 9)) { + state = PARSE_ERROR; + } + + if (state != PARSE_CONT) { + // Disable further IO + ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone; + http_parser_clear(&http_parser); + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_ERROR: + DebugSM("http", "[%" PRId64 "] error parsing push response header", sm_id); + call_transact_and_set_next_state(HttpTransact::HandleBadPushRespHdr); + break; + + case PARSE_CONT: + ua_entry->read_vio->reenable(); + return VC_EVENT_CONT; + + case PARSE_DONE: + DebugSM("http", "[%" PRId64 "] done parsing push response header", sm_id); + call_transact_and_set_next_state(HttpTransact::HandlePushResponseHdr); + break; + default: + ink_assert(!"not reached"); + } + + return VC_EVENT_DONE; +} + +////////////////////////////////////////////////////////////////////////////// +// +// HttpSM::state_http_server_open() +// +////////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_raw_http_server_open(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_raw_http_server_open, event); + ink_assert(server_entry == NULL); + milestones[TS_MILESTONE_SERVER_CONNECT_END] = Thread::get_hrtime(); + NetVConnection *netvc = NULL; + + pending_action = NULL; + switch (event) { + case NET_EVENT_OPEN: + + if (t_state.pCongestionEntry != NULL) { + t_state.pCongestionEntry->connection_opened(); + t_state.congestion_connection_opened = 1; + } + // Record the VC in our table + server_entry = vc_table.new_entry(); + server_entry->vc = netvc = (NetVConnection *)data; + server_entry->vc_type = HTTP_RAW_SERVER_VC; + t_state.current.state = HttpTransact::CONNECTION_ALIVE; + + netvc->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + netvc->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_out)); + break; + + case VC_EVENT_ERROR: + case NET_EVENT_OPEN_FAILED: + if (t_state.pCongestionEntry != NULL) { + t_state.current.state = HttpTransact::CONNECTION_ERROR; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + return 0; + } else { + t_state.current.state = HttpTransact::OPEN_RAW_ERROR; + // use this value just to get around other values + t_state.hdr_info.response_error = HttpTransact::STATUS_CODE_SERVER_ERROR; + } + break; + case CONGESTION_EVENT_CONGESTED_ON_F: + t_state.current.state = HttpTransact::CONGEST_CONTROL_CONGESTED_ON_F; + break; + case CONGESTION_EVENT_CONGESTED_ON_M: + t_state.current.state = HttpTransact::CONGEST_CONTROL_CONGESTED_ON_M; + break; + + case EVENT_INTERVAL: + Error("[HttpSM::state_raw_http_server_open] event: EVENT_INTERVAL state: %d server_entry: %p", t_state.current.state, + server_entry); + return 0; + + default: + ink_release_assert(0); + break; + } + + call_transact_and_set_next_state(HttpTransact::OriginServerRawOpen); + return 0; +} + +// int HttpSM::state_request_wait_for_transform_read(int event, void* data) +// +// We've done a successful transform open and issued a do_io_write +// to the transform. We are now ready for the transform to tell +// us it is now ready to be read from and it done modifying the +// server request header +// +int +HttpSM::state_request_wait_for_transform_read(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_request_wait_for_transform_read, event); + int64_t size = *((int64_t *)data); + + switch (event) { + case TRANSFORM_READ_READY: + if (size != INT64_MAX && size >= 0) { + // We got a content length so update our internal + // data as well as fix up the request header + t_state.hdr_info.transform_request_cl = size; + t_state.hdr_info.server_request.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, size); + setup_server_send_request_api(); + break; + } else { + // No content length from the post. This is a no go + // since http spec requires content length when + // sending a request message body. Change the event + // to an error and fall through + event = VC_EVENT_ERROR; + Log::error("Request transformation failed to set content length"); + } + // FALLTHROUGH + default: + state_common_wait_for_transform_read(&post_transform_info, &HttpSM::tunnel_handler_post, event, data); + break; + } + + return 0; +} + +// int HttpSM::state_response_wait_for_transform_read(int event, void* data) +// +// We've done a successful transform open and issued a do_io_write +// to the transform. We are now ready for the transform to tell +// us it is now ready to be read from and it done modifying the +// user agent response header +// +int +HttpSM::state_response_wait_for_transform_read(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_response_wait_for_transform_read, event); + int64_t size = *((int64_t *)data); + + switch (event) { + case TRANSFORM_READ_READY: + if (size != INT64_MAX && size >= 0) { + // We got a content length so update our internal state + t_state.hdr_info.transform_response_cl = size; + t_state.hdr_info.transform_response.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, size); + } else { + t_state.hdr_info.transform_response_cl = HTTP_UNDEFINED_CL; + } + call_transact_and_set_next_state(HttpTransact::handle_transform_ready); + break; + default: + state_common_wait_for_transform_read(&transform_info, &HttpSM::tunnel_handler, event, data); + break; + } + + return 0; +} + +// int HttpSM::state_common_wait_for_transform_read(...) +// +// This function handles the overlapping cases between request and response +// transforms which prevents code duplication +// +int +HttpSM::state_common_wait_for_transform_read(HttpTransformInfo *t_info, HttpSMHandler tunnel_handler, int event, void *data) +{ + STATE_ENTER(&HttpSM::state_common_wait_for_transform_read, event); + HttpTunnelConsumer *c = 0; + + switch (event) { + case HTTP_TUNNEL_EVENT_DONE: + // There are three reasons why the the tunnel could signal completed + // 1) there was error from the transform write + // 2) there was an error from the data source + // 3) the transform write completed before it sent + // TRANSFORM_READ_READY which is legal and in which + // case we should just wait for the transform read ready + c = tunnel.get_consumer(t_info->vc); + ink_assert(c != NULL); + ink_assert(c->vc == t_info->entry->vc); + + if (c->handler_state == HTTP_SM_TRANSFORM_FAIL) { + // Case 1 we failed to complete the write to the + // transform fall through to vc event error case + ink_assert(c->write_success == false); + } else if (c->producer->read_success == false) { + // Case 2 - error from data source + if (c->producer->vc_type == HT_HTTP_CLIENT) { + // Our source is the client. POST can't + // be truncated so forward to the tunnel + // handler to clean this mess up + ink_assert(t_info == &post_transform_info); + return (this->*tunnel_handler)(event, data); + } else { + // On the response side, we just forward as much + // as we can of truncated documents so + // just don't cache the result + ink_assert(t_info == &transform_info); + t_state.api_info.cache_transformed = false; + return 0; + } + } else { + // Case 3 - wait for transform read ready + return 0; + } + // FALLTHROUGH + case VC_EVENT_ERROR: + // Transform VC sends NULL on error conditions + if (!c) { + c = tunnel.get_consumer(t_info->vc); + ink_assert(c != NULL); + } + vc_table.cleanup_entry(t_info->entry); + t_info->entry = NULL; + // In Case 1: error due to transform write, + // we need to keep the original t_info->vc for transform_cleanup() + // to skip do_io_close(); otherwise, set it to NULL. + if (c->handler_state != HTTP_SM_TRANSFORM_FAIL) { + t_info->vc = NULL; + } + tunnel.kill_tunnel(); + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +// int HttpSM::state_api_callback(int event, void *data) + +// InkAPI.cc calls us directly here to avoid problems +// with setting and changing the default_handler +// function. As such, this is an entry point +// and needs to handle the reentrancy counter and +// deallocation the state machine if necessary +// +int +HttpSM::state_api_callback(int event, void *data) +{ + ink_release_assert(magic == HTTP_SM_MAGIC_ALIVE); + + ink_assert(reentrancy_count >= 0); + reentrancy_count++; + + milestone_update_api_time(milestones, api_timer); + + STATE_ENTER(&HttpSM::state_api_callback, event); + + state_api_callout(event, data); + + // The sub-handler signals when it is time for the state + // machine to exit. We can only exit if we are not reentrantly + // called otherwise when the our call unwinds, we will be + // running on a dead state machine + // + // Because of the need for an api shutdown hook, kill_this() + // is also reentrant. As such, we don't want to decrement + // the reentrancy count until after we run kill_this() + // + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + reentrancy_count--; + ink_assert(reentrancy_count >= 0); + } + + return VC_EVENT_CONT; +} + +int +HttpSM::state_api_callout(int event, void *data) +{ + // enum and variable for figuring out what the next action is after + // after we've finished the api state + enum AfterApiReturn_t { + API_RETURN_UNKNOWN = 0, + API_RETURN_CONTINUE, + API_RETURN_DEFERED_CLOSE, + API_RETURN_DEFERED_SERVER_ERROR, + API_RETURN_ERROR_JUMP, + API_RETURN_SHUTDOWN, + API_RETURN_INVALIDATE_ERROR + }; + AfterApiReturn_t api_next = API_RETURN_UNKNOWN; + + if (event != EVENT_NONE) { + STATE_ENTER(&HttpSM::state_api_callout, event); + } + + if (api_timer < 0) { + // This happens when either the plugin lock was missed and the hook rescheduled or + // the transaction got an event without the plugin calling TsHttpTxnReenable(). + // The call chain does not recurse here if @a api_timer < 0 which means this call + // is the first from an event dispatch in this case. + milestone_update_api_time(milestones, api_timer); + } + + switch (event) { + case EVENT_INTERVAL: + ink_assert(pending_action == data); + pending_action = NULL; + // FALLTHROUGH + case EVENT_NONE: + case HTTP_API_CONTINUE: + if ((cur_hook_id >= 0) && (cur_hook_id < TS_HTTP_LAST_HOOK)) { + if (!cur_hook) { + if (cur_hooks == 0) { + cur_hook = http_global_hooks->get(cur_hook_id); + cur_hooks++; + } + } + // even if ua_session is NULL, cur_hooks must + // be incremented otherwise cur_hooks is not set to 2 and + // transaction hooks (stored in api_hooks object) are not called. + if (!cur_hook) { + if (cur_hooks == 1) { + if (ua_session) { + cur_hook = ua_session->ssn_hook_get(cur_hook_id); + } + cur_hooks++; + } + } + if (!cur_hook) { + if (cur_hooks == 2) { + cur_hook = api_hooks.get(cur_hook_id); + cur_hooks++; + } + } + if (cur_hook) { + if (callout_state == HTTP_API_NO_CALLOUT) { + callout_state = HTTP_API_IN_CALLOUT; + } + + /* The MUTEX_TRY_LOCK macro was changed so + that it can't handle NULL mutex'es. The plugins + can use null mutexes so we have to do this manually. + We need to take a smart pointer to the mutex since + the plugin could release it's mutex while we're on + the callout + */ + bool plugin_lock; + Ptr plugin_mutex; + if (cur_hook->m_cont->mutex) { + plugin_mutex = cur_hook->m_cont->mutex; + plugin_lock = MUTEX_TAKE_TRY_LOCK(cur_hook->m_cont->mutex, mutex->thread_holding); + + if (!plugin_lock) { + api_timer = -Thread::get_hrtime_updated(); + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_api_callout); + ink_assert(pending_action == NULL); + pending_action = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); + // Should @a callout_state be reset back to HTTP_API_NO_CALLOUT here? Because the default + // handler has been changed the value isn't important to the rest of the state machine + // but not resetting means there is no way to reliably detect re-entrance to this state with an + // outstanding callout. + return 0; + } + } else { + plugin_lock = false; + } + + DebugSM("http", "[%" PRId64 "] calling plugin on hook %s at hook %p", sm_id, HttpDebugNames::get_api_hook_name(cur_hook_id), + cur_hook); + + APIHook *hook = cur_hook; + cur_hook = cur_hook->next(); + + if (!api_timer) + api_timer = Thread::get_hrtime_updated(); + hook->invoke(TS_EVENT_HTTP_READ_REQUEST_HDR + cur_hook_id, this); + if (api_timer > 0) { // true if the hook did not call TxnReenable() + milestone_update_api_time(milestones, api_timer); + api_timer = -Thread::get_hrtime_updated(); // set in order to track non-active callout duration + // which means that if we get back from the invoke with api_timer < 0 we're already + // tracking a non-complete callout from a chain so just let it ride. It will get cleaned + // up in state_api_callback when the plugin re-enables this transaction. + } + + if (plugin_lock) { + Mutex_unlock(plugin_mutex, mutex->thread_holding); + } + + return 0; + } + } + // Map the callout state into api_next + switch (callout_state) { + case HTTP_API_NO_CALLOUT: + case HTTP_API_IN_CALLOUT: + if (t_state.api_modifiable_cached_resp && t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_update_cached_object = HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE; + } + api_next = API_RETURN_CONTINUE; + break; + case HTTP_API_DEFERED_CLOSE: + api_next = API_RETURN_DEFERED_CLOSE; + break; + case HTTP_API_DEFERED_SERVER_ERROR: + api_next = API_RETURN_DEFERED_SERVER_ERROR; + break; + default: + ink_release_assert(0); + } + break; + + case HTTP_API_ERROR: + if (callout_state == HTTP_API_DEFERED_CLOSE) { + api_next = API_RETURN_DEFERED_CLOSE; + } else if (cur_hook_id == TS_HTTP_TXN_CLOSE_HOOK) { + // If we are closing the state machine, we can't + // jump to an error state so just continue + api_next = API_RETURN_CONTINUE; + } else if (t_state.api_http_sm_shutdown) { + t_state.api_http_sm_shutdown = false; + t_state.cache_info.object_read = NULL; + cache_sm.close_read(); + transform_cache_sm.close_read(); + release_server_session(); + terminate_sm = true; + api_next = API_RETURN_SHUTDOWN; + t_state.squid_codes.log_code = SQUID_LOG_TCP_DENIED; + } else if (t_state.api_modifiable_cached_resp && + t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_update_cached_object = HttpTransact::UPDATE_CACHED_OBJECT_ERROR; + api_next = API_RETURN_INVALIDATE_ERROR; + } else { + api_next = API_RETURN_ERROR_JUMP; + } + break; + + // We may receive an event from the tunnel + // if it took a long time to call the SEND_RESPONSE_HDR hook + case HTTP_TUNNEL_EVENT_DONE: + state_common_wait_for_transform_read(&transform_info, &HttpSM::tunnel_handler, event, data); + return 0; + + default: + ink_assert(false); + terminate_sm = true; + return 0; + } + + // Now that we're completed with the api state and figured out what + // to do next, do it + callout_state = HTTP_API_NO_CALLOUT; + api_timer = 0; + switch (api_next) { + case API_RETURN_CONTINUE: + if (t_state.api_next_action == HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR) { + do_redirect(); + } + handle_api_return(); + break; + case API_RETURN_DEFERED_CLOSE: + ink_assert(t_state.api_next_action == HttpTransact::SM_ACTION_API_SM_SHUTDOWN); + do_api_callout(); + break; + case API_RETURN_DEFERED_SERVER_ERROR: + ink_assert(t_state.api_next_action == HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR); + ink_assert(t_state.current.state != HttpTransact::CONNECTION_ALIVE); + call_transact_and_set_next_state(HttpTransact::HandleResponse); + break; + case API_RETURN_ERROR_JUMP: + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + break; + case API_RETURN_SHUTDOWN: + break; + case API_RETURN_INVALIDATE_ERROR: + do_cache_prepare_update(); + break; + default: + case API_RETURN_UNKNOWN: + ink_release_assert(0); + } + + return 0; +} + +// void HttpSM::handle_api_return() +// +// Figures out what to do after calling api callouts +// have finished. This messy and I would like +// to come up with a cleaner way to handle the api +// return. The way we are doing things also makes a +// mess of set_next_state() +// +void +HttpSM::handle_api_return() +{ + switch (t_state.api_next_action) { + case HttpTransact::SM_ACTION_API_SM_START: + if (t_state.client_info.port_attribute == HttpProxyPort::TRANSPORT_BLIND_TUNNEL) { + setup_blind_tunnel_port(); + } else { + setup_client_read_request_header(); + } + return; + case HttpTransact::SM_ACTION_API_PRE_REMAP: + case HttpTransact::SM_ACTION_API_POST_REMAP: + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + case HttpTransact::SM_ACTION_API_OS_DNS: + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: + if (t_state.api_next_action == HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE && t_state.api_cleanup_cache_read && + t_state.api_update_cached_object != HttpTransact::UPDATE_CACHED_OBJECT_PREPARE) { + t_state.api_cleanup_cache_read = false; + t_state.cache_info.object_read = NULL; + t_state.request_sent_time = UNDEFINED_TIME; + t_state.response_received_time = UNDEFINED_TIME; + cache_sm.close_read(); + transform_cache_sm.close_read(); + } + call_transact_and_set_next_state(NULL); + return; + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + setup_server_send_request(); + return; + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + // Set back the inactivity timeout + if (ua_session) { + ua_session->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + } + // we have further processing to do + // based on what t_state.next_action is + break; + case HttpTransact::SM_ACTION_API_SM_SHUTDOWN: + state_remove_from_list(EVENT_NONE, NULL); + return; + default: + ink_release_assert("! Not reached"); + break; + } + + switch (t_state.next_action) { + case HttpTransact::SM_ACTION_TRANSFORM_READ: { + HttpTunnelProducer *p = setup_transfer_from_transform(); + perform_transform_cache_write_action(); + tunnel.tunnel_run(p); + break; + } + case HttpTransact::SM_ACTION_SERVER_READ: { + if (unlikely(t_state.did_upgrade_succeed)) { + // We've sucessfully handled the upgrade, let's now setup + // a blind tunnel. + if (t_state.is_websocket) { + HTTP_INCREMENT_DYN_STAT(http_websocket_current_active_client_connections_stat); + + if (ua_session) { + DebugSM("http_websocket", + "(client session) Setting websocket active timeout=%" PRId64 "s and inactive timeout=%" PRId64 "s", + t_state.txn_conf->websocket_active_timeout, t_state.txn_conf->websocket_inactive_timeout); + ua_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout)); + ua_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout)); + } + + if (server_session) { + DebugSM("http_websocket", + "(server session) Setting websocket active timeout=%" PRId64 "s and inactive timeout=%" PRId64 "s", + t_state.txn_conf->websocket_active_timeout, t_state.txn_conf->websocket_inactive_timeout); + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout)); + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout)); + } + } + + setup_blind_tunnel(true); + } else { + HttpTunnelProducer *p = setup_server_transfer(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } + break; + } + case HttpTransact::SM_ACTION_SERVE_FROM_CACHE: { + HttpTunnelProducer *p = setup_cache_read_transfer(); + tunnel.tunnel_run(p); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_WRITE: { + if (cache_sm.cache_write_vc) { + setup_internal_transfer(&HttpSM::tunnel_handler_cache_fill); + } else { + setup_internal_transfer(&HttpSM::tunnel_handler); + } + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_NOOP: + case HttpTransact::SM_ACTION_INTERNAL_CACHE_DELETE: + case HttpTransact::SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS: + case HttpTransact::SM_ACTION_SEND_ERROR_CACHE_NOOP: { + setup_internal_transfer(&HttpSM::tunnel_handler); + break; + } + + case HttpTransact::SM_ACTION_REDIRECT_READ: { + // Clean up from any communication with previous servers + release_server_session(); + + call_transact_and_set_next_state(HttpTransact::HandleRequest); + break; + } + case HttpTransact::SM_ACTION_SSL_TUNNEL: { + setup_blind_tunnel(true); + break; + } + default: { + ink_release_assert(!"Should not get here"); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// HttpSM::state_http_server_open() +// +////////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_http_server_open(int event, void *data) +{ + DebugSM("http_track", "entered inside state_http_server_open"); + STATE_ENTER(&HttpSM::state_http_server_open, event); + // TODO decide whether to uncomment after finish testing redirect + // ink_assert(server_entry == NULL); + pending_action = NULL; + milestones[TS_MILESTONE_SERVER_CONNECT_END] = Thread::get_hrtime(); + HttpServerSession *session; + + switch (event) { + case NET_EVENT_OPEN: + session = (TS_SERVER_SESSION_SHARING_POOL_THREAD == t_state.http_config_param->server_session_sharing_pool) ? + THREAD_ALLOC_INIT(httpServerSessionAllocator, mutex->thread_holding) : + httpServerSessionAllocator.alloc(); + session->sharing_pool = static_cast(t_state.http_config_param->server_session_sharing_pool); + session->sharing_match = static_cast(t_state.txn_conf->server_session_sharing_match); + // If origin_max_connections or origin_min_keep_alive_connections is + // set then we are metering the max and or min number + // of connections per host. Set enable_origin_connection_limiting + // to true in the server session so it will increment and decrement + // the connection count. + if (t_state.txn_conf->origin_max_connections > 0 || t_state.http_config_param->origin_min_keep_alive_connections > 0) { + DebugSM("http_ss", "[%" PRId64 "] max number of connections: %" PRIu64, sm_id, t_state.txn_conf->origin_max_connections); + session->enable_origin_connection_limiting = true; + } + /*UnixNetVConnection * vc = (UnixNetVConnection*)(ua_session->client_vc); + UnixNetVConnection *server_vc = (UnixNetVConnection*)data; + printf("client fd is :%d , server fd is %d\n",vc->con.fd, + server_vc->con.fd); */ + session->attach_hostname(t_state.current.server->name); + ats_ip_copy(&session->server_ip, &t_state.current.server->dst_addr); + session->new_connection(static_cast(data)); + session->state = HSS_ACTIVE; + + attach_server_session(session); + if (t_state.current.request_to == HttpTransact::PARENT_PROXY) { + session->to_parent_proxy = true; + HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat); + HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat); + + } else { + session->to_parent_proxy = false; + } + handle_http_server_open(); + return 0; + case EVENT_INTERVAL: // Delayed call from another thread + if (server_session == NULL) { + do_http_server_open(); + } + break; + case VC_EVENT_ERROR: + case NET_EVENT_OPEN_FAILED: + t_state.current.state = HttpTransact::CONNECTION_ERROR; + // save the errno from the connect fail for future use (passed as negative value, flip back) + t_state.current.server->set_connect_fail(event == NET_EVENT_OPEN_FAILED ? -reinterpret_cast(data) : ECONNABORTED); + + /* If we get this error, then we simply can't bind to the 4-tuple to make the connection. There's no hope of + retries succeeding in the near future. The best option is to just shut down the connection without further + comment. The only known cause for this is outbound transparency combined with use client target address / source + port, as noted in TS-1424. If the keep alives desync the current connection can be attempting to rebind the 4 + tuple simultaneously with the shut down of an existing connection. Dropping the client side will cause it to pick + a new source port and recover from this issue. + */ + if (EADDRNOTAVAIL == t_state.current.server->connect_result) { + if (is_debug_tag_set("http_tproxy")) { + ip_port_text_buffer ip_c, ip_s; + Debug("http_tproxy", "Force close of client connect (%s->%s) due to EADDRNOTAVAIL [%" PRId64 "]", + ats_ip_nptop(&t_state.client_info.src_addr.sa, ip_c, sizeof ip_c), + ats_ip_nptop(&t_state.server_info.dst_addr.sa, ip_s, sizeof ip_s), sm_id); + } + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; // part of the problem, clear it. + terminate_sm = true; + } else { + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + return 0; + case CONGESTION_EVENT_CONGESTED_ON_F: + t_state.current.state = HttpTransact::CONGEST_CONTROL_CONGESTED_ON_F; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + return 0; + case CONGESTION_EVENT_CONGESTED_ON_M: + t_state.current.state = HttpTransact::CONGEST_CONTROL_CONGESTED_ON_M; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + return 0; + + default: + Error("[HttpSM::state_http_server_open] Unknown event: %d", event); + ink_release_assert(0); + return 0; + } + + return 0; +} + +int +HttpSM::state_read_server_response_header(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_read_server_response_header, event); + ink_assert(server_entry->read_vio == (VIO *)data); + ink_assert(t_state.current.server->state == HttpTransact::STATE_UNDEFINED); + ink_assert(t_state.current.state == HttpTransact::STATE_UNDEFINED); + + int bytes_used = 0; + VIO *vio = (VIO *)data; + + switch (event) { + case VC_EVENT_EOS: + server_entry->eos = true; + + // If no bytes were transmitted, the parser treats + // as a good 0.9 response which is technically is + // but it's indistinguishable from an overloaded + // server closing the connection so don't accept + // zero length responses + if (vio->ndone == 0) { + // Error handling function + handle_server_setup_error(event, data); + return 0; + } + // Fall through + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + // More data to parse + break; + + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // Error handling function + handle_server_setup_error(event, data); + return 0; + } + + // Reset the inactivity timeout if this is the first + // time we've been called. The timeout had been set to + // the connect timeout when we set up to read the header + // + if (server_response_hdr_bytes == 0) { + milestones[TS_MILESTONE_SERVER_FIRST_READ] = Thread::get_hrtime(); + + if (t_state.api_txn_no_activity_timeout_value != -1) { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_MSECONDS(t_state.api_txn_no_activity_timeout_value)); + } else { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + } + + // For requests that contain a body, we can cancel the ua inactivity timeout. + if (ua_session && t_state.hdr_info.request_content_length) { + ua_session->cancel_inactivity_timeout(); + } + } + ///////////////////// + // tokenize header // + ///////////////////// + MIMEParseResult state = + t_state.hdr_info.server_response.parse_resp(&http_parser, server_buffer_reader, &bytes_used, server_entry->eos); + + server_response_hdr_bytes += bytes_used; + + // Don't allow 0.9 (unparsable headers) on keep-alive connections after + // the connection has already served a transaction as what we are likely + // looking at is garbage on a keep-alive channel corrupted by the origin + // server + if (state == PARSE_DONE && t_state.hdr_info.server_response.version_get() == HTTPVersion(0, 9) && + server_session->transact_count > 1) { + state = PARSE_ERROR; + } + // Check to see if we are over the hdr size limit + if (server_response_hdr_bytes > t_state.txn_conf->response_hdr_max_size) { + state = PARSE_ERROR; + } + + if (state != PARSE_CONT) { + // Disable further IO + server_entry->read_vio->nbytes = server_entry->read_vio->ndone; + http_parser_clear(&http_parser); + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = Thread::get_hrtime(); + } + + switch (state) { + case PARSE_ERROR: { + // Many broken servers send really badly formed 302 redirects. + // Even if the parser doesn't like the redirect forward + // if it's got a Location header. We check the type of the + // response to make sure that the parser was able to parse + // something and didn't just throw up it's hands (INKqa05339) + bool allow_error = false; + if (t_state.hdr_info.server_response.type_get() == HTTP_TYPE_RESPONSE && + t_state.hdr_info.server_response.status_get() == HTTP_STATUS_MOVED_TEMPORARILY) { + if (t_state.hdr_info.server_response.field_find(MIME_FIELD_LOCATION, MIME_LEN_LOCATION)) { + allow_error = true; + } + } + + if (allow_error == false) { + DebugSM("http_seq", "Error parsing server response header"); + t_state.current.state = HttpTransact::PARSE_ERROR; + + // If the server closed prematurely on us, use the + // server setup error routine since it will forward + // error to a POST tunnel if any + if (event == VC_EVENT_EOS) { + handle_server_setup_error(VC_EVENT_EOS, data); + } else { + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + break; + } + // FALLTHROUGH (since we are allowing the parse error) + } + case PARSE_DONE: + DebugSM("http_seq", "Done parsing server response header"); + + // Now that we know that we have all of the origin server + // response headers, we can reset the client inactivity + // timeout. This is unlikely to cause a recurrence of + // old bug because there will be no more retries now that + // the connection has been established. It is possible + // however. We do not need to reset the inactivity timeout + // if the request contains a body (noted by the + // request_content_length field) because it was never + // cancelled. + // + + // we now reset the client inactivity timeout only + // when we are ready to send the response headers. In the + // case of transform plugin, this is after the transform + // outputs the 1st byte, which can take a long time if the + // plugin buffers the whole response. + // Also, if the request contains a body, we cancel the timeout + // when we read the 1st byte of the origin server response. + /* + if (ua_session && !t_state.hdr_info.request_content_length) { + ua_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS( + HttpConfig::m_master.accept_no_activity_timeout)); + } + */ + + t_state.current.state = HttpTransact::CONNECTION_ALIVE; + t_state.transact_return_point = HttpTransact::HandleResponse; + t_state.api_next_action = HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR; + + // if exceeded limit deallocate postdata buffers and disable redirection + if (enable_redirection && (redirection_tries < t_state.txn_conf->number_of_redirections)) { + ++redirection_tries; + } else { + tunnel.deallocate_redirect_postdata_buffers(); + enable_redirection = false; + } + + do_api_callout(); + break; + case PARSE_CONT: + ink_assert(server_entry->eos == false); + server_entry->read_vio->reenable(); + return VC_EVENT_CONT; + + default: + ink_assert(!"not reached"); + } + + return 0; +} + +int +HttpSM::state_send_server_request_header(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_send_server_request_header, event); + ink_assert(server_entry != NULL); + ink_assert(server_entry->write_vio == (VIO *)data || server_entry->read_vio == (VIO *)data); + + int method; + + switch (event) { + case VC_EVENT_WRITE_READY: + server_entry->write_vio->reenable(); + break; + + case VC_EVENT_WRITE_COMPLETE: + // We are done sending the request header, deallocate + // our buffer and then decide what to do next + free_MIOBuffer(server_entry->write_buffer); + server_entry->write_buffer = NULL; + method = t_state.hdr_info.server_request.method_get_wksidx(); + if (!t_state.api_server_request_body_set && method != HTTP_WKSIDX_TRACE && + (t_state.hdr_info.request_content_length > 0 || t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) { + if (post_transform_info.vc) { + setup_transform_to_server_transfer(); + } else { + do_setup_post_tunnel(HTTP_SERVER_VC); + } + } else { + // It's time to start reading the response + setup_server_read_response_header(); + } + + break; + + case VC_EVENT_READ_READY: + // We already did the read for the response header and + // we got some data. Wait for the request header + // send before dealing with it. However, we need to + // disable further IO here since the whole response + // may be in the buffer and we can not switch buffers + // on the io core later + ink_assert(server_entry->read_vio == (VIO *)data); + // setting nbytes to ndone would disable reads and remove it from the read queue. + // We can't do this in the epoll paradigm because we may be missing epoll errors that would + // prevent us from leaving this state. + // setup_server_read_response_header will trigger READ_READY to itself if there is data in the buffer. + + // server_entry->read_vio->nbytes = server_entry->read_vio->ndone; + + break; + + case VC_EVENT_EOS: + // EOS of stream comes from the read side. Treat it as + // as error if there is nothing in the read buffer. If + // there is something the server may have blasted back + // the response before receiving the request. Happens + // often with redirects + // + // If we are in the middle of an api callout, it + // means we haven't actually sent the request yet + // so the stuff in the buffer is garbage and we + // want to ignore it + // + server_entry->eos = true; + + // I'm not sure about the above comment, but if EOS is received on read and we are + // still in this state, we must have not gotten WRITE_COMPLETE. With epoll we might not receive EOS + // from both read and write sides of a connection so it should be handled correctly (close tunnels, + // deallocate, etc) here with handle_server_setup_error(). Otherwise we might hang due to not shutting + // down and never receiving another event again. + /*if (server_buffer_reader->read_avail() > 0 && callout_state == HTTP_API_NO_CALLOUT) { + break; + } */ + + // Nothing in the buffer + // FALLTHROUGH to error + case VC_EVENT_ERROR: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_INACTIVITY_TIMEOUT: + handle_server_setup_error(event, data); + break; + + case VC_EVENT_READ_COMPLETE: + // new event expected due to TS-3189 + DebugSM("http_ss", "read complete due to 0 byte do_io_read"); + break; + + default: + ink_release_assert(0); + break; + } + + return 0; +} + +void +HttpSM::process_srv_info(HostDBInfo *r) +{ + DebugSM("dns_srv", "beginning process_srv_info"); + + /* we didnt get any SRV records, continue w normal lookup */ + if (!r || !r->is_srv || !r->round_robin) { + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.dns_info.srv_lookup_success = false; + t_state.srv_lookup = false; + DebugSM("dns_srv", "No SRV records were available, continuing to lookup %s", t_state.dns_info.lookup_name); + } else { + HostDBRoundRobin *rr = r->rr(); + HostDBInfo *srv = NULL; + if (rr) { + srv = rr->select_best_srv(t_state.dns_info.srv_hostname, &mutex.m_ptr->thread_holding->generator, ink_cluster_time(), + (int)t_state.txn_conf->down_server_timeout); + } + if (!srv) { + t_state.dns_info.srv_lookup_success = false; + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.srv_lookup = false; + DebugSM("dns_srv", "SRV records empty for %s", t_state.dns_info.lookup_name); + } else { + ink_assert(r->md5_high == srv->md5_high && r->md5_low == srv->md5_low && r->md5_low_low == srv->md5_low_low); + t_state.dns_info.srv_lookup_success = true; + t_state.dns_info.srv_port = srv->data.srv.srv_port; + t_state.dns_info.srv_app = srv->app; + // t_state.dns_info.single_srv = (rr->good == 1); + ink_assert(srv->data.srv.key == makeHostHash(t_state.dns_info.srv_hostname)); + DebugSM("dns_srv", "select SRV records %s", t_state.dns_info.srv_hostname); + } + } + return; +} + +void +HttpSM::process_hostdb_info(HostDBInfo *r) +{ + if (r && !r->failed()) { + ink_time_t now = ink_cluster_time(); + HostDBInfo *ret = NULL; + t_state.dns_info.lookup_success = true; + t_state.dns_info.lookup_validated = true; + if (r->round_robin) { + // if use_client_target_addr is set, make sure the client + // addr sits in the pool + if (t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_ADDR_TRY_DEFAULT) { + HostDBRoundRobin *rr = r->rr(); + sockaddr const *addr = t_state.state_machine->ua_session->get_netvc()->get_local_addr(); + + if (rr && rr->find_ip(addr) == NULL) { + // The client specified server address does not appear + // in the DNS pool + DebugSM("http", + "use_client_target_addr == 1. Client specified address is not in the pool. Client address is not validated."); + t_state.dns_info.lookup_validated = false; + } + // Even if we did find the client specified address in the pool, + // We want to make sure that that address is used and not some + // other address in the DNS set. + // Copy over the client information and give up on the lookup + ats_ip_copy(t_state.host_db_info.ip(), addr); + t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_ADDR_TRY_CLIENT; + } else { + // Since the time elapsed between current time and client_request_time + // may be very large, we cannot use client_request_time to approximate + // current time when calling select_best_http(). + HostDBRoundRobin *rr = r->rr(); + ret = rr->select_best_http(&t_state.client_info.src_addr.sa, now, static_cast(t_state.txn_conf->down_server_timeout)); + + // set the srv target`s last_failure + if (t_state.dns_info.srv_lookup_success) { + uint32_t last_failure = 0xFFFFFFFF; + for (int i = 0; i < rr->rrcount && last_failure != 0; ++i) { + if (last_failure > rr->info[i].app.http_data.last_failure) + last_failure = rr->info[i].app.http_data.last_failure; + } + + if (last_failure != 0 && (uint32_t)(now - t_state.txn_conf->down_server_timeout) < last_failure) { + HostDBApplicationInfo app; + app.allotment.application1 = 0; + app.allotment.application2 = 0; + app.http_data.last_failure = last_failure; + hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &app); + } + } + } + } else { + if (t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_ADDR_TRY_DEFAULT) { + // Compare the client specified address against the looked up address + sockaddr const *addr = t_state.state_machine->ua_session->get_netvc()->get_local_addr(); + if (!ats_ip_addr_eq(addr, &r->data.ip.sa)) { + DebugSM("http", "use_client_target_addr == 1. Comparing single addresses failed. Client address is not validated."); + t_state.dns_info.lookup_validated = false; + } + // Regardless of whether the client address matches the DNS + // record or not, we want to use that address. Therefore, + // we copy over the client address info and skip the assignment + // from the DNS cache + ats_ip_copy(t_state.host_db_info.ip(), addr); + t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_ADDR_TRY_CLIENT; + + // Leave ret unassigned, so we don't overwrite the host_db_info + } else { + ret = r; + } + } + if (ret) { + t_state.host_db_info = *ret; + ink_release_assert(!t_state.host_db_info.reverse_dns); + ink_release_assert(ats_is_ip(t_state.host_db_info.ip())); + } + } else { + DebugSM("http", "[%" PRId64 "] DNS lookup failed for '%s'", sm_id, t_state.dns_info.lookup_name); + + t_state.dns_info.lookup_success = false; + t_state.host_db_info.app.allotment.application1 = 0; + t_state.host_db_info.app.allotment.application2 = 0; + ink_assert(!t_state.host_db_info.round_robin); + } + + milestones[TS_MILESTONE_DNS_LOOKUP_END] = Thread::get_hrtime(); + + if (is_debug_tag_set("http_timeout")) { + if (t_state.api_txn_dns_timeout_value != -1) { + int foo = (int)(milestones.difference_msec(TS_MILESTONE_DNS_LOOKUP_BEGIN, TS_MILESTONE_DNS_LOOKUP_END)); + DebugSM("http_timeout", "DNS took: %d msec", foo); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// HttpSM::state_hostdb_lookup() +// +////////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_hostdb_lookup(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_hostdb_lookup, event); + + // ink_assert (m_origin_server_vc == 0); + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed into + // REQ_FLAVOR_REVPROXY + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != NULL); + + switch (event) { + case EVENT_HOST_DB_LOOKUP: + pending_action = NULL; + process_hostdb_info((HostDBInfo *)data); + call_transact_and_set_next_state(NULL); + break; + case EVENT_SRV_LOOKUP: { + pending_action = NULL; + process_srv_info((HostDBInfo *)data); + + char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + HostDBProcessor::Options opt; + opt.port = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port(); + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_session->get_host_res_style(); + + Action *dns_lookup_action_handle = + hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&HttpSM::process_hostdb_info, host_name, 0, opt); + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + historical_action = pending_action; + } else { + call_transact_and_set_next_state(NULL); + } + } break; + case EVENT_HOST_DB_IP_REMOVED: + ink_assert(!"Unexpected event from HostDB"); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +int +HttpSM::state_hostdb_reverse_lookup(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_hostdb_reverse_lookup, event); + + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed into + // REQ_FLAVOR_REVPROXY + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != NULL); + + switch (event) { + case EVENT_HOST_DB_LOOKUP: + pending_action = NULL; + if (data) { + t_state.request_data.hostname_str = ((HostDBInfo *)data)->hostname(); + } else { + DebugSM("http", "[%" PRId64 "] reverse DNS lookup failed for '%s'", sm_id, t_state.dns_info.lookup_name); + } + call_transact_and_set_next_state(NULL); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// +// HttpSM:state_mark_os_down() +// +////////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_mark_os_down(int event, void *data) +{ + HostDBInfo *mark_down = NULL; + + if (event == EVENT_HOST_DB_LOOKUP && data) { + HostDBInfo *r = (HostDBInfo *)data; + + if (r->round_robin) { + // Look for the entry we need mark down in the round robin + ink_assert(t_state.current.server != NULL); + ink_assert(t_state.current.request_to == HttpTransact::ORIGIN_SERVER); + if (t_state.current.server) { + mark_down = r->rr()->find_ip(&t_state.current.server->dst_addr.sa); + } + } else { + // No longer a round robin, check to see if our address is the same + if (ats_ip_addr_eq(t_state.host_db_info.ip(), r->ip())) { + mark_down = r; + } + } + + if (mark_down) { + mark_host_failure(mark_down, t_state.request_sent_time); + } + } + // We either found our entry or we did not. Either way find + // the entry we should use now + return state_hostdb_lookup(event, data); +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::state_handle_stat_page() +// +////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_handle_stat_page(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_handle_stat_page, event); + switch (event) { + case STAT_PAGE_SUCCESS: + pending_action = NULL; + + if (data) { + StatPageData *spd = (StatPageData *)data; + + t_state.internal_msg_buffer = spd->data; + if (spd->type) + t_state.internal_msg_buffer_type = spd->type; + else + t_state.internal_msg_buffer_type = NULL; // Defaults to text/html + t_state.internal_msg_buffer_size = spd->length; + t_state.internal_msg_buffer_fast_allocator_size = -1; + } + + call_transact_and_set_next_state(HttpTransact::HandleStatPage); + break; + + case STAT_PAGE_FAILURE: + pending_action = NULL; + call_transact_and_set_next_state(HttpTransact::HandleStatPage); + break; + + default: + ink_release_assert(0); + break; + } + + return 0; +} + +/////////////////////////////////////////////////////////////// +// +// HttpSM::state_auth_callback() +// +/////////////////////////////////////////////////////////////// +// int +// HttpSM::state_auth_callback(int event, void *data) +//{ +// STATE_ENTER(&HttpSM::state_auth_lookup, event); + +// ink_release_assert(ua_entry != NULL); +// pending_action = NULL; + +// if (event == AUTH_MODULE_EVENT) { +// authAdapter.HandleAuthResponse(event, data); +//} else { +// ink_release_assert(!"Unknown authentication module event"); +//} +/************************************************************************\ + * pending_action=ACTION_RESULT_DONE only if Authentication step has * + * been done & authorization is left * + * pending_action=NULL only if we have to set_next_state. * + * pending_action=something else. Don't do anything. * + * One more callback is pending * +\************************************************************************/ + +// if (authAdapter.stateChangeRequired()) { +// set_next_state(); +//} +// OLD AND UGLY: if (pending_action == NULL) { +// OLD AND UGLY: pending_action=NULL; +// OLD AND UGLY: } else if(pending_action == ACTION_RESULT_DONE) { +// OLD AND UGLY: pending_action=NULL; +// OLD AND UGLY: } + +// return EVENT_DONE; +//} + +/////////////////////////////////////////////////////////////// +// +// HttpSM::state_icp_lookup() +// +/////////////////////////////////////////////////////////////// +int +HttpSM::state_icp_lookup(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_icp_lookup, event); + + // ua_entry is NULL for scheduled updates + ink_release_assert(ua_entry != NULL || t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY); + pending_action = NULL; + + switch (event) { + case ICP_LOOKUP_FOUND: + + DebugSM("http", "ICP says ICP_LOOKUP_FOUND"); + t_state.icp_lookup_success = true; + t_state.icp_ip_result = *(struct sockaddr_in *)data; + + /* + * Disable ICP loop detection since the Cidera network + * insists on trying to preload the cache from a + * a sibling cache. + * + * // inhibit bad ICP looping behavior + * if (t_state.icp_ip_result.sin_addr.s_addr == + * t_state.client_info.ip) { + * DebugSM("http","Loop in ICP config, bypassing..."); + * t_state.icp_lookup_success = false; + * } + */ + break; + + case ICP_LOOKUP_FAILED: + DebugSM("http", "ICP says ICP_LOOKUP_FAILED"); + t_state.icp_lookup_success = false; + break; + default: + ink_release_assert(0); + break; + } + + call_transact_and_set_next_state(HttpTransact::HandleICPLookup); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////// +// HttpSM::state_cache_open_write() +// +// This state is set by set_next_state() for a cache open write +// (SERVER_READ_CACHE_WRITE) +// +////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_cache_open_write(int event, void *data) +{ + STATE_ENTER(&HttpSM : state_cache_open_write, event); + + // Make sure we are on the "right" thread + if (ua_session) { + if ((pending_action = ua_session->adjust_thread(this, event, data))) { + return 0; // Go away if we reschedule + } + } + + milestones[TS_MILESTONE_CACHE_OPEN_WRITE_END] = Thread::get_hrtime(); + pending_action = NULL; + + switch (event) { + case CACHE_EVENT_OPEN_WRITE: + ////////////////////////////// + // OPEN WRITE is successful // + ////////////////////////////// + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_SUCCESS; + break; + + case CACHE_EVENT_OPEN_WRITE_FAILED: + // Failed on the write lock and retrying the vector + // for reading + if (t_state.redirect_info.redirect_in_process) { + DebugSM("http_redirect", "[%" PRId64 "] CACHE_EVENT_OPEN_WRITE_FAILED during redirect follow", sm_id); + t_state.cache_open_write_fail_action = HttpTransact::CACHE_WL_FAIL_ACTION_DEFAULT; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } + if (t_state.txn_conf->cache_open_write_fail_action == HttpTransact::CACHE_WL_FAIL_ACTION_DEFAULT) { + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } else { + t_state.cache_open_write_fail_action = t_state.txn_conf->cache_open_write_fail_action; + if (!t_state.cache_info.object_read || + (t_state.cache_open_write_fail_action == HttpTransact::CACHE_WL_FAIL_ACTION_ERROR_ON_MISS_OR_REVALIDATE)) { + // cache miss, set wl_state to fail + DebugSM("http", "[%" PRId64 "] cache object read %p, cache_wl_fail_action %d", sm_id, t_state.cache_info.object_read, + t_state.cache_open_write_fail_action); + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_FAIL; + break; + } + } + // INTENTIONAL FALL THROUGH + // Allow for stale object to be served + case CACHE_EVENT_OPEN_READ: + // The write vector was locked and the cache_sm retried + // and got the read vector again. + cache_sm.cache_read_vc->get_http_info(&t_state.cache_info.object_read); + // ToDo: Should support other levels of cache hits here, but the cache does not support it (yet) + if (cache_sm.cache_read_vc->is_ram_cache_hit()) { + t_state.cache_info.hit_miss_code = SQUID_HIT_RAM; + } else { + t_state.cache_info.hit_miss_code = SQUID_HIT_DISK; + } + + ink_assert(t_state.cache_info.object_read != 0); + t_state.source = HttpTransact::SOURCE_CACHE; + // clear up CACHE_LOOKUP_MISS, let Freshness function decide + // hit status + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_NONE; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_READ_RETRY; + break; + + case HTTP_TUNNEL_EVENT_DONE: + // In the case where we have issued a cache write for the + // transformed copy, the tunnel from the origin server to + // the transform may complete while we are waiting for + // the cache write. If this is the case, forward the event + // to the transform read state as it will know how to + // handle it + if (t_state.next_action == HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM) { + state_common_wait_for_transform_read(&transform_info, &HttpSM::tunnel_handler, event, data); + + return 0; + } + // Fallthrough + default: + ink_release_assert(0); + } + + if (t_state.api_lock_url != HttpTransact::LOCK_URL_FIRST) { + if (event == CACHE_EVENT_OPEN_WRITE || event == CACHE_EVENT_OPEN_WRITE_FAILED) { + if (t_state.api_lock_url == HttpTransact::LOCK_URL_SECOND) { + t_state.api_lock_url = HttpTransact::LOCK_URL_ORIGINAL; + do_cache_prepare_action(second_cache_sm, t_state.cache_info.second_object_read, true); + return 0; + } else { + t_state.api_lock_url = HttpTransact::LOCK_URL_DONE; + } + } else if (event != CACHE_EVENT_OPEN_READ || t_state.api_lock_url != HttpTransact::LOCK_URL_SECOND) + t_state.api_lock_url = HttpTransact::LOCK_URL_QUIT; + } + // The write either succeeded or failed, notify transact + call_transact_and_set_next_state(NULL); + + return 0; +} + +inline void +HttpSM::setup_cache_lookup_complete_api() +{ + t_state.api_next_action = HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE; + do_api_callout(); +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::state_cache_open_read() +// +// This state handles the result of CacheProcessor::open_read() +// that attempts to do cache lookup and open a particular cached +// object for reading. +// +////////////////////////////////////////////////////////////////////////// +int +HttpSM::state_cache_open_read(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_cache_open_read, event); + milestones[TS_MILESTONE_CACHE_OPEN_READ_END] = Thread::get_hrtime(); + + ink_assert(server_entry == NULL); + ink_assert(t_state.cache_info.object_read == 0); + + switch (event) { + case CACHE_EVENT_OPEN_READ: { + pending_action = NULL; + + DebugSM("http", "[%" PRId64 "] cache_open_read - CACHE_EVENT_OPEN_READ", sm_id); + + ///////////////////////////////// + // lookup/open is successful. // + ///////////////////////////////// + ink_assert(cache_sm.cache_read_vc != NULL); + t_state.source = HttpTransact::SOURCE_CACHE; + + cache_sm.cache_read_vc->get_http_info(&t_state.cache_info.object_read); + // ToDo: Should support other levels of cache hits here, but the cache does not support it (yet) + if (cache_sm.cache_read_vc->is_ram_cache_hit()) { + t_state.cache_info.hit_miss_code = SQUID_HIT_RAM; + } else { + t_state.cache_info.hit_miss_code = SQUID_HIT_DISK; + } + + ink_assert(t_state.cache_info.object_read != 0); + call_transact_and_set_next_state(HttpTransact::HandleCacheOpenRead); + break; + } + case CACHE_EVENT_OPEN_READ_FAILED: + pending_action = NULL; + + DebugSM("http", "[%" PRId64 "] cache_open_read - " + "CACHE_EVENT_OPEN_READ_FAILED", + sm_id); + DebugSM("http", "[state_cache_open_read] open read failed."); + // Inform HttpTransact somebody else is updating the document + // HttpCacheSM already waited so transact should go ahead. + if (data == (void *)-ECACHE_DOC_BUSY) + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_DOC_BUSY; + else + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_MISS; + + ink_assert(t_state.transact_return_point == NULL); + t_state.transact_return_point = HttpTransact::HandleCacheOpenRead; + setup_cache_lookup_complete_api(); + break; + + default: + ink_release_assert("!Unknown event"); + break; + } + + return 0; +} + +int +HttpSM::main_handler(int event, void *data) +{ + ink_release_assert(magic == HTTP_SM_MAGIC_ALIVE); + + HttpSMHandler jump_point = NULL; + ink_assert(reentrancy_count >= 0); + reentrancy_count++; + + // Don't use the state enter macro since it uses history + // space that we don't care about + DebugSM("http", "[%" PRId64 "] [HttpSM::main_handler, %s]", sm_id, HttpDebugNames::get_event_name(event)); + + HttpVCTableEntry *vc_entry = NULL; + + if (data != NULL) { + // Only search the VC table if the event could have to + // do with a VIO to save a few cycles + + if (event < VC_EVENT_EVENTS_START + 100) { + vc_entry = vc_table.find_entry((VIO *)data); + } + } + + if (vc_entry) { + jump_point = vc_entry->vc_handler; + ink_assert(jump_point != (HttpSMHandler)NULL); + ink_assert(vc_entry->vc != (VConnection *)NULL); + (this->*jump_point)(event, data); + } else { + ink_assert(default_handler != (HttpSMHandler)NULL); + (this->*default_handler)(event, data); + } + + // The sub-handler signals when it is time for the state + // machine to exit. We can only exit if we are not reentrantly + // called otherwise when the our call unwinds, we will be + // running on a dead state machine + // + // Because of the need for an api shutdown hook, kill_this() + // is also reentrant. As such, we don't want to decrement + // the reentrancy count until after we run kill_this() + // + if (terminate_sm == true && reentrancy_count == 1) { + kill_this(); + } else { + reentrancy_count--; + ink_assert(reentrancy_count >= 0); + } + + return (VC_EVENT_CONT); +} + +// void HttpSM::tunnel_handler_post_or_put() +// +// Handles the common cleanup tasks for Http post/put +// to prevent code duplication +// +void +HttpSM::tunnel_handler_post_or_put(HttpTunnelProducer *p) +{ + ink_assert(p->vc_type == HT_HTTP_CLIENT); + HttpTunnelConsumer *c; + + // If there is a post transform, remove it's entry from the State + // Machine's VC table + // + // MUST NOT clear the vc pointer from post_transform_info + // as this causes a double close of the transform vc in transform_cleanup + // + if (post_transform_info.vc != NULL) { + ink_assert(post_transform_info.entry->in_tunnel == true); + ink_assert(post_transform_info.vc == post_transform_info.entry->vc); + vc_table.cleanup_entry(post_transform_info.entry); + post_transform_info.entry = NULL; + } + + switch (p->handler_state) { + case HTTP_SM_POST_SERVER_FAIL: + c = tunnel.get_consumer(server_entry->vc); + ink_assert(c->write_success == false); + break; + case HTTP_SM_POST_UA_FAIL: + // UA quit - shutdown the SM + ink_assert(p->read_success == false); + terminate_sm = true; + break; + case HTTP_SM_POST_SUCCESS: + // The post succeeded + ink_assert(p->read_success == true); + ink_assert(p->consumer_list.head->write_success == true); + tunnel.deallocate_buffers(); + tunnel.reset(); + // When the ua completed sending it's data we must have + // removed it from the tunnel + ink_release_assert(ua_entry->in_tunnel == false); + server_entry->in_tunnel = false; + + break; + default: + ink_release_assert(0); + } +} + +// int HttpSM::tunnel_handler_post(int event, void* data) +// +// Handles completion of any http request body tunnel +// Having 'post' in its name is a misnomer +// +int +HttpSM::tunnel_handler_post(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler_post, event); + + HttpTunnelProducer *p = ua_session != NULL ? tunnel.get_producer(ua_session) : tunnel.get_producer(HT_HTTP_CLIENT); + if (!p) + return 0; // Cannot do anything if there is no producer + + if (event != HTTP_TUNNEL_EVENT_DONE) { + if ((event == VC_EVENT_WRITE_COMPLETE) || (event == VC_EVENT_EOS)) { + if (ua_entry->write_buffer) { + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = NULL; + } + } + if (p->handler_state == HTTP_SM_POST_UA_FAIL) { + Debug("http_tunnel", "cleanup tunnel in tunnel_handler_post"); + hsm_release_assert(ua_entry->in_tunnel == true); + ink_assert((event == VC_EVENT_WRITE_COMPLETE) || (event == VC_EVENT_EOS)); + vc_table.cleanup_all(); + tunnel.chain_abort_all(p); + p->read_vio = NULL; + p->vc->do_io_close(EHTTP_ERROR); + tunnel_handler_post_or_put(p); + tunnel.kill_tunnel(); + return 0; + } + } + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + // The tunnel calls this when it is done + + int p_handler_state = p->handler_state; + tunnel_handler_post_or_put(p); + + switch (p_handler_state) { + case HTTP_SM_POST_SERVER_FAIL: + handle_post_failure(); + break; + case HTTP_SM_POST_UA_FAIL: + break; + case HTTP_SM_POST_SUCCESS: + // It's time to start reading the response + setup_server_read_response_header(); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +HttpSM::tunnel_handler_cache_fill(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler_cache_fill, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + ink_release_assert(cache_sm.cache_write_vc); + + tunnel.deallocate_buffers(); + tunnel.deallocate_redirect_postdata_buffers(); + tunnel.reset(); + + setup_server_transfer_to_cache_only(); + tunnel.tunnel_run(); + + return 0; +} + +int +HttpSM::tunnel_handler_100_continue(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler_100_continue, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + // We're done sending the 100 continue. If we succeeded, + // we set up to parse the next server response. If we + // failed, shutdown the state machine + HttpTunnelConsumer *c = tunnel.get_consumer(ua_session); + + if (c->write_success) { + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.client_response.destroy(); + tunnel.deallocate_buffers(); + tunnel.deallocate_redirect_postdata_buffers(); + tunnel.reset(); + + if (server_entry->eos) { + // if the server closed while sending the + // 100 continue header, handle it here so we + // don't assert later + DebugSM("http", "[%" PRId64 "] tunnel_handler_100_continue - server already " + "closed, terminating connection", + sm_id); + + // Since 100 isn't a final (loggable) response header + // kill the 100 continue header and create an empty one + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + handle_server_setup_error(VC_EVENT_EOS, server_entry->read_vio); + } else { + setup_server_read_response_header(); + } + } else { + terminate_sm = true; + } + + return 0; +} + +int +HttpSM::tunnel_handler_push(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler_push, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + + // Check to see if the client is still around + HttpTunnelProducer *ua = (ua_session) ? tunnel.get_producer(ua_session) : tunnel.get_producer(HT_HTTP_CLIENT); + + if (ua && !ua->read_success) { + // Client failed to send the body, it's gone. Kill the + // state machine + terminate_sm = true; + return 0; + } + + HttpTunnelConsumer *cache = ua->consumer_list.head; + ink_release_assert(cache->vc_type == HT_CACHE_WRITE); + bool cache_write_success = cache->write_success; + + // Reset tunneling state since we need to send a response + // to client as whether we succeeded + tunnel.deallocate_buffers(); + tunnel.deallocate_redirect_postdata_buffers(); + tunnel.reset(); + + if (cache_write_success) { + call_transact_and_set_next_state(HttpTransact::HandlePushTunnelSuccess); + } else { + call_transact_and_set_next_state(HttpTransact::HandlePushTunnelFailure); + } + + return 0; +} + +int +HttpSM::tunnel_handler(int event, void *data) +{ + STATE_ENTER(&HttpSM::tunnel_handler, event); + + ink_assert(event == HTTP_TUNNEL_EVENT_DONE); + ink_assert(data == &tunnel); + // The tunnel calls this when it is done + terminate_sm = true; + + if (unlikely(t_state.is_websocket)) { + HTTP_DECREMENT_DYN_STAT(http_websocket_current_active_client_connections_stat); + } + + return 0; +} + +/**************************************************** + TUNNELLING HANDLERS + ******************************************************/ + +bool +HttpSM::is_http_server_eos_truncation(HttpTunnelProducer *p) +{ + if ((p->do_dechunking || p->do_chunked_passthru) && p->chunked_handler.truncation) { + // TS-3054 - In the chunked cases, chunked data that is incomplete + // should not be cached, but it should be passed onto the client + // This makes ATS more transparent in the case of non-standard + // servers. The cache aborts are dealt with in other checks + // on the truncation flag elsewhere in the code. This return value + // invalidates the current data being passed over to the client. + // So changing it from return true to return false, so the partial data + // is passed onto the client. + return false; + } + + ////////////////////////////////////////////////////////////// + // If we did not get or did not trust the origin server's // + // content-length, read_content_length is unset. The // + // only way the end of the document is signaled is the // + // origin server closing the connection. However, we // + // need to protect against the document getting truncated // + // because the origin server crashed. The following // + // tabled outlines when we mark the server read as failed // + // // + // No C-L : read success // + // Received byts < C-L : read failed (=> Cache Abort) // + // Received byts == C-L : read success // + // Received byts > C-L : read success // + ////////////////////////////////////////////////////////////// + int64_t cl = t_state.hdr_info.server_response.get_content_length(); + + if (cl != UNDEFINED_COUNT && cl > server_response_body_bytes) { + DebugSM("http", "[%" PRId64 "] server EOS after %" PRId64 " bytes, expected %" PRId64, sm_id, server_response_body_bytes, cl); + return true; + } else { + return false; + } +} + +int +HttpSM::tunnel_handler_server(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_server, event); + + milestones[TS_MILESTONE_SERVER_CLOSE] = Thread::get_hrtime(); + + bool close_connection = false; + + if (t_state.current.server->keep_alive == HTTP_KEEPALIVE && server_entry->eos == false && + plugin_tunnel_type == HTTP_NO_PLUGIN_TUNNEL && t_state.txn_conf->keep_alive_enabled_out == 1) { + close_connection = false; + } else { + close_connection = true; + } + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_TIMEOUT; + t_state.squid_codes.hier_code = SQUID_HIER_TIMEOUT_DIRECT; + /* fallthru */ + + case VC_EVENT_EOS: + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + t_state.current.server->state = HttpTransact::INACTIVE_TIMEOUT; + break; + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.current.server->state = HttpTransact::ACTIVE_TIMEOUT; + break; + case VC_EVENT_ERROR: + t_state.current.server->state = HttpTransact::CONNECTION_ERROR; + break; + case VC_EVENT_EOS: + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + break; + } + + close_connection = true; + + ink_assert(p->vc_type == HT_HTTP_SERVER); + + if (is_http_server_eos_truncation(p)) { + DebugSM("http", "[%" PRId64 "] [HttpSM::tunnel_handler_server] aborting HTTP tunnel due to server truncation", sm_id); + tunnel.chain_abort_all(p); + // UA session may not be in the tunnel yet, don't NULL out the pointer in that case. + // Note: This is a hack. The correct solution is for the UA session to signal back to the SM + // when the UA is about to be destroyed and clean up the pointer there. That should be done once + // the TS-3612 changes are in place (and similarly for the server session). + if (ua_entry->in_tunnel) + ua_session = NULL; + + t_state.current.server->abort = HttpTransact::ABORTED; + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + t_state.squid_codes.log_code = SQUID_LOG_ERR_READ_ERROR; + } else { + DebugSM("http", "[%" PRId64 "] [HttpSM::tunnel_handler_server] finishing HTTP tunnel", sm_id); + p->read_success = true; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + // Appending reason to a response without Content-Length will result in + // the reason string being written to the client and a bad CL when reading from cache. + // I didn't find anywhere this appended reason is being used, so commenting it out. + /* + if (t_state.negative_caching && p->bytes_read == 0) { + int reason_len; + const char *reason = t_state.hdr_info.server_response.reason_get(&reason_len); + if (reason == NULL) + tunnel.append_message_to_producer_buffer(p, "Negative Response", sizeof("Negative Response") - 1); + else + tunnel.append_message_to_producer_buffer(p, reason, reason_len); + } + */ + tunnel.local_finish_all(p); + } + break; + + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case VC_EVENT_READ_COMPLETE: + // + // The transfer completed successfully + // If there is still data in the buffer, the server + // sent too much indicating a failed transfer + p->read_success = true; + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + + if (p->do_dechunking || p->do_chunked_passthru) { + if (p->chunked_handler.truncation) { + tunnel.abort_cache_write_finish_others(p); + // We couldn't read all chunks successfully: + // Disable keep-alive. + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + } else { + tunnel.local_finish_all(p); + } + } + break; + + case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: + // All consumers are prematurely gone. Shutdown + // the server connection + p->read_success = true; + t_state.current.server->state = HttpTransact::TRANSACTION_COMPLETE; + t_state.current.server->abort = HttpTransact::DIDNOT_ABORT; + close_connection = true; + break; + + case VC_EVENT_READ_READY: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + // turn off negative caching in case there are multiple server contacts + if (t_state.negative_caching) + t_state.negative_caching = false; + + // If we had a ground fill, check update our status + if (background_fill == BACKGROUND_FILL_STARTED) { + background_fill = p->read_success ? BACKGROUND_FILL_COMPLETED : BACKGROUND_FILL_ABORTED; + HTTP_DECREMENT_DYN_STAT(http_background_fill_current_count_stat); + } + // We handled the event. Now either shutdown the connection or + // setup it up for keep-alive + ink_assert(server_entry->vc == p->vc); + ink_assert(p->vc_type == HT_HTTP_SERVER); + ink_assert(p->vc == server_session); + + if (close_connection) { + p->vc->do_io_close(); + server_session = NULL; // Because p->vc == server_session + p->read_vio = NULL; + /* TS-1424: if we're outbound transparent and using the client + source port for the outbound connection we must effectively + propagate server closes back to the client. Part of that is + disabling KeepAlive if the server closes. + */ + if (ua_session && ua_session->is_outbound_transparent() && t_state.http_config_param->use_client_source_port) { + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + } + } else { + server_session->attach_hostname(t_state.current.server->name); + server_session->server_trans_stat--; + HTTP_DECREMENT_DYN_STAT(http_current_server_transactions_stat); + + // If the option to attach the server session to the client session is set + // and if the client is still around and the client is keep-alive, attach the + // server session to so the next ka request can use it. Server sessions will + // be placed into the shared pool if the next incoming request is for a different + // origin server + if (t_state.txn_conf->attach_server_session_to_client == 1 && ua_session && t_state.client_info.keep_alive == HTTP_KEEPALIVE) { + Debug("http", "attaching server session to the client"); + ua_session->attach_server_session(server_session); + } else { + // Release the session back into the shared session pool + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + server_session->release(); + } + } + + return 0; +} + +// int HttpSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer* c) +// +// Used for tunneling the 100 continue response. The tunnel +// should not close or release the user agent unless there is +// an error since the real response is yet to come +// +int +HttpSM::tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_100_continue_ua, event); + + ink_assert(c->vc == ua_session); + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + // FALL-THROUGH + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + set_ua_abort(HttpTransact::ABORTED, event); + c->vc->do_io_close(); + break; + case VC_EVENT_WRITE_COMPLETE: + // mark the vc as no longer in tunnel + // so we don't get hosed if the ua abort before + // real response header is received + ua_entry->in_tunnel = false; + c->write_success = true; + } + + return 0; +} + +bool +HttpSM::is_bg_fill_necessary(HttpTunnelConsumer *c) +{ + ink_assert(c->vc_type == HT_HTTP_CLIENT); + + if (c->producer->alive && // something there to read + server_entry && server_entry->vc && // from an origin server + server_session && server_session->get_netvc() && // which is still open and valid + c->producer->num_consumers > 1 // with someone else reading it + ) { + // If threshold is 0.0 or negative then do background + // fill regardless of the content length. Since this + // is floating point just make sure the number is near zero + if (t_state.txn_conf->background_fill_threshold <= 0.001) { + return true; + } + + int64_t ua_cl = t_state.hdr_info.client_response.get_content_length(); + + if (ua_cl > 0) { + int64_t ua_body_done = c->bytes_written - client_response_hdr_bytes; + float pDone = (float)ua_body_done / ua_cl; + + // If we got a good content length. Check to make sure that we haven't already + // done more the content length since that would indicate the content-length + // is bogus. If we've done more than the threshold, continue the background fill + if (pDone <= 1.0 && pDone > t_state.txn_conf->background_fill_threshold) { + return true; + } else { + DebugSM("http", "[%" PRId64 "] no background. Only %%%f of %%%f done [%" PRId64 " / %" PRId64 " ]", sm_id, pDone, + t_state.txn_conf->background_fill_threshold, ua_body_done, ua_cl); + } + } + } + + return false; +} + +int +HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer *c) +{ + bool close_connection = true; + HttpTunnelProducer *p = NULL; + HttpTunnelConsumer *selfc = NULL; + + STATE_ENTER(&HttpSM::tunnel_handler_ua, event); + ink_assert(c->vc == ua_session); + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + + switch (event) { + case VC_EVENT_EOS: + ua_entry->eos = true; + + // FALL-THROUGH + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + + if (ua_session != NULL) { + // The user agent died or aborted. Check to + // see if we should setup a background fill + set_ua_abort(HttpTransact::ABORTED, event); + } + + if (is_bg_fill_necessary(c)) { + DebugSM("http", "[%" PRId64 "] Initiating background fill", sm_id); + background_fill = BACKGROUND_FILL_STARTED; + HTTP_INCREMENT_DYN_STAT(http_background_fill_current_count_stat); + + // There is another consumer (cache write) so + // detach the user agent + ink_assert(server_entry->vc == server_session); + ink_assert(c->is_downstream_from(server_session)); + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->background_fill_active_timeout)); + } else { + // No background fill + p = c->producer; + tunnel.chain_abort_all(c->producer); + selfc = p->self_consumer; + if (selfc) { + // This is the case where there is a transformation between ua and os + p = selfc->producer; + // if producer is the cache or OS, close the producer. + // Otherwise in case of large docs, producer iobuffer gets filled up, + // waiting for a consumer to consume data and the connection is never closed. + if (p->alive && ((p->vc_type == HT_CACHE_READ) || (p->vc_type == HT_HTTP_SERVER))) { + tunnel.chain_abort_all(p); + } + } + } + break; + + case VC_EVENT_WRITE_COMPLETE: + c->write_success = true; + t_state.client_info.abort = HttpTransact::DIDNOT_ABORT; + if (t_state.client_info.keep_alive == HTTP_KEEPALIVE) { + if (t_state.www_auth_content != HttpTransact::CACHE_AUTH_SERVE || ua_session->get_server_session()) { + // successful keep-alive + close_connection = false; + } + // else { the authenticated server connection (cache + // authenticated feature) closed during the serve-from-cache. + // We want the client to issue a new connection for the + // session based authenticated mechanism like NTLM, instead + // of still using the existing client connection. } + } + break; + case VC_EVENT_WRITE_READY: + case VC_EVENT_READ_READY: + case VC_EVENT_READ_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + client_response_body_bytes = c->bytes_written - client_response_hdr_bytes; + + if (client_response_body_bytes < 0) + client_response_body_bytes = 0; + + // attribute the size written to the client from various sources + // NOTE: responses that go through a range transform are attributed + // to their original sources + // all other transforms attribute the total number of input bytes + // to a source in HttpSM::tunnel_handler_transform_write + // + HttpTransact::Source_t original_source = t_state.source; + if (HttpTransact::SOURCE_TRANSFORM == original_source && t_state.range_setup != HttpTransact::RANGE_NONE) { + original_source = t_state.pre_transform_source; + } + + switch (original_source) { + case HttpTransact::SOURCE_HTTP_ORIGIN_SERVER: + server_response_body_bytes = client_response_body_bytes; + break; + case HttpTransact::SOURCE_CACHE: + cache_response_body_bytes = client_response_body_bytes; + break; + default: + break; + } + + ink_assert(ua_entry->vc == c->vc); + if (close_connection) { + // If the client could be pipelining or is doing a POST, we need to + // set the ua_session into half close mode + + // only external POSTs should be subject to this logic; ruling out internal POSTs here + bool is_eligible_post_request = (t_state.method == HTTP_WKSIDX_POST); + if (is_eligible_post_request) { + NetVConnection *vc = ua_session->get_netvc(); + if (vc) { + is_eligible_post_request &= !vc->get_is_internal_request(); + } + } + if ((is_eligible_post_request || t_state.client_info.pipeline_possible == true) && c->producer->vc_type != HT_STATIC && + event == VC_EVENT_WRITE_COMPLETE) { + ua_session->set_half_close_flag(true); + } + + ua_session->do_io_close(); + ua_session = NULL; + } else { + ink_assert(ua_buffer_reader != NULL); + ua_session->release(ua_buffer_reader); + ua_buffer_reader = NULL; + ua_session = NULL; + } + + return 0; +} + +int +HttpSM::tunnel_handler_ua_push(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_ua_push, event); + + pushed_response_body_bytes += p->bytes_read; + client_request_body_bytes += p->bytes_read; + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + // Transfer terminated. Bail on the cache write. + t_state.client_info.abort = HttpTransact::ABORTED; + p->vc->do_io_close(EHTTP_ERROR); + p->read_vio = NULL; + tunnel.chain_abort_all(p); + break; + + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case VC_EVENT_READ_COMPLETE: + // + // The transfer completed successfully + p->read_success = true; + ua_entry->in_tunnel = false; + break; + + case VC_EVENT_READ_READY: + case VC_EVENT_WRITE_READY: + case VC_EVENT_WRITE_COMPLETE: + default: + // None of these events should ever come our way + ink_assert(0); + break; + } + + return 0; +} + +int +HttpSM::tunnel_handler_cache_read(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_cache_read, event); + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + ink_assert(t_state.cache_info.object_read->valid()); + if (t_state.cache_info.object_read->object_size_get() != INT64_MAX || event == VC_EVENT_ERROR) { + // Abnormal termination + t_state.squid_codes.log_code = SQUID_LOG_TCP_SWAPFAIL; + p->vc->do_io_close(EHTTP_ERROR); + p->read_vio = NULL; + tunnel.chain_abort_all(p); + HTTP_INCREMENT_DYN_STAT(http_cache_read_errors); + break; + } else { + tunnel.local_finish_all(p); + // fall through for the case INT64_MAX read with VC_EVENT_EOS + // callback (read successful) + } + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + case HTTP_TUNNEL_EVENT_CONSUMER_DETACH: + p->read_success = true; + p->vc->do_io_close(); + p->read_vio = NULL; + break; + default: + ink_release_assert(0); + break; + } + + HTTP_DECREMENT_DYN_STAT(http_current_cache_connections_stat); + return 0; +} + +int +HttpSM::tunnel_handler_cache_write(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_cache_write, event); + + HttpTransact::CacheWriteStatus_t *status_ptr = + (c->producer->vc_type == HT_TRANSFORM) ? &t_state.cache_info.transform_write_status : &t_state.cache_info.write_status; + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + // Abnormal termination + *status_ptr = HttpTransact::CACHE_WRITE_ERROR; + c->write_vio = NULL; + c->vc->do_io_close(EHTTP_ERROR); + + HTTP_INCREMENT_DYN_STAT(http_cache_write_errors); + DebugSM("http", "[%" PRId64 "] aborting cache write due %s event from cache", sm_id, HttpDebugNames::get_event_name(event)); + // abort the producer if the cache_writevc is the only consumer. + if (c->producer->alive && c->producer->num_consumers == 1) + tunnel.chain_abort_all(c->producer); + break; + case VC_EVENT_WRITE_COMPLETE: + // if we've never initiated a cache write + // abort the cache since it's finicky about a close + // in this case. This case can only occur + // we got a truncated header from the origin server + // but decided to accept it anyways + if (c->write_vio == NULL) { + *status_ptr = HttpTransact::CACHE_WRITE_ERROR; + c->write_success = false; + c->vc->do_io_close(EHTTP_ERROR); + } else { + *status_ptr = HttpTransact::CACHE_WRITE_COMPLETE; + c->write_success = true; + c->write_vio = c->vc->do_io(VIO::CLOSE); + } + break; + default: + // All other events indicate problems + ink_assert(0); + break; + } + + HTTP_DECREMENT_DYN_STAT(http_current_cache_connections_stat); + return 0; +} + +int +HttpSM::tunnel_handler_post_ua(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_post_ua, event); + client_request_body_bytes = p->init_bytes_done + p->bytes_read; + int64_t alloc_index, nbytes; + IOBufferReader *buf_start; + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + if (client_response_hdr_bytes == 0) { + p->handler_state = HTTP_SM_POST_UA_FAIL; + set_ua_abort(HttpTransact::ABORTED, event); + + switch (event) { + case VC_EVENT_INACTIVITY_TIMEOUT: + HttpTransact::build_error_response(&t_state, HTTP_STATUS_REQUEST_TIMEOUT, "POST Request timeout", "timeout#inactivity", + NULL); + break; + case VC_EVENT_ACTIVE_TIMEOUT: + HttpTransact::build_error_response(&t_state, HTTP_STATUS_REQUEST_TIMEOUT, "POST Request timeout", "timeout#activity", NULL); + break; + } + + // send back 408 request timeout + alloc_index = buffer_size_to_index(len_408_request_timeout_response + t_state.internal_msg_buffer_size); + if (ua_entry->write_buffer) { + free_MIOBuffer(ua_entry->write_buffer); + ua_entry->write_buffer = NULL; + } + ua_entry->write_buffer = new_MIOBuffer(alloc_index); + buf_start = ua_entry->write_buffer->alloc_reader(); + + DebugSM("http_tunnel", "send 408 response to client to vc %p, tunnel vc %p", ua_session->get_netvc(), p->vc); + + nbytes = ua_entry->write_buffer->write(str_408_request_timeout_response, len_408_request_timeout_response); + nbytes += ua_entry->write_buffer->write(t_state.internal_msg_buffer, t_state.internal_msg_buffer_size); + + p->vc->do_io_write(this, nbytes, buf_start); + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + return 0; + } + // fall through + case VC_EVENT_EOS: + // My reading of spec says that user agents can not terminate + // posts with a half close so this is an error + case VC_EVENT_ERROR: + // Did not complete post tunnling. Abort the + // server and close the ua + p->handler_state = HTTP_SM_POST_UA_FAIL; + set_ua_abort(HttpTransact::ABORTED, event); + + tunnel.chain_abort_all(p); + p->read_vio = NULL; + p->vc->do_io_close(EHTTP_ERROR); + + // the in_tunnel status on both the ua & and + // it's consumer must already be set to true. Previously + // we were setting it again to true but incorrectly in + // the case of a transform + hsm_release_assert(ua_entry->in_tunnel == true); + if (p->consumer_list.head->vc_type == HT_TRANSFORM) { + hsm_release_assert(post_transform_info.entry->in_tunnel == true); + } else if (server_entry != NULL) { + hsm_release_assert(server_entry->in_tunnel == true); + } + break; + + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + p->handler_state = HTTP_SM_POST_SUCCESS; + p->read_success = true; + ua_entry->in_tunnel = false; + + if (p->do_dechunking || p->do_chunked_passthru) { + if (p->chunked_handler.truncation) { + tunnel.abort_cache_write_finish_others(p); + } else { + tunnel.local_finish_all(p); + } + } + // Initiate another read to watch catch aborts and + // timeouts + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + ua_entry->read_vio = p->vc->do_io_read(this, INT64_MAX, ua_buffer_reader->mbuf); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +// YTS Team, yamsat Plugin +// Tunnel handler to deallocate the tunnel buffers and +// set redirect_in_process=false +// Copy partial POST data to buffers. Check for the various parameters including +// the maximum configured post data size +int +HttpSM::tunnel_handler_for_partial_post(int event, void * /* data ATS_UNUSED */) +{ + STATE_ENTER(&HttpSM::tunnel_handler_for_partial_post, event); + tunnel.deallocate_buffers(); + tunnel.reset(); + + tunnel.allocate_redirect_postdata_producer_buffer(); + + t_state.redirect_info.redirect_in_process = false; + + if (post_failed) { + post_failed = false; + handle_post_failure(); + } else + do_setup_post_tunnel(HTTP_SERVER_VC); + + return 0; +} + +int +HttpSM::tunnel_handler_post_server(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_post_server, event); + + server_request_body_bytes = c->bytes_written; + + switch (event) { + case VC_EVENT_EOS: + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // Did not complete post tunneling + // + // In the http case, we don't want to close + // the connection because the + // destroys the header buffer which may + // a response even though the tunnel failed. + + // Shutdown both sides of the connection. This prevents us + // from getting any further events and signals to client + // that POST data will not be forwarded to the server. Doing + // shutdown on the write side will likely generate a TCP + // reset to the client but if the proxy wasn't here this is + // exactly what would happen. + // we should wait to shutdown read side of the + // client to prevent sending a reset + server_entry->eos = true; + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + + // We may be reading from a transform. In that case, we + // want to close the transform + HttpTunnelProducer *ua_producer; + if (c->producer->vc_type == HT_TRANSFORM) { + if (c->producer->handler_state == HTTP_SM_TRANSFORM_OPEN) { + ink_assert(c->producer->vc == post_transform_info.vc); + c->producer->vc->do_io_close(); + c->producer->alive = false; + c->producer->self_consumer->alive = false; + } + ua_producer = c->producer->self_consumer->producer; + } else { + ua_producer = c->producer; + } + ink_assert(ua_producer->vc_type == HT_HTTP_CLIENT); + ink_assert(ua_producer->vc == ua_session); + ink_assert(ua_producer->vc == ua_entry->vc); + + // Before shutting down, initiate another read + // on the user agent in order to get timeouts + // coming to the state machine and not the tunnel + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + + // YTS Team, yamsat Plugin + // When event is VC_EVENT_ERROR,and when redirection is enabled + // do not shut down the client read + if (enable_redirection) { + if (ua_producer->vc_type == HT_STATIC && event != VC_EVENT_ERROR && event != VC_EVENT_EOS) { + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + t_state.client_info.pipeline_possible = false; + } else { + if (ua_producer->vc_type == HT_STATIC && t_state.redirect_info.redirect_in_process) { + post_failed = true; + } + } + } else { + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + // we should not shutdown read side of the client here to prevent sending a reset + // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + t_state.client_info.pipeline_possible = false; + } // end of added logic + + // We want to shutdown the tunnel here and see if there + // is a response on from the server. Mark the user + // agent as down so that tunnel concludes. + ua_producer->alive = false; + ua_producer->handler_state = HTTP_SM_POST_SERVER_FAIL; + ink_assert(tunnel.is_tunnel_alive() == false); + break; + + case VC_EVENT_WRITE_COMPLETE: + // Completed successfully + c->write_success = true; + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +HttpSM::tunnel_handler_ssl_producer(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_ssl_producer, event); + + switch (event) { + case VC_EVENT_EOS: + // The write side of this connection is still alive + // so half-close the read + if (p->self_consumer->alive) { + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + tunnel.local_finish_all(p); + break; + } + // FALL THROUGH - both sides of the tunnel are dea + case VC_EVENT_ERROR: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // The other side of the connection is either already dead + // or rendered inoperative by the error on the connection + // Note: use tunnel close vc so the tunnel knows we are + // nuking the of the connection as well + tunnel.close_vc(p); + tunnel.local_finish_all(p); + + // Because we've closed the net vc this error came in, it's write + // direction is now dead as well. If that side still being fed data, + // we need to kill that pipe as well + if (p->self_consumer->producer->alive) { + p->self_consumer->producer->alive = false; + if (p->self_consumer->producer->self_consumer->alive) { + p->self_consumer->producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } else { + tunnel.close_vc(p->self_consumer->producer); + } + } + break; + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + // We should never get these event since we don't know + // how long the stream is + default: + ink_release_assert(0); + } + + // Update stats + switch (p->vc_type) { + case HT_HTTP_SERVER: + server_response_body_bytes += p->bytes_read; + break; + case HT_HTTP_CLIENT: + client_request_body_bytes += p->bytes_read; + break; + default: + // Covered here: + // HT_CACHE_READ, HT_CACHE_WRITE, + // HT_TRANSFORM, HT_STATIC. + break; + } + + return 0; +} + +int +HttpSM::tunnel_handler_ssl_consumer(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_ssl_consumer, event); + + switch (event) { + case VC_EVENT_ERROR: + case VC_EVENT_EOS: + case VC_EVENT_INACTIVITY_TIMEOUT: + case VC_EVENT_ACTIVE_TIMEOUT: + // we need to mark the producer dead + // otherwise it can stay alive forever. + if (c->producer->alive) { + c->producer->alive = false; + if (c->producer->self_consumer->alive) { + c->producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } else { + tunnel.close_vc(c->producer); + } + } + // Since we are changing the state of the self_producer + // we must have the tunnel shutdown the vc + tunnel.close_vc(c); + tunnel.local_finish_all(c->self_producer); + break; + + case VC_EVENT_WRITE_COMPLETE: + // If we get this event, it means that the producer + // has finished and we wrote the remaining data + // to the consumer + // + // If the read side of this connection has not yet + // closed, do a write half-close and then wait for + // read side to close so that we don't cut off + // pipelined responses with TCP resets + // + // ink_assert(c->producer->alive == false); + c->write_success = true; + if (c->self_producer->alive == true) { + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + } else { + c->vc->do_io_close(); + } + break; + + default: + ink_release_assert(0); + } + + // Update stats + switch (c->vc_type) { + case HT_HTTP_SERVER: + server_request_body_bytes += c->bytes_written; + break; + case HT_HTTP_CLIENT: + client_response_body_bytes += c->bytes_written; + break; + default: + // Handled here: + // HT_CACHE_READ, HT_CACHE_WRITE, HT_TRANSFORM, + // HT_STATIC + break; + } + + return 0; +} + +int +HttpSM::tunnel_handler_transform_write(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_transform_write, event); + + HttpTransformInfo *i; + + // Figure out if this the request or response transform + // : use post_transform_info.entry because post_transform_info.vc + // is not set to NULL after the post transform is done. + if (post_transform_info.entry) { + i = &post_transform_info; + ink_assert(c->vc == i->entry->vc); + } else { + i = &transform_info; + ink_assert(c->vc == i->vc); + ink_assert(c->vc == i->entry->vc); + } + + switch (event) { + case VC_EVENT_ERROR: + // Transform error + tunnel.chain_abort_all(c->producer); + c->handler_state = HTTP_SM_TRANSFORM_FAIL; + c->vc->do_io_close(EHTTP_ERROR); + break; + case VC_EVENT_EOS: + // It possible the transform quit + // before the producer finished. If this is true + // we need shut down the producer if it doesn't + // have other consumers to serve or else it will + // fill up buffer and get hung + if (c->producer->alive && c->producer->num_consumers == 1) { + // Send a tunnel detach event to the producer + // to shut it down but indicates it should not abort + // downstream (on the other side of the transform) + // cache writes + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + // FALLTHROUGH + case VC_EVENT_WRITE_COMPLETE: + // write to transform complete - shutdown the write side + c->write_success = true; + c->vc->do_io_shutdown(IO_SHUTDOWN_WRITE); + + // If the read side has not started up yet, then the + // this transform_vc is no longer owned by the tunnel + if (c->self_producer == NULL) { + i->entry->in_tunnel = false; + } else if (c->self_producer->alive == false) { + // The read side of the Transform + // has already completed (possible when the + // transform intentionally truncates the response). + // So close it + c->vc->do_io(VIO::CLOSE); + } + break; + default: + ink_release_assert(0); + } + + // attribute the size written to the transform from various sources + // NOTE: the range transform is excluded from this accounting and + // is instead handled in HttpSM::tunnel_handler_ua + // + // the reasoning is that the range transform is internal functionality + // in support of HTTP 1.1 compliance, therefore part of "normal" operation + // all other transforms are plugin driven and the difference between + // source data and final data should represent the transformation delta + // + if (t_state.range_setup == HttpTransact::RANGE_NONE) { + switch (t_state.pre_transform_source) { + case HttpTransact::SOURCE_HTTP_ORIGIN_SERVER: + server_response_body_bytes = client_response_body_bytes; + break; + case HttpTransact::SOURCE_CACHE: + cache_response_body_bytes = client_response_body_bytes; + break; + default: + break; + } + } + + return 0; +} + +int +HttpSM::tunnel_handler_transform_read(int event, HttpTunnelProducer *p) +{ + STATE_ENTER(&HttpSM::tunnel_handler_transform_read, event); + + ink_assert(p->vc == transform_info.vc || p->vc == post_transform_info.vc); + + switch (event) { + case VC_EVENT_ERROR: + // Transform error + tunnel.chain_abort_all(p->self_consumer->producer); + break; + case VC_EVENT_EOS: + // If we did not get enough data from the transform abort the + // cache write otherwise fallthrough to the transform + // completing successfully + if (t_state.hdr_info.transform_response_cl != HTTP_UNDEFINED_CL && + p->read_vio->nbytes < t_state.hdr_info.transform_response_cl) { + tunnel.abort_cache_write_finish_others(p); + break; + } + // FALL-THROUGH + case VC_EVENT_READ_COMPLETE: + case HTTP_TUNNEL_EVENT_PRECOMPLETE: + // Transform complete + p->read_success = true; + tunnel.local_finish_all(p); + break; + default: + ink_release_assert(0); + } + + // it's possible that the write side of the + // transform hasn't detached yet. If it is still alive, + // don't close the transform vc + if (p->self_consumer->alive == false) { + p->vc->do_io_close(); + } + p->handler_state = HTTP_SM_TRANSFORM_CLOSED; + + return 0; +} + +int +HttpSM::tunnel_handler_plugin_agent(int event, HttpTunnelConsumer *c) +{ + STATE_ENTER(&HttpSM::tunnel_handler_plugin_client, event); + + switch (event) { + case VC_EVENT_ERROR: + c->vc->do_io_close(EHTTP_ERROR); // close up + // Signal producer if we're the last consumer. + if (c->producer->alive && c->producer->num_consumers == 1) { + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + break; + case VC_EVENT_EOS: + if (c->producer->alive && c->producer->num_consumers == 1) { + tunnel.producer_handler(HTTP_TUNNEL_EVENT_CONSUMER_DETACH, c->producer); + } + // FALLTHROUGH + case VC_EVENT_WRITE_COMPLETE: + c->write_success = true; + c->vc->do_io(VIO::CLOSE); + break; + default: + ink_release_assert(0); + } + + return 0; +} + +int +HttpSM::state_srv_lookup(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_srv_lookup, event); + + ink_assert(t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || ua_entry->vc != NULL); + + switch (event) { + case EVENT_SRV_LOOKUP: + pending_action = NULL; + process_srv_info((HostDBInfo *)data); + break; + case EVENT_SRV_IP_REMOVED: + ink_assert(!"Unexpected SRV event from HostDB. What up, Eric?"); + break; + default: + ink_assert(!"Unexpected event"); + } + + return 0; +} + +int +HttpSM::state_remap_request(int event, void * /* data ATS_UNUSED */) +{ + STATE_ENTER(&HttpSM::state_remap_request, event); + + switch (event) { + case EVENT_REMAP_ERROR: { + ink_assert(!"this doesn't happen"); + pending_action = NULL; + Error("error remapping request [see previous errors]"); + call_transact_and_set_next_state(HttpTransact::HandleRequest); // HandleRequest skips EndRemapRequest + break; + } + + case EVENT_REMAP_COMPLETE: { + pending_action = NULL; + DebugSM("url_rewrite", "completed processor-based remapping request for [%" PRId64 "]", sm_id); + t_state.url_remap_success = remapProcessor.finish_remap(&t_state); + call_transact_and_set_next_state(NULL); + break; + } + + default: + ink_assert("Unexpected event inside state_remap_request"); + break; + } + + return 0; +} + +void +HttpSM::do_remap_request(bool run_inline) +{ + DebugSM("http_seq", "[HttpSM::do_remap_request] Remapping request"); + DebugSM("url_rewrite", "Starting a possible remapping for request [%" PRId64 "]", sm_id); + + bool ret = false; + if (t_state.cop_test_page == false) { + ret = remapProcessor.setup_for_remap(&t_state); + } + + // Preserve pristine url before remap + // This needs to be done after the Host: header for reverse proxy is added to the url, but + // before we return from this function for forward proxy + t_state.pristine_url.create(t_state.hdr_info.client_request.url_get()->m_heap); + t_state.pristine_url.copy(t_state.hdr_info.client_request.url_get()); + + if (!ret) { + DebugSM("url_rewrite", "Could not find a valid remapping entry for this request [%" PRId64 "]", sm_id); + if (!run_inline) { + handleEvent(EVENT_REMAP_COMPLETE, NULL); + } + return; + } + + DebugSM("url_rewrite", "Found a remap map entry for [%" PRId64 "], attempting to remap request and call any plugins", sm_id); + Action *remap_action_handle = remapProcessor.perform_remap(this, &t_state); + + if (remap_action_handle != ACTION_RESULT_DONE) { + DebugSM("url_rewrite", "Still more remapping needed for [%" PRId64 "]", sm_id); + ink_assert(!pending_action); + historical_action = pending_action = remap_action_handle; + } + + return; +} + +void +HttpSM::do_hostdb_lookup() +{ + /* + ////////////////////////////////////////// + // if a connection to the origin server // + // is currently opened --- close it. // + ////////////////////////////////////////// + if (m_origin_server_vc != 0) { + origin_server_close(CLOSE_CONNECTION); + if (m_response_body_tunnel_buffer_.buf() != 0) + m_response_body_tunnel_buffer_.reset(); + } + */ + + ink_assert(t_state.dns_info.lookup_name != NULL); + ink_assert(pending_action == NULL); + + milestones[TS_MILESTONE_DNS_LOOKUP_BEGIN] = Thread::get_hrtime(); + bool use_srv_records = t_state.srv_lookup; + + if (use_srv_records) { + char d[MAXDNAME]; + + memcpy(d, "_http._tcp.", 11); // don't copy '\0' + ink_strlcpy(d + 11, t_state.server_info.name, sizeof(d) - 11); // all in the name of performance! + + DebugSM("dns_srv", "Beginning lookup of SRV records for origin %s", d); + + HostDBProcessor::Options opt; + if (t_state.api_txn_dns_timeout_value != -1) + opt.timeout = t_state.api_txn_dns_timeout_value; + Action *srv_lookup_action_handle = + hostDBProcessor.getSRVbyname_imm(this, (process_srv_info_pfn)&HttpSM::process_srv_info, d, 0, opt); + + if (srv_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = srv_lookup_action_handle; + historical_action = pending_action; + } else { + char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + opt.port = t_state.dns_info.srv_lookup_success ? + t_state.dns_info.srv_port : + t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() : + t_state.hdr_info.client_request.port_get(); + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_session->get_host_res_style(); + + Action *dns_lookup_action_handle = + hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&HttpSM::process_hostdb_info, host_name, 0, opt); + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + historical_action = pending_action; + } else { + call_transact_and_set_next_state(NULL); + } + } + return; + } else { /* we aren't using SRV stuff... */ + DebugSM("http_seq", "[HttpSM::do_hostdb_lookup] Doing DNS Lookup"); + + // If there is not a current server, we must be looking up the origin + // server at the beginning of the transaction + int server_port = t_state.current.server ? + t_state.current.server->dst_addr.host_order_port() : + t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() : + t_state.hdr_info.client_request.port_get(); + + if (t_state.api_txn_dns_timeout_value != -1) { + DebugSM("http_timeout", "beginning DNS lookup. allowing %d mseconds for DNS lookup", t_state.api_txn_dns_timeout_value); + } + + HostDBProcessor::Options opt; + opt.port = server_port; + opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : + HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; + opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; + opt.host_res_style = ua_session->get_host_res_style(); + + Action *dns_lookup_action_handle = hostDBProcessor.getbyname_imm(this, (process_hostdb_info_pfn)&HttpSM::process_hostdb_info, + t_state.dns_info.lookup_name, 0, opt); + + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + historical_action = pending_action; + } else { + call_transact_and_set_next_state(NULL); + } + return; + } + ink_assert(!"not reached"); + return; +} + +void +HttpSM::do_hostdb_reverse_lookup() +{ + ink_assert(t_state.dns_info.lookup_name != NULL); + ink_assert(pending_action == NULL); + + DebugSM("http_seq", "[HttpSM::do_hostdb_reverse_lookup] Doing reverse DNS Lookup"); + + IpEndpoint addr; + ats_ip_pton(t_state.dns_info.lookup_name, &addr.sa); + Action *dns_lookup_action_handle = hostDBProcessor.getbyaddr_re(this, &addr.sa); + + if (dns_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = dns_lookup_action_handle; + historical_action = pending_action; + } + return; +} + +void +HttpSM::do_hostdb_update_if_necessary() +{ + int issue_update = 0; + + if (t_state.current.server == NULL || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL) { + // No server, so update is not necessary + return; + } + // If we failed back over to the origin server, we don't have our + // hostdb information anymore which means we shouldn't update the hostdb + if (!ats_ip_addr_eq(&t_state.current.server->dst_addr.sa, t_state.host_db_info.ip())) { + DebugSM("http", "[%" PRId64 "] skipping hostdb update due to server failover", sm_id); + return; + } + + if (t_state.updated_server_version != HostDBApplicationInfo::HTTP_VERSION_UNDEFINED) { + // we may have incorrectly assumed that the hostdb had the wrong version of + // http for the server because our first few connect attempts to the server + // failed, causing us to downgrade our requests to a lower version and changing + // our information about the server version. + // + // This test therefore just issues the update only if the hostdb version is + // in fact different from the version we want the value to be updated to. + if (t_state.host_db_info.app.http_data.http_version != t_state.updated_server_version) { + t_state.host_db_info.app.http_data.http_version = t_state.updated_server_version; + issue_update |= 1; + } + + t_state.updated_server_version = HostDBApplicationInfo::HTTP_VERSION_UNDEFINED; + } + // Check to see if we need to report or clear a connection failure + if (t_state.current.server->had_connect_fail()) { + issue_update |= 1; + mark_host_failure(&t_state.host_db_info, t_state.client_request_time); + } else { + if (t_state.host_db_info.app.http_data.last_failure != 0) { + t_state.host_db_info.app.http_data.last_failure = 0; + issue_update |= 1; + char addrbuf[INET6_ADDRPORTSTRLEN]; + DebugSM("http", "[%" PRId64 "] hostdb update marking IP: %s as up", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + DebugSM("http", "[%" PRId64 "] hostdb update marking IP: %s as up", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + } + + if (t_state.dns_info.srv_lookup_success && t_state.dns_info.srv_app.http_data.last_failure != 0) { + t_state.dns_info.srv_app.http_data.last_failure = 0; + hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &t_state.dns_info.srv_app); + DebugSM("http", "[%" PRId64 "] hostdb update marking SRV: %s as up", sm_id, t_state.dns_info.srv_hostname); + } + } + + if (issue_update) { + hostDBProcessor.setby(t_state.current.server->name, strlen(t_state.current.server->name), &t_state.current.server->dst_addr.sa, + &t_state.host_db_info.app); + } + + char addrbuf[INET6_ADDRPORTSTRLEN]; + DebugSM("http", "server info = %s", ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + return; +} + +/* + * range entry valid [a,b] (a >= 0 and b >= 0 and a <= b) + * HttpTransact::RANGE_NONE if the content length of cached copy is zero or + * no range entry + * HttpTransact::RANGE_NOT_SATISFIABLE iff all range entries are valid but + * none overlap the current extent of the cached copy + * HttpTransact::RANGE_NOT_HANDLED if out-of-order Range entries or + * the cached copy`s content_length is INT64_MAX (e.g. read_from_writer and trunked) + * HttpTransact::RANGE_REQUESTED if all sub range entries are valid and + * in order (remove the entries that not overlap the extent of cache copy) + */ +void +HttpSM::parse_range_and_compare(MIMEField *field, int64_t content_length) +{ + int prev_good_range = -1; + const char *value; + int value_len; + int n_values; + int nr = 0; // number of valid ranges, also index to range array. + int not_satisfy = 0; + HdrCsvIter csv; + const char *s, *e, *tmp; + RangeRecord *ranges = NULL; + int64_t start, end; + + ink_assert(field != NULL && t_state.range_setup == HttpTransact::RANGE_NONE && t_state.ranges == NULL); + + if (content_length <= 0) + return; + + // ToDo: Can this really happen? + if (content_length == INT64_MAX) { + t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED; + return; + } + + if (parse_range_done) { + Debug("http_range", "parse_range already done, t_state.range_setup %d", t_state.range_setup); + return; + } + parse_range_done = true; + + n_values = 0; + value = csv.get_first(field, &value_len); + while (value) { + ++n_values; + value = csv.get_next(&value_len); + } + + value = csv.get_first(field, &value_len); + if (n_values <= 0 || ptr_len_ncmp(value, value_len, "bytes=", 6)) + return; + + ranges = new RangeRecord[n_values]; + value += 6; // skip leading 'bytes=' + value_len -= 6; + + // assume range_in_cache + t_state.range_in_cache = true; + + for (; value; value = csv.get_next(&value_len)) { + if (!(tmp = (const char *)memchr(value, '-', value_len))) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + // process start value + s = value; + e = tmp; + // skip leading white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) + ; + + if (s >= e) + start = -1; + else { + for (start = 0; s < e && *s >= '0' && *s <= '9'; ++s) + start = start * 10 + (*s - '0'); + // skip last white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) + ; + + if (s < e || start < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + } + + // process end value + s = tmp + 1; + e = value + value_len; + // skip leading white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) + ; + + if (s >= e) { + if (start < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } else if (start >= content_length) { + not_satisfy++; + continue; + } + end = content_length - 1; + } else { + for (end = 0; s < e && *s >= '0' && *s <= '9'; ++s) + end = end * 10 + (*s - '0'); + // skip last white spaces + for (; s < e && ParseRules::is_ws(*s); ++s) + ; + + if (s < e || end < 0) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + if (start < 0) { + if (end >= content_length) + end = content_length; + start = content_length - end; + end = content_length - 1; + } else if (start >= content_length && start <= end) { + not_satisfy++; + continue; + } + + if (end >= content_length) + end = content_length - 1; + } + + if (start > end) { + t_state.range_setup = HttpTransact::RANGE_NONE; + goto Lfaild; + } + + if (prev_good_range >= 0 && start <= ranges[prev_good_range]._end) { + t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED; + goto Lfaild; + } + + ink_assert(start >= 0 && end >= 0 && start < content_length && end < content_length); + + prev_good_range = nr; + ranges[nr]._start = start; + ranges[nr]._end = end; + ++nr; + + if (!cache_sm.cache_read_vc->is_pread_capable() && cache_config_read_while_writer == 2) { + // write in progress, check if request range not in cache yet + HTTPInfo::FragOffset *frag_offset_tbl = t_state.cache_info.object_read->get_frag_table(); + int frag_offset_cnt = t_state.cache_info.object_read->get_frag_offset_count(); + + if (!frag_offset_tbl || !frag_offset_cnt || (frag_offset_tbl[frag_offset_cnt - 1] < (uint64_t)end)) { + Debug("http_range", "request range in cache, end %" PRId64 ", frg_offset_cnt %d" PRId64, end, frag_offset_cnt); + t_state.range_in_cache = false; + } + } + } + + if (nr > 0) { + t_state.range_setup = HttpTransact::RANGE_REQUESTED; + t_state.ranges = ranges; + t_state.num_range_fields = nr; + return; + } + + if (not_satisfy) + t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE; + +Lfaild: + t_state.range_in_cache = false; + t_state.num_range_fields = -1; + delete[] ranges; + return; +} + +void +HttpSM::calculate_output_cl(int64_t num_chars_for_ct, int64_t num_chars_for_cl) +{ + int i; + + if (t_state.range_setup != HttpTransact::RANGE_REQUESTED && t_state.range_setup != HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED) + return; + + ink_assert(t_state.ranges); + + if (t_state.num_range_fields == 1) { + t_state.range_output_cl = t_state.ranges[0]._end - t_state.ranges[0]._start + 1; + } else { + for (i = 0; i < t_state.num_range_fields; i++) { + if (t_state.ranges[i]._start >= 0) { + t_state.range_output_cl += boundary_size; + t_state.range_output_cl += sub_header_size + num_chars_for_ct; + t_state.range_output_cl += + num_chars_for_int(t_state.ranges[i]._start) + num_chars_for_int(t_state.ranges[i]._end) + num_chars_for_cl + 2; + t_state.range_output_cl += t_state.ranges[i]._end - t_state.ranges[i]._start + 1; + t_state.range_output_cl += 2; + } + } + + t_state.range_output_cl += boundary_size + 2; + } + + Debug("http_range", "Pre-calculated Content-Length for Range response is %" PRId64, t_state.range_output_cl); +} + +void +HttpSM::do_range_parse(MIMEField *range_field) +{ + int num_chars_for_ct = 0; + t_state.cache_info.object_read->response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, &num_chars_for_ct); + + int64_t content_length = t_state.cache_info.object_read->object_size_get(); + int64_t num_chars_for_cl = num_chars_for_int(content_length); + + parse_range_and_compare(range_field, content_length); + calculate_output_cl(num_chars_for_ct, num_chars_for_cl); +} + +// this function looks for any Range: headers, parses them and either +// sets up a transform processor to handle the request OR defers to the +// HttpTunnel +void +HttpSM::do_range_setup_if_necessary() +{ + MIMEField *field; + INKVConnInternal *range_trans; + int field_content_type_len = -1; + const char *content_type; + + ink_assert(t_state.cache_info.object_read != NULL); + + field = t_state.hdr_info.client_request.field_find(MIME_FIELD_RANGE, MIME_LEN_RANGE); + ink_assert(field != NULL); + + t_state.range_setup = HttpTransact::RANGE_NONE; + + if (t_state.method == HTTP_WKSIDX_GET && t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1)) { + do_range_parse(field); + + if (t_state.range_setup == HttpTransact::RANGE_REQUESTED) { + if (!t_state.range_in_cache) { + Debug("http_range", "range can't be satisifed from cache, force origin request"); + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_MISS; + return; + } + + // if only one range entry and pread is capable, no need transform range + if (t_state.num_range_fields == 1 && cache_sm.cache_read_vc->is_pread_capable()) { + t_state.range_setup = HttpTransact::RANGE_NOT_TRANSFORM_REQUESTED; + } else if (api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK) == NULL) { + Debug("http_trans", "Unable to accelerate range request, fallback to transform"); + content_type = t_state.cache_info.object_read->response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, + &field_content_type_len); + // create a Range: transform processor for requests of type Range: bytes=1-2,4-5,10-100 (eg. multiple ranges) + range_trans = transformProcessor.range_transform(mutex, t_state.ranges, t_state.num_range_fields, + &t_state.hdr_info.transform_response, content_type, field_content_type_len, + t_state.cache_info.object_read->object_size_get()); + api_hooks.append(TS_HTTP_RESPONSE_TRANSFORM_HOOK, range_trans); + } + } + } +} + +void +HttpSM::do_cache_lookup_and_read() +{ + // TODO decide whether to uncomment after finish testing redirect + // ink_assert(server_session == NULL); + ink_assert(pending_action == 0); + + HTTP_INCREMENT_DYN_STAT(http_cache_lookups_stat); + + milestones[TS_MILESTONE_CACHE_OPEN_READ_BEGIN] = Thread::get_hrtime(); + t_state.cache_lookup_result = HttpTransact::CACHE_LOOKUP_NONE; + t_state.cache_info.lookup_count++; + // YTS Team, yamsat Plugin + // Changed the lookup_url to c_url which enables even + // the new redirect url to perform a CACHE_LOOKUP + URL *c_url; + if (t_state.redirect_info.redirect_in_process && !t_state.txn_conf->redirect_use_orig_cache_key) + c_url = t_state.hdr_info.client_request.url_get(); + else + c_url = t_state.cache_info.lookup_url; + + DebugSM("http_seq", "[HttpSM::do_cache_lookup_and_read] [%" PRId64 "] Issuing cache lookup for URL %s", sm_id, + c_url->string_get(&t_state.arena)); + + HttpCacheKey key; + Cache::generate_key(&key, c_url, t_state.txn_conf->cache_generation_number); + + Action *cache_action_handle = + cache_sm.open_read(&key, c_url, &t_state.hdr_info.client_request, &(t_state.cache_info.config), + (time_t)((t_state.cache_control.pin_in_cache_for < 0) ? 0 : t_state.cache_control.pin_in_cache_for)); + // + // pin_in_cache value is an open_write parameter. + // It is passed in open_read to allow the cluster to + // optimize the typical open_read/open_read failed/open_write + // sequence. + // + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + historical_action = pending_action; + } + REMEMBER((long)pending_action, reentrancy_count); + + return; +} + +void +HttpSM::do_cache_delete_all_alts(Continuation *cont) +{ + // Do not delete a non-existant object. + ink_assert(t_state.cache_info.object_read); + + DebugSM("http_seq", "[HttpSM::do_cache_delete_all_alts] Issuing cache delete for %s", + t_state.cache_info.lookup_url->string_get_ref()); + + Action *cache_action_handle = NULL; + + HttpCacheKey key; + Cache::generate_key(&key, t_state.cache_info.lookup_url, t_state.txn_conf->cache_generation_number); + cache_action_handle = cacheProcessor.remove(cont, &key, t_state.cache_control.cluster_cache_local); + if (cont != NULL) { + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + historical_action = pending_action; + } + } + + return; +} + +inline void +HttpSM::do_cache_prepare_write() +{ + // statistically no need to retry when we are trying to lock + // LOCK_URL_SECOND url because the server's behavior is unlikely to change + milestones[TS_MILESTONE_CACHE_OPEN_WRITE_BEGIN] = Thread::get_hrtime(); + bool retry = (t_state.api_lock_url == HttpTransact::LOCK_URL_FIRST); + do_cache_prepare_action(&cache_sm, t_state.cache_info.object_read, retry); +} + +inline void +HttpSM::do_cache_prepare_write_transform() +{ + if (cache_sm.cache_write_vc != NULL || tunnel.has_cache_writer()) + do_cache_prepare_action(&transform_cache_sm, NULL, false, true); + else + do_cache_prepare_action(&transform_cache_sm, NULL, false); +} + +void +HttpSM::do_cache_prepare_update() +{ + if (t_state.cache_info.object_read != NULL && t_state.cache_info.object_read->valid() && + t_state.cache_info.object_store.valid() && t_state.cache_info.object_store.response_get() != NULL && + t_state.cache_info.object_store.response_get()->valid() && + t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_GET) { + t_state.cache_info.object_store.request_set(t_state.cache_info.object_read->request_get()); + // t_state.cache_info.object_read = NULL; + // cache_sm.close_read(); + + t_state.transact_return_point = HttpTransact::HandleUpdateCachedObject; + ink_assert(cache_sm.cache_write_vc == NULL); + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_cache_open_write); + // don't retry read for update + do_cache_prepare_action(&cache_sm, t_state.cache_info.object_read, false); + } else { + t_state.api_modifiable_cached_resp = false; + call_transact_and_set_next_state(HttpTransact::HandleApiErrorJump); + } +} + +void +HttpSM::do_cache_prepare_action(HttpCacheSM *c_sm, CacheHTTPInfo *object_read_info, bool retry, bool allow_multiple) +{ + URL *o_url, *c_url, *s_url; + bool restore_client_request = false; + + ink_assert(!pending_action); + + if (t_state.api_lock_url == HttpTransact::LOCK_URL_FIRST) { + if (t_state.redirect_info.redirect_in_process) { + o_url = &(t_state.redirect_info.original_url); + ink_assert(o_url->valid()); + restore_client_request = true; + s_url = o_url; + } else { + o_url = &(t_state.cache_info.original_url); + if (o_url->valid()) + s_url = o_url; + else + s_url = t_state.cache_info.lookup_url; + } + } else if (t_state.api_lock_url == HttpTransact::LOCK_URL_SECOND) { + s_url = &t_state.cache_info.lookup_url_storage; + } else { + ink_assert(t_state.api_lock_url == HttpTransact::LOCK_URL_ORIGINAL); + s_url = &(t_state.cache_info.original_url); + restore_client_request = true; + } + + // modify client request to make it have the url we are going to + // store into the cache + if (restore_client_request) { + c_url = t_state.hdr_info.client_request.url_get(); + s_url->copy(c_url); + } + + ink_assert(s_url != NULL && s_url->valid()); + DebugSM("http_cache_write", "[%" PRId64 "] writing to cache with URL %s", sm_id, s_url->string_get(&t_state.arena)); + + HttpCacheKey key; + Cache::generate_key(&key, s_url, t_state.txn_conf->cache_generation_number); + + Action *cache_action_handle = c_sm->open_write( + &key, s_url, &t_state.hdr_info.client_request, object_read_info, + (time_t)((t_state.cache_control.pin_in_cache_for < 0) ? 0 : t_state.cache_control.pin_in_cache_for), retry, allow_multiple); + + if (cache_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = cache_action_handle; + historical_action = pending_action; + } +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::do_http_server_open() +// +////////////////////////////////////////////////////////////////////////// +void +HttpSM::do_http_server_open(bool raw) +{ + int ip_family = t_state.current.server->dst_addr.sa.sa_family; + DebugSM("http_track", "entered inside do_http_server_open ][%s]", ats_ip_family_name(ip_family)); + + // Make sure we are on the "right" thread + if (ua_session) { + if ((pending_action = ua_session->adjust_thread(this, EVENT_INTERVAL, NULL))) { + return; // Go away if we reschedule + } + } + pending_action = NULL; + ink_assert(server_entry == NULL); + + // ua_entry can be null if a scheduled update is also a reverse proxy + // request. Added REVPROXY to the assert below, and then changed checks + // to be based on ua_session != NULL instead of req_flavor value. + ink_assert(ua_entry != NULL || t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY); + + ink_assert(pending_action == NULL); + + if (false == t_state.api_server_addr_set) { + ink_assert(t_state.current.server->dst_addr.host_order_port() > 0); + } else { + ink_assert(t_state.current.server->dst_addr.port() != 0); // verify the plugin set it to something. + } + + char addrbuf[INET6_ADDRPORTSTRLEN]; + DebugSM("http", "[%" PRId64 "] open connection to %s: %s", sm_id, t_state.current.server->name, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + + if (plugin_tunnel) { + PluginVCCore *t = plugin_tunnel; + plugin_tunnel = NULL; + Action *pvc_action_handle = t->connect_re(this); + + // This connect call is always reentrant + ink_release_assert(pvc_action_handle == ACTION_RESULT_DONE); + return; + } + + DebugSM("http_seq", "[HttpSM::do_http_server_open] Sending request to server"); + + milestones[TS_MILESTONE_SERVER_CONNECT] = Thread::get_hrtime(); + if (milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] == 0) { + milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] = milestones[TS_MILESTONE_SERVER_CONNECT]; + } + + if (t_state.pCongestionEntry != NULL) { + if (t_state.pCongestionEntry->F_congested() && + (!t_state.pCongestionEntry->proxy_retry(milestones[TS_MILESTONE_SERVER_CONNECT]))) { + t_state.congestion_congested_or_failed = 1; + t_state.pCongestionEntry->stat_inc_F(); + CONGEST_INCREMENT_DYN_STAT(congested_on_F_stat); + handleEvent(CONGESTION_EVENT_CONGESTED_ON_F, NULL); + return; + } else if (t_state.pCongestionEntry->M_congested(ink_hrtime_to_sec(milestones[TS_MILESTONE_SERVER_CONNECT]))) { + t_state.pCongestionEntry->stat_inc_M(); + t_state.congestion_congested_or_failed = 1; + CONGEST_INCREMENT_DYN_STAT(congested_on_M_stat); + handleEvent(CONGESTION_EVENT_CONGESTED_ON_M, NULL); + return; + } + } + // If this is not a raw connection, we try to get a session from the + // shared session pool. Raw connections are for SSLs tunnel and + // require a new connection + // + + // This problem with POST requests is a bug. Because of the issue of the + // race with us sending a request after server has closed but before the FIN + // gets to us, we should open a new connection for POST. I believe TS used + // to do this but as far I can tell the code that prevented keep-alive if + // there is a request body has been removed. + + // If we are sending authorizations headers, mark the connection private + // + // We do this here because it means that we will not waste a connection from the pool if we already + // know that the session will be private. This is overridable meaning that if a plugin later decides + // it shouldn't be private it can still be returned to a shared pool. + // + if (t_state.txn_conf->auth_server_session_private == 1 && + t_state.hdr_info.server_request.presence(MIME_PRESENCE_AUTHORIZATION | MIME_PRESENCE_PROXY_AUTHORIZATION | + MIME_PRESENCE_WWW_AUTHENTICATE)) { + DebugSM("http_ss_auth", "Setting server session to private for authorization header"); + will_be_private_ss = true; + } + + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + // don't share the session if keep-alive for post is not on + if (t_state.txn_conf->keep_alive_post_out == 0) { + DebugSM("http_ss", "Setting server session to private because of keep-alive post out"); + will_be_private_ss = true; + } + } + + // If there is already an attached server session mark it as private. + if (server_session != NULL && will_be_private_ss) { + set_server_session_private(true); + } + + if (raw == false && TS_SERVER_SESSION_SHARING_MATCH_NONE != t_state.txn_conf->server_session_sharing_match && + (t_state.txn_conf->keep_alive_post_out == 1 || t_state.hdr_info.request_content_length == 0) && !is_private() && + ua_session != NULL) { + HSMresult_t shared_result; + shared_result = httpSessionManager.acquire_session(this, // state machine + &t_state.current.server->dst_addr.sa, // ip + port + t_state.current.server->name, // hostname + ua_session, // has ptr to bound ua sessions + this // sm + ); + + switch (shared_result) { + case HSM_DONE: + hsm_release_assert(server_session != NULL); + handle_http_server_open(); + return; + case HSM_NOT_FOUND: + hsm_release_assert(server_session == NULL); + break; + case HSM_RETRY: + // Could not get shared pool lock + // FIX: should retry lock + break; + default: + hsm_release_assert(0); + } + } + // Avoid a problem where server session sharing is disabled and we have keep-alive, we are trying to open a new server + // session when we already have an attached server session. + else if ((TS_SERVER_SESSION_SHARING_MATCH_NONE == t_state.txn_conf->server_session_sharing_match || is_private()) && + (ua_session != NULL)) { + HttpServerSession *existing_ss = ua_session->get_server_session(); + + if (existing_ss) { + // [amc] Not sure if this is the best option, but we don't get here unless session sharing is disabled + // so there's no point in further checking on the match or pool values. But why check anything? The + // client has already exchanged a request with this specific origin server and has sent another one + // shouldn't we just automatically keep the association? + if (ats_ip_addr_port_eq(&existing_ss->server_ip.sa, &t_state.current.server->dst_addr.sa)) { + ua_session->attach_server_session(NULL); + existing_ss->state = HSS_ACTIVE; + this->attach_server_session(existing_ss); + hsm_release_assert(server_session != NULL); + handle_http_server_open(); + return; + } else { + // As this is in the non-sharing configuration, we want to close + // the existing connection and call connect_re to get a new one + existing_ss->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + existing_ss->release(); + ua_session->attach_server_session(NULL); + } + } + } + // Otherwise, we release the existing connection and call connect_re + // to get a new one. + // ua_session is null when t_state.req_flavor == REQ_FLAVOR_SCHEDULED_UPDATE + else if (ua_session != NULL) { + HttpServerSession *existing_ss = ua_session->get_server_session(); + if (existing_ss) { + existing_ss->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + existing_ss->release(); + ua_session->attach_server_session(NULL); + } + } + // Check to see if we have reached the max number of connections. + // Atomically read the current number of connections and check to see + // if we have gone above the max allowed. + if (t_state.http_config_param->server_max_connections > 0) { + int64_t sum; + + HTTP_READ_GLOBAL_DYN_SUM(http_current_server_connections_stat, sum); + + // Note that there is a potential race condition here where + // the value of the http_current_server_connections_stat gets changed + // between the statement above and the check below. + // If this happens, we might go over the max by 1 but this is ok. + if (sum >= t_state.http_config_param->server_max_connections) { + ink_assert(pending_action == NULL); + pending_action = eventProcessor.schedule_in(this, HRTIME_MSECONDS(100)); + httpSessionManager.purge_keepalives(); + return; + } + } + // Check to see if we have reached the max number of connections on this + // host. + if (t_state.txn_conf->origin_max_connections > 0) { + ConnectionCount *connections = ConnectionCount::getInstance(); + + INK_MD5 hostname_hash; + MD5Context md5_ctx; + md5_ctx.hash_immediate(hostname_hash, static_cast(t_state.current.server->name), + strlen(t_state.current.server->name)); + + ip_port_text_buffer addrbuf; + if (connections->getCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match) >= + t_state.txn_conf->origin_max_connections) { + DebugSM("http", "[%" PRId64 "] over the number of connection for this host: %s", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + ink_assert(pending_action == NULL); + + // if we were previously queued, or the queue is disabled-- just reschedule + if (t_state.origin_request_queued || t_state.txn_conf->origin_max_connections_queue < 0) { + pending_action = eventProcessor.schedule_in(this, HRTIME_MSECONDS(100)); + return; + } else if (t_state.txn_conf->origin_max_connections_queue > 0) { // If we have a queue, lets see if there is a slot + ConnectionCountQueue *waiting_connections = ConnectionCountQueue::getInstance(); + // if there is space in the queue + if (waiting_connections->getCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match) < + t_state.txn_conf->origin_max_connections_queue) { + t_state.origin_request_queued = true; + Debug("http", "[%" PRId64 "] queued for this host: %s", sm_id, + ats_ip_ntop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + waiting_connections->incrementCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match, 1); + pending_action = eventProcessor.schedule_in(this, HRTIME_MSECONDS(100)); + } else { // the queue is full + t_state.current.state = HttpTransact::CONNECTION_ERROR; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + } else { // the queue is set to 0 + t_state.current.state = HttpTransact::CONNECTION_ERROR; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } + return; + } + } + + // We did not manage to get an existing session + // and need to open a new connection + Action *connect_action_handle; + + NetVCOptions opt; + opt.f_blocking_connect = false; + opt.set_sock_param(t_state.txn_conf->sock_recv_buffer_size_out, t_state.txn_conf->sock_send_buffer_size_out, + t_state.txn_conf->sock_option_flag_out, t_state.txn_conf->sock_packet_mark_out, + t_state.txn_conf->sock_packet_tos_out); + + opt.ip_family = ip_family; + + if (ua_session) { + opt.local_port = ua_session->get_outbound_port(); + + const IpAddr &outbound_ip = AF_INET6 == ip_family ? ua_session->get_outbound_ip6() : ua_session->get_outbound_ip4(); + if (outbound_ip.isValid()) { + opt.addr_binding = NetVCOptions::INTF_ADDR; + opt.local_ip = outbound_ip; + } else if (ua_session->is_outbound_transparent()) { + opt.addr_binding = NetVCOptions::FOREIGN_ADDR; + opt.local_ip = t_state.client_info.src_addr; + /* If the connection is server side transparent, we can bind to the + port that the client chose instead of randomly assigning one at + the proxy. This is controlled by the 'use_client_source_port' + configuration parameter. + */ + + NetVConnection *client_vc = ua_session->get_netvc(); + if (t_state.http_config_param->use_client_source_port && NULL != client_vc) { + opt.local_port = client_vc->get_remote_port(); + } + } + } + + int scheme_to_use = t_state.scheme; // get initial scheme + + if (!t_state.is_websocket) { // if not websocket, then get scheme from server request + int new_scheme_to_use = t_state.hdr_info.server_request.url_get()->scheme_get_wksidx(); + // if the server_request url scheme was never set, try the client_request + if (new_scheme_to_use < 0) { + new_scheme_to_use = t_state.hdr_info.client_request.url_get()->scheme_get_wksidx(); + } + if (new_scheme_to_use >= 0) { // found a new scheme, use it + scheme_to_use = new_scheme_to_use; + } + } + + if (scheme_to_use == URL_WKSIDX_HTTPS) { + DebugSM("http", "calling sslNetProcessor.connect_re"); + int len = 0; + const char *host = t_state.hdr_info.server_request.host_get(&len); + if (host && len > 0) + opt.set_sni_servername(host, len); + connect_action_handle = sslNetProcessor.connect_re(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + &opt); + } else { + if (t_state.method != HTTP_WKSIDX_CONNECT) { + DebugSM("http", "calling netProcessor.connect_re"); + connect_action_handle = netProcessor.connect_re(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + &opt); + } else { + // Setup the timeouts + // Set the inactivity timeout to the connect timeout so that we + // we fail this server if it doesn't start sending the response + // header + MgmtInt connect_timeout; + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + connect_timeout = t_state.txn_conf->post_connect_attempts_timeout; + } else if (t_state.current.server == &t_state.parent_info) { + connect_timeout = t_state.http_config_param->parent_connect_timeout; + } else { + if (t_state.pCongestionEntry != NULL) + connect_timeout = t_state.pCongestionEntry->connect_timeout(); + else + connect_timeout = t_state.txn_conf->connect_attempts_timeout; + } + DebugSM("http", "calling netProcessor.connect_s"); + connect_action_handle = netProcessor.connect_s(this, // state machine + &t_state.current.server->dst_addr.sa, // addr + port + connect_timeout, &opt); + } + } + + if (connect_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = connect_action_handle; + historical_action = pending_action; + } + + return; +} + +void +HttpSM::do_icp_lookup() +{ + ink_assert(pending_action == NULL); + + URL *o_url = &t_state.cache_info.original_url; + + Action *icp_lookup_action_handle = icpProcessor.ICPQuery(this, o_url->valid() ? o_url : t_state.cache_info.lookup_url); + + if (icp_lookup_action_handle != ACTION_RESULT_DONE) { + ink_assert(!pending_action); + pending_action = icp_lookup_action_handle; + historical_action = pending_action; + } + + return; +} + +void +HttpSM::do_api_callout_internal() +{ + if (t_state.backdoor_request) { + handle_api_return(); + return; + } + + switch (t_state.api_next_action) { + case HttpTransact::SM_ACTION_API_SM_START: + cur_hook_id = TS_HTTP_TXN_START_HOOK; + break; + case HttpTransact::SM_ACTION_API_PRE_REMAP: + cur_hook_id = TS_HTTP_PRE_REMAP_HOOK; + break; + case HttpTransact::SM_ACTION_API_POST_REMAP: + cur_hook_id = TS_HTTP_POST_REMAP_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + cur_hook_id = TS_HTTP_READ_REQUEST_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_OS_DNS: + cur_hook_id = TS_HTTP_OS_DNS_HOOK; + break; + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + cur_hook_id = TS_HTTP_SEND_REQUEST_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + cur_hook_id = TS_HTTP_READ_CACHE_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: + cur_hook_id = TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK; + break; + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + cur_hook_id = TS_HTTP_READ_RESPONSE_HDR_HOOK; + break; + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + cur_hook_id = TS_HTTP_SEND_RESPONSE_HDR_HOOK; + milestones[TS_MILESTONE_UA_BEGIN_WRITE] = Thread::get_hrtime(); + break; + case HttpTransact::SM_ACTION_API_SM_SHUTDOWN: + if (callout_state == HTTP_API_IN_CALLOUT || callout_state == HTTP_API_DEFERED_SERVER_ERROR) { + callout_state = HTTP_API_DEFERED_CLOSE; + return; + } else { + cur_hook_id = TS_HTTP_TXN_CLOSE_HOOK; + } + break; + default: + cur_hook_id = (TSHttpHookID)-1; + ink_assert(!"not reached"); + } + + cur_hook = NULL; + cur_hooks = 0; + state_api_callout(0, NULL); +} + +VConnection * +HttpSM::do_post_transform_open() +{ + ink_assert(post_transform_info.vc == NULL); + + if (is_action_tag_set("http_post_nullt")) { + txn_hook_prepend(TS_HTTP_REQUEST_TRANSFORM_HOOK, transformProcessor.null_transform(mutex)); + } + + post_transform_info.vc = transformProcessor.open(this, api_hooks.get(TS_HTTP_REQUEST_TRANSFORM_HOOK)); + if (post_transform_info.vc) { + // Record the transform VC in our table + post_transform_info.entry = vc_table.new_entry(); + post_transform_info.entry->vc = post_transform_info.vc; + post_transform_info.entry->vc_type = HTTP_TRANSFORM_VC; + } + + return post_transform_info.vc; +} + +VConnection * +HttpSM::do_transform_open() +{ + ink_assert(transform_info.vc == NULL); + APIHook *hooks; + + if (is_action_tag_set("http_nullt")) { + txn_hook_prepend(TS_HTTP_RESPONSE_TRANSFORM_HOOK, transformProcessor.null_transform(mutex)); + } + + hooks = api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK); + if (hooks) { + transform_info.vc = transformProcessor.open(this, hooks); + + // Record the transform VC in our table + transform_info.entry = vc_table.new_entry(); + transform_info.entry->vc = transform_info.vc; + transform_info.entry->vc_type = HTTP_TRANSFORM_VC; + } else { + transform_info.vc = NULL; + } + + return transform_info.vc; +} + +void +HttpSM::mark_host_failure(HostDBInfo *info, time_t time_down) +{ + char addrbuf[INET6_ADDRPORTSTRLEN]; + + if (info->app.http_data.last_failure == 0) { + char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, 0); + Log::error("CONNECT: could not connect to %s " + "for '%s' (setting last failure time)", + ats_ip_ntop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), url_str ? url_str : ""); + if (url_str) + t_state.arena.str_free(url_str); + } + + info->app.http_data.last_failure = time_down; + +#ifdef DEBUG + ink_assert(ink_cluster_time() + t_state.txn_conf->down_server_timeout > time_down); +#endif + + DebugSM("http", "[%" PRId64 "] hostdb update marking IP: %s as down", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); +} + +void +HttpSM::set_ua_abort(HttpTransact::AbortState_t ua_abort, int event) +{ + t_state.client_info.abort = ua_abort; + + switch (ua_abort) { + case HttpTransact::ABORTED: + case HttpTransact::MAYBE_ABORTED: + t_state.squid_codes.log_code = SQUID_LOG_ERR_CLIENT_ABORT; + break; + default: + // Handled here: + // HttpTransact::ABORT_UNDEFINED, HttpTransact::DIDNOT_ABORT + break; + } + + // Set the connection attribute code for the client so that + // we log the client finish code correctly + switch (event) { + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.client_info.state = HttpTransact::ACTIVE_TIMEOUT; + break; + case VC_EVENT_INACTIVITY_TIMEOUT: + t_state.client_info.state = HttpTransact::INACTIVE_TIMEOUT; + break; + case VC_EVENT_ERROR: + t_state.client_info.state = HttpTransact::CONNECTION_ERROR; + break; + } +} + +void +HttpSM::mark_server_down_on_client_abort() +{ + ///////////////////////////////////////////////////// + // Check see if the client aborted because the // + // origin server was too slow in sending the // + // response header. If so, mark that // + // server as down so other clients won't try to // + // for revalidation or select it from a round // + // robin set // + // // + // Note: we do not want to mark parent or icp // + // proxies as down with this metric because // + // that upstream proxy may be working but // + // the actual origin server is one that is hung // + ///////////////////////////////////////////////////// + if (t_state.current.request_to == HttpTransact::ORIGIN_SERVER && t_state.hdr_info.request_content_length == 0) { + if (milestones[TS_MILESTONE_SERVER_FIRST_CONNECT] != 0 && milestones[TS_MILESTONE_SERVER_FIRST_READ] == 0) { + // Check to see if client waited for the threshold + // to declare the origin server as down + ink_hrtime wait = Thread::get_hrtime() - milestones[TS_MILESTONE_SERVER_FIRST_CONNECT]; + if (wait < 0) { + wait = 0; + } + if (ink_hrtime_to_sec(wait) > t_state.txn_conf->client_abort_threshold) { + t_state.current.server->set_connect_fail(ETIMEDOUT); + do_hostdb_update_if_necessary(); + } + } + } +} + +// void HttpSM::release_server_session() +// +// Called when we are not tunneling a response from the +// server. If the session is keep alive, release it back to the +// shared pool, otherwise close it +// +void +HttpSM::release_server_session(bool serve_from_cache) +{ + if (server_session == NULL) { + return; + } + + if (TS_SERVER_SESSION_SHARING_MATCH_NONE != t_state.txn_conf->server_session_sharing_match && t_state.current.server != NULL && + t_state.current.server->keep_alive == HTTP_KEEPALIVE && t_state.hdr_info.server_response.valid() && + t_state.hdr_info.server_request.valid() && (t_state.hdr_info.server_response.status_get() == HTTP_STATUS_NOT_MODIFIED || + (t_state.hdr_info.server_request.method_get_wksidx() == HTTP_WKSIDX_HEAD && + t_state.www_auth_content != HttpTransact::CACHE_AUTH_NONE)) && + plugin_tunnel_type == HTTP_NO_PLUGIN_TUNNEL) { + HTTP_DECREMENT_DYN_STAT(http_current_server_transactions_stat); + server_session->server_trans_stat--; + server_session->attach_hostname(t_state.current.server->name); + if (t_state.www_auth_content == HttpTransact::CACHE_AUTH_NONE || serve_from_cache == false) { + // Must explicitly set the keep_alive_no_activity time before doing the release + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out)); + server_session->release(); + } else { + // an authenticated server connection - attach to the local client + // we are serving from cache for the current transaction + t_state.www_auth_content = HttpTransact::CACHE_AUTH_SERVE; + ua_session->attach_server_session(server_session, false); + } + } else { + server_session->do_io_close(); + } + + ink_assert(server_entry->vc == server_session); + server_entry->in_tunnel = true; + vc_table.cleanup_entry(server_entry); + server_entry = NULL; + server_session = NULL; +} + +// void HttpSM::handle_post_failure() +// +// We failed in our attempt post (or put) a document +// to the server. Two cases happen here. The normal +// one is the server died, in which case we ought to +// return an error to the client. The second one is +// stupid. The server returned a response without reading +// all the post data. In order to be as transparent as +// possible process the server's response +void +HttpSM::handle_post_failure() +{ + STATE_ENTER(&HttpSM::handle_post_failure, VC_EVENT_NONE); + + ink_assert(ua_entry->vc == ua_session); + ink_assert(server_entry->eos == true); + + // First order of business is to clean up from + // the tunnel + // note: since the tunnel is providing the buffer for a lingering + // client read (for abort watching purposes), we need to stop + // the read + if (false == t_state.redirect_info.redirect_in_process) { + ua_entry->read_vio = ua_session->do_io_read(this, 0, NULL); + } + ua_entry->in_tunnel = false; + server_entry->in_tunnel = false; + + // disable redirection in case we got a partial response and then EOS, because the buffer might not + // have the full post and it's deallocating the post buffers here + enable_redirection = false; + tunnel.deallocate_redirect_postdata_buffers(); + + // Don't even think about doing keep-alive after this debacle + t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE; + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + + if (server_buffer_reader->read_avail() > 0) { + tunnel.deallocate_buffers(); + tunnel.reset(); + // There's data from the server so try to read the header + setup_server_read_response_header(); + } else { + tunnel.deallocate_buffers(); + tunnel.reset(); + // Server died + vc_table.cleanup_entry(server_entry); + server_entry = NULL; + server_session = NULL; + t_state.current.state = HttpTransact::CONNECTION_CLOSED; + call_transact_and_set_next_state(HttpTransact::HandleResponse); + } +} + +// void HttpSM::handle_http_server_open() +// +// The server connection is now open. If there is a POST or PUT, +// we need setup a transform is there is one otherwise we need +// to send the request header +// +void +HttpSM::handle_http_server_open() +{ + // if we were a queued request, we need to decrement the queue size-- as we got a connection + if (t_state.origin_request_queued) { + INK_MD5 hostname_hash; + MD5Context md5_ctx; + md5_ctx.hash_immediate(hostname_hash, static_cast(t_state.current.server->name), + strlen(t_state.current.server->name)); + + ConnectionCountQueue *waiting_connections = ConnectionCountQueue::getInstance(); + waiting_connections->incrementCount(t_state.current.server->dst_addr, hostname_hash, + (TSServerSessionSharingMatchType)t_state.txn_conf->server_session_sharing_match, -1); + // The request is now not queued. This is important if the request will ever retry, the t_state is re-used + t_state.origin_request_queued = false; + } + + // [bwyatt] applying per-transaction OS netVC options here + // IFF they differ from the netVC's current options. + // This should keep this from being redundant on a + // server session's first transaction. + if (NULL != server_session) { + NetVConnection *vc = server_session->get_netvc(); + if (vc != NULL && (vc->options.sockopt_flags != t_state.txn_conf->sock_option_flag_out || + vc->options.packet_mark != t_state.txn_conf->sock_packet_mark_out || + vc->options.packet_tos != t_state.txn_conf->sock_packet_tos_out)) { + vc->options.sockopt_flags = t_state.txn_conf->sock_option_flag_out; + vc->options.packet_mark = t_state.txn_conf->sock_packet_mark_out; + vc->options.packet_tos = t_state.txn_conf->sock_packet_tos_out; + vc->apply_options(); + } + } + + if (t_state.pCongestionEntry != NULL) { + if (t_state.congestion_connection_opened == 0) { + t_state.congestion_connection_opened = 1; + t_state.pCongestionEntry->connection_opened(); + } + } + + int method = t_state.hdr_info.server_request.method_get_wksidx(); + if (method != HTTP_WKSIDX_TRACE && + (t_state.hdr_info.request_content_length > 0 || t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) && + do_post_transform_open()) { + do_setup_post_tunnel(HTTP_TRANSFORM_VC); + } else { + setup_server_send_request_api(); + } +} + +// void HttpSM::handle_server_setup_error(int event, void* data) +// +// Handles setting t_state.current.state and calling +// Transact in between opening an origin server connection +// and receiving the response header (in the case of the +// POST, a post tunnel happens in between the sending +// request header and reading the response header +// +void +HttpSM::handle_server_setup_error(int event, void *data) +{ + VIO *vio = (VIO *)data; + ink_assert(vio != NULL); + + STATE_ENTER(&HttpSM::handle_server_setup_error, event); + + // If there is POST or PUT tunnel wait for the tunnel + // to figure out that things have gone to hell + + if (tunnel.is_tunnel_active()) { + ink_assert(server_entry->read_vio == data); + DebugSM("http", "[%" PRId64 "] [handle_server_setup_error] " + "forwarding event %s to post tunnel", + sm_id, HttpDebugNames::get_event_name(event)); + HttpTunnelConsumer *c = tunnel.get_consumer(server_entry->vc); + // it is possible only user agent post->post transform is set up + // this happened for Linux iocore where NET_EVENT_OPEN was returned + // for a non-existing listening port. the hack is to pass the error + // event for server connection to post_transform_info + if (c == NULL && post_transform_info.vc) { + c = tunnel.get_consumer(post_transform_info.vc); + // c->handler_state = HTTP_SM_TRANSFORM_FAIL; + + // No point in proceeding if there is no consumer + // Do we need to do additional clean up in the c == NULL case? + if (c != NULL) { + HttpTunnelProducer *ua_producer = c->producer; + ink_assert(ua_entry->vc == ua_producer->vc); + + ua_entry->vc_handler = &HttpSM::state_watch_for_client_abort; + ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, c->producer->read_buffer); + ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ); + + ua_producer->alive = false; + ua_producer->handler_state = HTTP_SM_POST_SERVER_FAIL; + tunnel.handleEvent(VC_EVENT_ERROR, c->write_vio); + } + } else { + // c could be null here as well + if (c != NULL) + tunnel.handleEvent(event, c->write_vio); + } + return; + } else { + if (post_transform_info.vc) { + HttpTunnelConsumer *c = tunnel.get_consumer(post_transform_info.vc); + if (c && c->handler_state == HTTP_SM_TRANSFORM_OPEN) { + vc_table.cleanup_entry(post_transform_info.entry); + post_transform_info.entry = NULL; + tunnel.deallocate_buffers(); + tunnel.reset(); + } + } + } + + if (event == VC_EVENT_ERROR) { + t_state.cause_of_death_errno = server_session->get_netvc()->lerrno; + } + + switch (event) { + case VC_EVENT_EOS: + t_state.current.state = HttpTransact::CONNECTION_CLOSED; + break; + case VC_EVENT_ERROR: + t_state.current.state = HttpTransact::CONNECTION_ERROR; + break; + case VC_EVENT_ACTIVE_TIMEOUT: + t_state.current.state = HttpTransact::ACTIVE_TIMEOUT; + break; + + case VC_EVENT_INACTIVITY_TIMEOUT: + // If we're writing the request and get an inactivity timeout + // before any bytes are written, the connection to the + // server failed + // In case of TIMEOUT, the iocore sends back + // server_entry->read_vio instead of the write_vio + // if (vio->op == VIO::WRITE && vio->ndone == 0) { + if (server_entry->write_vio && server_entry->write_vio->nbytes > 0 && server_entry->write_vio->ndone == 0) { + t_state.current.state = HttpTransact::CONNECTION_ERROR; + } else { + t_state.current.state = HttpTransact::INACTIVE_TIMEOUT; + } + break; + default: + ink_release_assert(0); + } + + // Closedown server connection and deallocate buffers + ink_assert(server_entry->in_tunnel == false); + vc_table.cleanup_entry(server_entry); + server_entry = NULL; + server_session = NULL; + + // if we are waiting on a plugin callout for + // HTTP_API_SEND_REQUEST_HDR defer calling transact until + // after we've finished processing the plugin callout + switch (callout_state) { + case HTTP_API_NO_CALLOUT: + // Normal fast path case, no api callouts in progress + break; + case HTTP_API_IN_CALLOUT: + case HTTP_API_DEFERED_SERVER_ERROR: + // Callout in progress note that we are in deferring + // the server error + callout_state = HTTP_API_DEFERED_SERVER_ERROR; + return; + case HTTP_API_DEFERED_CLOSE: + // The user agent has shutdown killing the sm + // but we are stuck waiting for the server callout + // to finish so do nothing here. We don't care + // about the server connection at this and are + // just waiting till we can execute the close hook + return; + default: + ink_release_assert(0); + } + + call_transact_and_set_next_state(HttpTransact::HandleResponse); +} + +void +HttpSM::setup_transform_to_server_transfer() +{ + ink_assert(post_transform_info.vc != NULL); + ink_assert(post_transform_info.entry->vc == post_transform_info.vc); + + int64_t nbytes = t_state.hdr_info.transform_request_cl; + int64_t alloc_index = buffer_size_to_index(nbytes); + MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = post_buffer->alloc_reader(); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_post); + + HttpTunnelConsumer *c = tunnel.get_consumer(post_transform_info.vc); + + HttpTunnelProducer *p = tunnel.add_producer(post_transform_info.vc, nbytes, buf_start, &HttpSM::tunnel_handler_transform_read, + HT_TRANSFORM, "post transform"); + tunnel.chain(c, p); + post_transform_info.entry->in_tunnel = true; + + tunnel.add_consumer(server_entry->vc, post_transform_info.vc, &HttpSM::tunnel_handler_post_server, HT_HTTP_SERVER, + "http server post"); + server_entry->in_tunnel = true; + + tunnel.tunnel_run(p); +} + +#ifdef PROXY_DRAIN +void +HttpSM::do_drain_request_body() +{ + int64_t post_bytes = t_state.hdr_info.request_content_length; + int64_t avail = ua_buffer_reader->read_avail(); + + int64_t act_on = (avail < post_bytes) ? avail : post_bytes; + + client_request_body_bytes = act_on; + ua_buffer_reader->consume(act_on); + + ink_assert(client_request_body_bytes <= post_bytes); + + if (client_request_body_bytes < post_bytes) { + ua_buffer_reader->mbuf->size_index = buffer_size_to_index(t_state.hdr_info.request_content_length); + ua_entry->vc_handler = &HttpSM::state_drain_client_request_body; + ua_entry->read_vio = ua_entry->vc->do_io_read(this, post_bytes - client_request_body_bytes, ua_buffer_reader->mbuf); + } else { + call_transact_and_set_next_state(NULL); + } +} +#endif /* PROXY_DRAIN */ + +void +HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type) +{ + bool chunked = (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING); + bool post_redirect = false; + + HttpTunnelProducer *p = NULL; + // YTS Team, yamsat Plugin + // if redirect_in_process and redirection is enabled add static producer + + if (t_state.redirect_info.redirect_in_process && enable_redirection && + (tunnel.postbuf && tunnel.postbuf->postdata_copy_buffer_start != NULL && tunnel.postbuf->postdata_producer_buffer != NULL)) { + post_redirect = true; + // copy the post data into a new producer buffer for static producer + tunnel.postbuf->postdata_producer_buffer->write(tunnel.postbuf->postdata_copy_buffer_start); + int64_t post_bytes = tunnel.postbuf->postdata_producer_reader->read_avail(); + transfered_bytes = post_bytes; + p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, post_bytes, tunnel.postbuf->postdata_producer_reader, + (HttpProducerHandler)NULL, HT_STATIC, "redirect static agent post"); + // the tunnel has taken over the buffer and will free it + tunnel.postbuf->postdata_producer_buffer = NULL; + tunnel.postbuf->postdata_producer_reader = NULL; + } else { + int64_t alloc_index; + // content length is undefined, use default buffer size + if (t_state.hdr_info.request_content_length == HTTP_UNDEFINED_CL) { + alloc_index = (int)t_state.txn_conf->default_buffer_size_index; + if (alloc_index < MIN_CONFIG_BUFFER_SIZE_INDEX || alloc_index > MAX_BUFFER_SIZE_INDEX) { + alloc_index = DEFAULT_REQUEST_BUFFER_SIZE_INDEX; + } + } else { + alloc_index = buffer_size_to_index(t_state.hdr_info.request_content_length); + } + MIOBuffer *post_buffer = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = post_buffer->alloc_reader(); + int64_t post_bytes = chunked ? INT64_MAX : t_state.hdr_info.request_content_length; + + // Note: Many browsers, Netscape and IE included send two extra + // bytes (CRLF) at the end of the post. We just ignore those + // bytes since the sending them is not spec + + // Next order of business if copy the remaining data from the + // header buffer into new buffer + client_request_body_bytes = post_buffer->write(ua_buffer_reader, chunked ? ua_buffer_reader->read_avail() : post_bytes); + + ua_buffer_reader->consume(client_request_body_bytes); + p = tunnel.add_producer(ua_entry->vc, post_bytes - transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua, HT_HTTP_CLIENT, + "user agent post"); + } + ua_entry->in_tunnel = true; + + switch (to_vc_type) { + case HTTP_TRANSFORM_VC: + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_request_wait_for_transform_read); + ink_assert(post_transform_info.entry != NULL); + ink_assert(post_transform_info.entry->vc == post_transform_info.vc); + tunnel.add_consumer(post_transform_info.entry->vc, ua_entry->vc, &HttpSM::tunnel_handler_transform_write, HT_TRANSFORM, + "post transform"); + post_transform_info.entry->in_tunnel = true; + break; + case HTTP_SERVER_VC: + // YTS Team, yamsat Plugin + // When redirect in process is true and redirection is enabled + // add http server as the consumer + if (post_redirect) { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_for_partial_post); + tunnel.add_consumer(server_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &HttpSM::tunnel_handler_post_server, HT_HTTP_SERVER, + "redirect http server post"); + } else { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_post); + tunnel.add_consumer(server_entry->vc, ua_entry->vc, &HttpSM::tunnel_handler_post_server, HT_HTTP_SERVER, "http server post"); + } + server_entry->in_tunnel = true; + break; + default: + ink_release_assert(0); + break; + } + + if (chunked) + tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT); + + ua_session->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in)); + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_out)); + + tunnel.tunnel_run(p); + + // If we're half closed, we got a FIN from the client. Forward it on to the origin server + // now that we have the tunnel operational. + if (ua_session->get_half_close_flag()) { + p->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } +} + +// void HttpSM::perform_transform_cache_write_action() +// +// Called to do cache write from the transform +// +void +HttpSM::perform_transform_cache_write_action() +{ + DebugSM("http", "[%" PRId64 "] perform_transform_cache_write_action %s", sm_id, + HttpDebugNames::get_cache_action_name(t_state.cache_info.action)); + + if (t_state.range_setup) + return; + + switch (t_state.cache_info.transform_action) { + case HttpTransact::CACHE_DO_NO_ACTION: { + // Nothing to do + transform_cache_sm.end_both(); + break; + } + + case HttpTransact::CACHE_DO_WRITE: { + transform_cache_sm.close_read(); + t_state.cache_info.transform_write_status = HttpTransact::CACHE_WRITE_IN_PROGRESS; + setup_cache_write_transfer(&transform_cache_sm, transform_info.entry->vc, &t_state.cache_info.transform_store, + client_response_hdr_bytes, "cache write t"); + break; + } + + default: + ink_release_assert(0); + break; + } +} + +// void HttpSM::perform_cache_write_action() +// +// Called to do cache write, delete and updates based +// on s->cache_info.action. Does not setup cache +// read tunnels +// +void +HttpSM::perform_cache_write_action() +{ + DebugSM("http", "[%" PRId64 "] perform_cache_write_action %s", sm_id, + HttpDebugNames::get_cache_action_name(t_state.cache_info.action)); + + switch (t_state.cache_info.action) { + case HttpTransact::CACHE_DO_NO_ACTION: + + { + // Nothing to do + cache_sm.end_both(); + break; + } + + case HttpTransact::CACHE_DO_SERVE: { + cache_sm.abort_write(); + break; + } + + case HttpTransact::CACHE_DO_DELETE: { + // Write close deletes the old alternate + cache_sm.close_write(); + cache_sm.close_read(); + break; + } + + case HttpTransact::CACHE_DO_SERVE_AND_DELETE: { + // FIX ME: need to set up delete for after cache write has + // completed + break; + } + + case HttpTransact::CACHE_DO_SERVE_AND_UPDATE: { + issue_cache_update(); + break; + } + + case HttpTransact::CACHE_DO_UPDATE: { + cache_sm.close_read(); + issue_cache_update(); + break; + } + + case HttpTransact::CACHE_DO_WRITE: + case HttpTransact::CACHE_DO_REPLACE: + // Fix need to set up delete for after cache write has + // completed + if (transform_info.entry == NULL || t_state.api_info.cache_untransformed == true) { + cache_sm.close_read(); + t_state.cache_info.write_status = HttpTransact::CACHE_WRITE_IN_PROGRESS; + setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, client_response_hdr_bytes, + "cache write"); + } else { + // We are not caching the untransformed. We might want to + // use the cache writevc to cache the transformed copy + ink_assert(transform_cache_sm.cache_write_vc == NULL); + transform_cache_sm.cache_write_vc = cache_sm.cache_write_vc; + cache_sm.cache_write_vc = NULL; + } + break; + + default: + ink_release_assert(0); + break; + } +} + +void +HttpSM::issue_cache_update() +{ + ink_assert(cache_sm.cache_write_vc != NULL); + if (cache_sm.cache_write_vc) { + t_state.cache_info.object_store.request_sent_time_set(t_state.request_sent_time); + t_state.cache_info.object_store.response_received_time_set(t_state.response_received_time); + ink_assert(t_state.cache_info.object_store.request_sent_time_get() > 0); + ink_assert(t_state.cache_info.object_store.response_received_time_get() > 0); + cache_sm.cache_write_vc->set_http_info(&t_state.cache_info.object_store); + t_state.cache_info.object_store.clear(); + } + // Now close the write which commits the update + cache_sm.close_write(); +} + +int +HttpSM::write_header_into_buffer(HTTPHdr *h, MIOBuffer *b) +{ + int bufindex; + int dumpoffset; + int done, tmp; + IOBufferBlock *block; + + dumpoffset = 0; + do { + bufindex = 0; + tmp = dumpoffset; + block = b->get_current_block(); + ink_assert(block->write_avail() > 0); + done = h->print(block->start(), block->write_avail(), &bufindex, &tmp); + dumpoffset += bufindex; + ink_assert(bufindex > 0); + b->fill(bufindex); + if (!done) { + b->add_block(); + } + } while (!done); + + return dumpoffset; +} + +void +HttpSM::attach_server_session(HttpServerSession *s) +{ + hsm_release_assert(server_session == NULL); + hsm_release_assert(server_entry == NULL); + hsm_release_assert(s->state == HSS_ACTIVE); + server_session = s; + server_transact_count = server_session->transact_count++; + // Propagate the per client IP debugging + if (ua_session) + s->get_netvc()->control_flags = get_cont_flags(); + else { // If there is no ua_session no sense in continuing to attach the server session + return; + } + + if (ua_session) { + NetVConnection *server_vc = s->get_netvc(); + NetVConnection *ua_vc = ua_session->get_netvc(); + ink_release_assert(server_vc->thread == ua_vc->thread); + } + + // Set the mutex so that we have something to update + // stats with + server_session->mutex = this->mutex; + + HTTP_INCREMENT_DYN_STAT(http_current_server_transactions_stat); + ++s->server_trans_stat; + + // Record the VC in our table + server_entry = vc_table.new_entry(); + server_entry->vc = server_session; + server_entry->vc_type = HTTP_SERVER_VC; + server_entry->vc_handler = &HttpSM::state_send_server_request_header; + + // es - is this a concern here in HttpSM? Does it belong somewhere else? + // Get server and client connections + UnixNetVConnection *server_vc = dynamic_cast(server_session->get_netvc()); + UnixNetVConnection *client_vc = (UnixNetVConnection *)(ua_session->get_netvc()); + SSLNetVConnection *ssl_vc = dynamic_cast(client_vc); + bool associated_connection = false; + if (server_vc) { // if server_vc isn't a PluginVC + if (ssl_vc) { // if incoming connection is SSL + bool client_trace = ssl_vc->getSSLTrace(); + if (client_trace) { + // get remote address and port to mark corresponding traces + const sockaddr *remote_addr = ssl_vc->get_remote_addr(); + uint16_t remote_port = ssl_vc->get_remote_port(); + server_vc->setOriginTrace(true); + server_vc->setOriginTraceAddr(remote_addr); + server_vc->setOriginTracePort(remote_port); + associated_connection = true; + } + } + } + if (!associated_connection && server_vc) { + server_vc->setOriginTrace(false); + server_vc->setOriginTraceAddr(NULL); + server_vc->setOriginTracePort(0); + } + + // set flag for server session is SSL + SSLNetVConnection *server_ssl_vc = dynamic_cast(server_vc); + if (server_ssl_vc) { + server_connection_is_ssl = true; + } + + // Initiate a read on the session so that the SM and not + // session manager will get called back if the timeout occurs + // or the server closes on us. The IO Core now requires us to + // do the read with a buffer and a size so preallocate the + // buffer + server_buffer_reader = server_session->get_reader(); + // ts-3189 We are only setting up an empty read at this point. This + // is suffient to have the timeout errors directed to the appropriate + // SM handler, but we don't want to read any data until the tunnel has + // been set up. This isn't such a big deal with GET results, since + // if no tunnels are set up, there is no danger of data being delivered + // to the wrong tunnel's consumer handler. But for post and other + // methods that send data after the request, two tunnels are created in + // series, and with a full read set up at this point, the EOS from the + // first tunnel was sometimes behind handled by the consumer of the + // first tunnel instead of the producer of the second tunnel. + // The real read is setup in setup_server_read_response_header() + // + server_entry->read_vio = server_session->do_io_read(this, 0, server_session->read_buffer); + + // Transfer control of the write side as well + server_session->do_io_write(this, 0, NULL); + + // Setup the timeouts + // Set the inactivity timeout to the connect timeout so that we + // we fail this server if it doesn't start sending the response + // header + MgmtInt connect_timeout; + + if (t_state.method == HTTP_WKSIDX_POST || t_state.method == HTTP_WKSIDX_PUT) { + connect_timeout = t_state.txn_conf->post_connect_attempts_timeout; + } else if (t_state.current.server == &t_state.parent_info) { + connect_timeout = t_state.http_config_param->parent_connect_timeout; + } else { + connect_timeout = t_state.txn_conf->connect_attempts_timeout; + } + if (t_state.pCongestionEntry != NULL) + connect_timeout = t_state.pCongestionEntry->connect_timeout(); + + if (t_state.api_txn_connect_timeout_value != -1) { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_MSECONDS(t_state.api_txn_connect_timeout_value)); + } else { + server_session->get_netvc()->set_inactivity_timeout(HRTIME_SECONDS(connect_timeout)); + } + + if (t_state.api_txn_active_timeout_value != -1) { + server_session->get_netvc()->set_active_timeout(HRTIME_MSECONDS(t_state.api_txn_active_timeout_value)); + } else { + server_session->get_netvc()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_out)); + } + + if (plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL || will_be_private_ss) { + DebugSM("http_ss", "Setting server session to private"); + set_server_session_private(true); + } +} + +void +HttpSM::setup_server_send_request_api() +{ + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR; + do_api_callout(); +} + +void +HttpSM::setup_server_send_request() +{ + int hdr_length; + int64_t msg_len = 0; /* lv: just make gcc happy */ + + hsm_release_assert(server_entry != NULL); + hsm_release_assert(server_session != NULL); + hsm_release_assert(server_entry->vc == server_session); + + // Send the request header + server_entry->vc_handler = &HttpSM::state_send_server_request_header; + server_entry->write_buffer = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); + + if (t_state.api_server_request_body_set) { + msg_len = t_state.internal_msg_buffer_size; + t_state.hdr_info.server_request.value_set_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH, msg_len); + } + + if (!t_state.cop_test_page) { + DUMP_HEADER("http_hdrs", &(t_state.hdr_info.server_request), t_state.state_machine_id, "Proxy's Request after hooks"); + } + + // We need a reader so bytes don't fall off the end of + // the buffer + IOBufferReader *buf_start = server_entry->write_buffer->alloc_reader(); + server_request_hdr_bytes = hdr_length = write_header_into_buffer(&t_state.hdr_info.server_request, server_entry->write_buffer); + + // the plugin decided to append a message to the request + if (t_state.api_server_request_body_set) { + DebugSM("http", "[%" PRId64 "] appending msg of %" PRId64 " bytes to request %s", sm_id, msg_len, t_state.internal_msg_buffer); + hdr_length += server_entry->write_buffer->write(t_state.internal_msg_buffer, msg_len); + server_request_body_bytes = msg_len; + } + + milestones[TS_MILESTONE_SERVER_BEGIN_WRITE] = Thread::get_hrtime(); + server_entry->write_vio = server_entry->vc->do_io_write(this, hdr_length, buf_start); +} + +void +HttpSM::setup_server_read_response_header() +{ + ink_assert(server_session != NULL); + ink_assert(server_entry != NULL); + // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed in REQ_FLAVOR_REVPROXY + ink_assert(ua_session != NULL || t_state.req_flavor == HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE || + t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY); + + // We should have set the server_buffer_reader + // we sent the request header + ink_assert(server_buffer_reader != NULL); + + // Now that we've got the ability to read from the + // server, setup to read the response header + server_entry->vc_handler = &HttpSM::state_read_server_response_header; + + t_state.current.state = HttpTransact::STATE_UNDEFINED; + t_state.current.server->state = HttpTransact::STATE_UNDEFINED; + + // Note: we must use destroy() here since clear() + // does not free the memory from the header + t_state.hdr_info.server_response.destroy(); + t_state.hdr_info.server_response.create(HTTP_TYPE_RESPONSE); + http_parser_clear(&http_parser); + server_response_hdr_bytes = 0; + milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = 0; + + // We already done the READ when we setup the connection to + // read the request header + ink_assert(server_entry->read_vio); + + // The tunnel from OS to UA is now setup. Ready to read the response + server_entry->read_vio = server_session->do_io_read(this, INT64_MAX, server_buffer_reader->mbuf); + + // If there is anything in the buffer call the parsing routines + // since if the response is finished, we won't get any + // additional callbacks + + if (server_buffer_reader->read_avail() > 0) { + state_read_server_response_header((server_entry->eos) ? VC_EVENT_EOS : VC_EVENT_READ_READY, server_entry->read_vio); + } +} + +HttpTunnelProducer * +HttpSM::setup_cache_read_transfer() +{ + int64_t alloc_index, hdr_size; + int64_t doc_size; + + ink_assert(cache_sm.cache_read_vc != NULL); + + doc_size = t_state.cache_info.object_read->object_size_get(); + alloc_index = buffer_size_to_index(doc_size + index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX)); + +#ifndef USE_NEW_EMPTY_MIOBUFFER + MIOBuffer *buf = new_MIOBuffer(alloc_index); +#else + MIOBuffer *buf = new_empty_MIOBuffer(alloc_index); + buf->append_block(HTTP_HEADER_BUFFER_SIZE_INDEX); +#endif + + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + + IOBufferReader *buf_start = buf->alloc_reader(); + + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = hdr_size = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + cache_response_hdr_bytes = client_response_hdr_bytes; + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + if (doc_size != INT64_MAX) + doc_size += hdr_size; + + HttpTunnelProducer *p = tunnel.add_producer(cache_sm.cache_read_vc, doc_size, buf_start, &HttpSM::tunnel_handler_cache_read, + HT_CACHE_READ, "cache read"); + tunnel.add_consumer(ua_entry->vc, cache_sm.cache_read_vc, &HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + // if size of a cached item is not known, we'll do chunking for keep-alive HTTP/1.1 clients + // this only applies to read-while-write cases where origin server sends a dynamically generated chunked content + // w/o providing a Content-Length header + if (t_state.client_info.receive_chunked_response) { + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + } + ua_entry->in_tunnel = true; + cache_sm.cache_read_vc = NULL; + return p; +} + +HttpTunnelProducer * +HttpSM::setup_cache_transfer_to_transform() +{ + int64_t alloc_index; + int64_t doc_size; + + ink_assert(cache_sm.cache_read_vc != NULL); + ink_assert(transform_info.vc != NULL); + ink_assert(transform_info.entry->vc == transform_info.vc); + + // grab this here + cache_response_hdr_bytes = t_state.hdr_info.cache_response.length_get(); + + doc_size = t_state.cache_info.object_read->object_size_get(); + alloc_index = buffer_size_to_index(doc_size); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_response_wait_for_transform_read); + + HttpTunnelProducer *p = tunnel.add_producer(cache_sm.cache_read_vc, doc_size, buf_start, &HttpSM::tunnel_handler_cache_read, + HT_CACHE_READ, "cache read"); + + tunnel.add_consumer(transform_info.vc, cache_sm.cache_read_vc, &HttpSM::tunnel_handler_transform_write, HT_TRANSFORM, + "transform write"); + transform_info.entry->in_tunnel = true; + cache_sm.cache_read_vc = NULL; + + return p; +} + +void +HttpSM::setup_cache_write_transfer(HttpCacheSM *c_sm, VConnection *source_vc, HTTPInfo *store_info, int64_t skip_bytes, + const char *name) +{ + ink_assert(c_sm->cache_write_vc != NULL); + ink_assert(t_state.request_sent_time > 0); + ink_assert(t_state.response_received_time > 0); + + store_info->request_sent_time_set(t_state.request_sent_time); + store_info->response_received_time_set(t_state.response_received_time); + + c_sm->cache_write_vc->set_http_info(store_info); + store_info->clear(); + + tunnel.add_consumer(c_sm->cache_write_vc, source_vc, &HttpSM::tunnel_handler_cache_write, HT_CACHE_WRITE, name, skip_bytes); + + c_sm->cache_write_vc = NULL; +} + +void +HttpSM::setup_100_continue_transfer() +{ + MIOBuffer *buf = new_MIOBuffer(HTTP_HEADER_BUFFER_SIZE_INDEX); + IOBufferReader *buf_start = buf->alloc_reader(); + + // First write the client response header into the buffer + ink_assert(t_state.client_info.http_version != HTTPVersion(0, 9)); + client_response_hdr_bytes = write_header_into_buffer(&t_state.hdr_info.client_response, buf); + ink_assert(client_response_hdr_bytes > 0); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_100_continue); + + // Setup the tunnel to the client + HttpTunnelProducer *p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, client_response_hdr_bytes, buf_start, + (HttpProducerHandler)NULL, HT_STATIC, "internal msg - 100 continue"); + tunnel.add_consumer(ua_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &HttpSM::tunnel_handler_100_continue_ua, HT_HTTP_CLIENT, + "user agent"); + + // Make sure the half_close is not set. + ua_session->set_half_close_flag(false); + ua_entry->in_tunnel = true; + tunnel.tunnel_run(p); +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::setup_error_transfer() +// +// The proxy has generated an error message which it +// is sending to the client. For some cases, however, +// such as when the proxy is transparent, returning +// a proxy-generated error message exposes the proxy, +// destroying transparency. The HttpBodyFactory code, +// therefore, does not generate an error message body +// in such cases. This function checks for the presence +// of an error body. If its not present, it closes the +// connection to the user, else it simply calls +// setup_write_proxy_internal, which is the standard +// routine for setting up proxy-generated responses. +// +////////////////////////////////////////////////////////////////////////// +void +HttpSM::setup_error_transfer() +{ + if (t_state.internal_msg_buffer) { + // Since we need to send the error message, call the API + // function + ink_assert(t_state.internal_msg_buffer_size > 0); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + } else { + DebugSM("http", "[setup_error_transfer] Now closing connection ..."); + vc_table.cleanup_entry(ua_entry); + ua_entry = NULL; + ua_session = NULL; + terminate_sm = true; + t_state.source = HttpTransact::SOURCE_INTERNAL; + } +} + +void +HttpSM::setup_internal_transfer(HttpSMHandler handler_arg) +{ + bool is_msg_buf_present; + + if (t_state.internal_msg_buffer) { + is_msg_buf_present = true; + ink_assert(t_state.internal_msg_buffer_size > 0); + + // Set the content length here since a plugin + // may have changed the error body + t_state.hdr_info.client_response.set_content_length(t_state.internal_msg_buffer_size); + t_state.hdr_info.client_response.field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + + // set internal_msg_buffer_type if available + if (t_state.internal_msg_buffer_type) { + int len = strlen(t_state.internal_msg_buffer_type); + + if (len > 0) { + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, t_state.internal_msg_buffer_type, + len); + } + ats_free(t_state.internal_msg_buffer_type); + t_state.internal_msg_buffer_type = NULL; + } else { + t_state.hdr_info.client_response.value_set(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, "text/html", 9); + } + } else { + is_msg_buf_present = false; + + // If we are sending a response that can have a body + // but doesn't have a body add a content-length of zero. + // Needed for keep-alive on PURGE requests + if (!is_response_body_precluded(t_state.hdr_info.client_response.status_get(), t_state.method)) { + t_state.hdr_info.client_response.set_content_length(0); + t_state.hdr_info.client_response.field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + } + } + + t_state.source = HttpTransact::SOURCE_INTERNAL; + + int64_t buf_size = + index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX) + (is_msg_buf_present ? t_state.internal_msg_buffer_size : 0); + + MIOBuffer *buf = new_MIOBuffer(buffer_size_to_index(buf_size)); + IOBufferReader *buf_start = buf->alloc_reader(); + + // First write the client response header into the buffer + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + int64_t nbytes = client_response_hdr_bytes; + + // Next append the message onto the MIOBuffer + + // From HTTP/1.1 RFC: + // "The HEAD method is identical to GET except that the server + // MUST NOT return a message-body in the response. The metainformation + // in the HTTP headers in response to a HEAD request SHOULD be + // identical to the information sent in response to a GET request." + // --> do not append the message onto the MIOBuffer and keep our pointer + // to it so that it can be freed. + + if (is_msg_buf_present && t_state.method != HTTP_WKSIDX_HEAD) { + nbytes += t_state.internal_msg_buffer_size; + + if (t_state.internal_msg_buffer_fast_allocator_size < 0) + buf->append_xmalloced(t_state.internal_msg_buffer, t_state.internal_msg_buffer_size); + else + buf->append_fast_allocated(t_state.internal_msg_buffer, t_state.internal_msg_buffer_size, + t_state.internal_msg_buffer_fast_allocator_size); + + // The IOBufferBlock will free the msg buffer when necessary so + // eliminate our pointer to it + t_state.internal_msg_buffer = NULL; + t_state.internal_msg_buffer_size = 0; + } + + HTTP_SM_SET_DEFAULT_HANDLER(handler_arg); + + // Clear the decks before we setup the new producers + // As things stand, we cannot have two static producers operating at + // once + tunnel.kill_tunnel(); + + // Setup the tunnel to the client + HttpTunnelProducer *p = + tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, nbytes, buf_start, (HttpProducerHandler)NULL, HT_STATIC, "internal msg"); + tunnel.add_consumer(ua_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, &HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + ua_entry->in_tunnel = true; + tunnel.tunnel_run(p); +} + +// int HttpSM::find_http_resp_buffer_size(int cl) +// +// Returns the allocation index for the buffer for +// a response based on the content length +// +int +HttpSM::find_http_resp_buffer_size(int64_t content_length) +{ + int64_t buf_size; + int64_t alloc_index; + + if (content_length == HTTP_UNDEFINED_CL) { + // Try use our configured default size. Otherwise pick + // the default size + alloc_index = (int)t_state.txn_conf->default_buffer_size_index; + if (alloc_index < MIN_CONFIG_BUFFER_SIZE_INDEX || alloc_index > DEFAULT_MAX_BUFFER_SIZE) { + alloc_index = DEFAULT_RESPONSE_BUFFER_SIZE_INDEX; + } + } else { +#ifdef WRITE_AND_TRANSFER + buf_size = HTTP_HEADER_BUFFER_SIZE + content_length - index_to_buffer_size(HTTP_SERVER_RESP_HDR_BUFFER_INDEX); +#else + buf_size = index_to_buffer_size(HTTP_HEADER_BUFFER_SIZE_INDEX) + content_length; +#endif + alloc_index = buffer_size_to_index(buf_size); + } + + return alloc_index; +} + +// int HttpSM::server_transfer_init() +// +// Moves data from the header buffer into the reply buffer +// and return the number of bytes we should use for initiating the +// tunnel +// +int64_t +HttpSM::server_transfer_init(MIOBuffer *buf, int hdr_size) +{ + int64_t nbytes; + int64_t to_copy = INT64_MAX; + + ink_assert(t_state.current.server != NULL); // should have been set up if we're doing a transfer. + + if (server_entry->eos == true) { + // The server has shutdown on us already so the only data + // we'll get is already in the buffer + nbytes = server_buffer_reader->read_avail() + hdr_size; + } else if (t_state.hdr_info.response_content_length == HTTP_UNDEFINED_CL) { + nbytes = -1; + } else { + // Set to copy to the number of bytes we want to write as + // if the server is sending us a bogus response we have to + // truncate it as we've already decided to trust the content + // length + to_copy = t_state.hdr_info.response_content_length; + nbytes = t_state.hdr_info.response_content_length + hdr_size; + } + + // Next order of business if copy the remaining data from the + // header buffer into new buffer. + + int64_t server_response_pre_read_bytes = +#ifdef WRITE_AND_TRANSFER + /* relinquish the space in server_buffer and let + the tunnel use the trailing space + */ + buf->write_and_transfer_left_over_space(server_buffer_reader, to_copy); +#else + buf->write(server_buffer_reader, to_copy); +#endif + server_buffer_reader->consume(server_response_pre_read_bytes); + + // If we know the length & copied the entire body + // of the document out of the header buffer make + // sure the server isn't screwing us by having sent too + // much. If it did, we want to close the server connection + if (server_response_pre_read_bytes == to_copy && server_buffer_reader->read_avail() > 0) { + t_state.current.server->keep_alive = HTTP_NO_KEEPALIVE; + } +#ifdef LAZY_BUF_ALLOC + // reset the server session buffer + server_session->reset_read_buffer(); +#endif + return nbytes; +} + +HttpTunnelProducer * +HttpSM::setup_server_transfer_to_transform() +{ + int64_t alloc_index; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + nbytes = server_transfer_init(buf, 0); + + if (t_state.negative_caching && t_state.hdr_info.server_response.status_get() == HTTP_STATUS_NO_CONTENT) { + int s = sizeof("No Content") - 1; + buf->write("No Content", s); + nbytes += s; + } + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_response_wait_for_transform_read); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.add_consumer(transform_info.vc, server_entry->vc, &HttpSM::tunnel_handler_transform_write, HT_TRANSFORM, + "transform write"); + + server_entry->in_tunnel = true; + transform_info.entry->in_tunnel = true; + + if (t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) { + client_response_hdr_bytes = 0; // fixed by YTS Team, yamsat + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_DECHUNK_CONTENT); + } + + return p; +} + +HttpTunnelProducer * +HttpSM::setup_transfer_from_transform() +{ + int64_t alloc_index = find_server_buffer_size(); + + // TODO change this call to new_empty_MIOBuffer() + MIOBuffer *buf = new_MIOBuffer(alloc_index); + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + IOBufferReader *buf_start = buf->alloc_reader(); + + HttpTunnelConsumer *c = tunnel.get_consumer(transform_info.vc); + ink_assert(c != NULL); + ink_assert(c->vc == transform_info.vc); + ink_assert(c->vc_type == HT_TRANSFORM); + + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + HttpTunnelProducer *p = tunnel.add_producer(transform_info.vc, INT64_MAX, buf_start, &HttpSM::tunnel_handler_transform_read, + HT_TRANSFORM, "transform read"); + tunnel.chain(c, p); + + tunnel.add_consumer(ua_entry->vc, transform_info.vc, &HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + transform_info.entry->in_tunnel = true; + ua_entry->in_tunnel = true; + + this->setup_plugin_agents(p); + + if (t_state.client_info.receive_chunked_response) { + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, TCA_CHUNK_CONTENT); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + } + + return p; +} + +HttpTunnelProducer * +HttpSM::setup_transfer_from_transform_to_cache_only() +{ + int64_t alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + HttpTunnelConsumer *c = tunnel.get_consumer(transform_info.vc); + ink_assert(c != NULL); + ink_assert(c->vc == transform_info.vc); + ink_assert(c->vc_type == HT_TRANSFORM); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + HttpTunnelProducer *p = tunnel.add_producer(transform_info.vc, INT64_MAX, buf_start, &HttpSM::tunnel_handler_transform_read, + HT_TRANSFORM, "transform read"); + tunnel.chain(c, p); + + transform_info.entry->in_tunnel = true; + + ink_assert(t_state.cache_info.transform_action == HttpTransact::CACHE_DO_WRITE); + + perform_transform_cache_write_action(); + + return p; +} + +void +HttpSM::setup_server_transfer_to_cache_only() +{ + TunnelChunkingAction_t action; + int64_t alloc_index; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + action = (t_state.current.server && t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) ? + TCA_DECHUNK_CONTENT : + TCA_PASSTHRU_DECHUNKED_CONTENT; + + nbytes = server_transfer_init(buf, 0); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.set_producer_chunking_action(p, 0, action); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + + setup_cache_write_transfer(&cache_sm, server_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); + + server_entry->in_tunnel = true; +} + +HttpTunnelProducer * +HttpSM::setup_server_transfer() +{ + DebugSM("http", "Setup Server Transfer"); + int64_t alloc_index, hdr_size; + int64_t nbytes; + + alloc_index = find_server_buffer_size(); +#ifndef USE_NEW_EMPTY_MIOBUFFER + MIOBuffer *buf = new_MIOBuffer(alloc_index); +#else + MIOBuffer *buf = new_empty_MIOBuffer(alloc_index); + buf->append_block(HTTP_HEADER_BUFFER_SIZE_INDEX); +#endif + buf->water_mark = (int)t_state.txn_conf->default_buffer_water_mark; + IOBufferReader *buf_start = buf->alloc_reader(); + + // we need to know if we are going to chunk the response or not + // before we write the response header into buffer + TunnelChunkingAction_t action; + if (t_state.client_info.receive_chunked_response == false) { + if (t_state.current.server->transfer_encoding == HttpTransact::CHUNKED_ENCODING) + action = TCA_DECHUNK_CONTENT; + else + action = TCA_PASSTHRU_DECHUNKED_CONTENT; + } else { + if (t_state.current.server->transfer_encoding != HttpTransact::CHUNKED_ENCODING) + if (t_state.client_info.http_version == HTTPVersion(0, 9)) + action = TCA_PASSTHRU_DECHUNKED_CONTENT; // send as-is + else + action = TCA_CHUNK_CONTENT; + else + action = TCA_PASSTHRU_CHUNKED_CONTENT; + } + if (action == TCA_CHUNK_CONTENT || action == TCA_PASSTHRU_CHUNKED_CONTENT) { // remove Content-Length + t_state.hdr_info.client_response.field_delete(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH); + } + // Now dump the header into the buffer + ink_assert(t_state.hdr_info.client_response.status_get() != HTTP_STATUS_NOT_MODIFIED); + client_response_hdr_bytes = hdr_size = write_response_header_into_buffer(&t_state.hdr_info.client_response, buf); + + nbytes = server_transfer_init(buf, hdr_size); + + if (t_state.negative_caching && t_state.hdr_info.server_response.status_get() == HTTP_STATUS_NO_CONTENT) { + int s = sizeof("No Content") - 1; + buf->write("No Content", s); + nbytes += s; + } + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + HttpTunnelProducer *p = + tunnel.add_producer(server_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_server, HT_HTTP_SERVER, "http server"); + + tunnel.add_consumer(ua_entry->vc, server_entry->vc, &HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent"); + + ua_entry->in_tunnel = true; + server_entry->in_tunnel = true; + + this->setup_plugin_agents(p); + + // If the incoming server response is chunked and the client does not + // expect a chunked response, then dechunk it. Otherwise, if the + // incoming response is not chunked and the client expects a chunked + // response, then chunk it. + /* + // this block is moved up so that we know if we need to remove + // Content-Length field from response header before writing the + // response header into buffer bz50730 + TunnelChunkingAction_t action; + if (t_state.client_info.receive_chunked_response == false) { + if (t_state.current.server->transfer_encoding == + HttpTransact::CHUNKED_ENCODING) + action = TCA_DECHUNK_CONTENT; + else action = TCA_PASSTHRU_DECHUNKED_CONTENT; + } + else { + if (t_state.current.server->transfer_encoding != + HttpTransact::CHUNKED_ENCODING) + action = TCA_CHUNK_CONTENT; + else action = TCA_PASSTHRU_CHUNKED_CONTENT; + } + */ + tunnel.set_producer_chunking_action(p, client_response_hdr_bytes, action); + tunnel.set_producer_chunking_size(p, t_state.txn_conf->http_chunking_size); + return p; +} + +HttpTunnelProducer * +HttpSM::setup_push_transfer_to_cache() +{ + int64_t nbytes, alloc_index; + + alloc_index = find_http_resp_buffer_size(t_state.hdr_info.request_content_length); + MIOBuffer *buf = new_MIOBuffer(alloc_index); + IOBufferReader *buf_start = buf->alloc_reader(); + + ink_release_assert(t_state.hdr_info.request_content_length != HTTP_UNDEFINED_CL); + nbytes = t_state.hdr_info.request_content_length - pushed_response_hdr_bytes; + ink_release_assert(nbytes >= 0); + + if (ua_entry->eos == true) { + // The ua has shutdown on us already so the only data + // we'll get is already in the buffer. Make sure it + // fulfills the stated length + int64_t avail = ua_buffer_reader->read_avail(); + + if (avail < nbytes) { + // Client failed to send the body, it's gone. Kill the + // state machine + terminate_sm = true; + return NULL; + } + } + // Next order of business is copy the remaining data from the + // header buffer into new buffer. + pushed_response_body_bytes = buf->write(ua_buffer_reader, nbytes); + ua_buffer_reader->consume(pushed_response_body_bytes); + client_request_body_bytes += pushed_response_body_bytes; + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler_push); + + HttpTunnelProducer *p = + tunnel.add_producer(ua_entry->vc, nbytes, buf_start, &HttpSM::tunnel_handler_ua_push, HT_HTTP_CLIENT, "user_agent"); + setup_cache_write_transfer(&cache_sm, ua_entry->vc, &t_state.cache_info.object_store, 0, "cache write"); + + ua_entry->in_tunnel = true; + return p; +} + +void +HttpSM::setup_blind_tunnel(bool send_response_hdr) +{ + HttpTunnelConsumer *c_ua; + HttpTunnelConsumer *c_os; + HttpTunnelProducer *p_ua; + HttpTunnelProducer *p_os; + MIOBuffer *from_ua_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); + MIOBuffer *to_ua_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); + IOBufferReader *r_from = from_ua_buf->alloc_reader(); + IOBufferReader *r_to = to_ua_buf->alloc_reader(); + + milestones[TS_MILESTONE_SERVER_BEGIN_WRITE] = Thread::get_hrtime(); + if (send_response_hdr) { + client_response_hdr_bytes = write_response_header_into_buffer(&t_state.hdr_info.client_response, to_ua_buf); + } else { + client_response_hdr_bytes = 0; + } + + client_request_body_bytes = 0; + if (ua_raw_buffer_reader != NULL) { + client_request_body_bytes += from_ua_buf->write(ua_raw_buffer_reader, client_request_hdr_bytes); + ua_raw_buffer_reader->dealloc(); + ua_raw_buffer_reader = NULL; + } + + // Next order of business if copy the remaining data from the + // header buffer into new buffer + client_request_body_bytes += from_ua_buf->write(ua_buffer_reader); + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::tunnel_handler); + + p_os = + tunnel.add_producer(server_entry->vc, -1, r_to, &HttpSM::tunnel_handler_ssl_producer, HT_HTTP_SERVER, "http server - tunnel"); + + c_ua = tunnel.add_consumer(ua_entry->vc, server_entry->vc, &HttpSM::tunnel_handler_ssl_consumer, HT_HTTP_CLIENT, + "user agent - tunnel"); + + p_ua = tunnel.add_producer(ua_entry->vc, -1, r_from, &HttpSM::tunnel_handler_ssl_producer, HT_HTTP_CLIENT, "user agent - tunnel"); + + c_os = tunnel.add_consumer(server_entry->vc, ua_entry->vc, &HttpSM::tunnel_handler_ssl_consumer, HT_HTTP_SERVER, + "http server - tunnel"); + + // Make the tunnel aware that the entries are bi-directional + tunnel.chain(c_os, p_os); + tunnel.chain(c_ua, p_ua); + + ua_entry->in_tunnel = true; + server_entry->in_tunnel = true; + + tunnel.tunnel_run(); + + // If we're half closed, we got a FIN from the client. Forward it on to the origin server + // now that we have the tunnel operational. + if (ua_session->get_half_close_flag()) { + p_ua->vc->do_io_shutdown(IO_SHUTDOWN_READ); + } +} + +void +HttpSM::setup_plugin_agents(HttpTunnelProducer *p) +{ + APIHook *agent = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK); + has_active_plugin_agents = agent != 0; + while (agent) { + INKVConnInternal *contp = static_cast(agent->m_cont); + tunnel.add_consumer(contp, p->vc, &HttpSM::tunnel_handler_plugin_agent, HT_HTTP_CLIENT, "plugin agent"); + // We don't put these in the SM VC table because the tunnel + // will clean them up in do_io_close(). + agent = agent->next(); + } +} + +inline void +HttpSM::transform_cleanup(TSHttpHookID hook, HttpTransformInfo *info) +{ + APIHook *t_hook = api_hooks.get(hook); + if (t_hook && info->vc == NULL) { + do { + VConnection *t_vcon = t_hook->m_cont; + t_vcon->do_io_close(); + t_hook = t_hook->m_link.next; + } while (t_hook != NULL); + } +} + +void +HttpSM::plugin_agents_cleanup() +{ + // If this is set then all of the plugin agent VCs were put in + // the VC table and cleaned up there. This handles the case where + // something went wrong early. + if (!has_active_plugin_agents) { + APIHook *agent = txn_hook_get(TS_HTTP_RESPONSE_CLIENT_HOOK); + while (agent) { + INKVConnInternal *contp = static_cast(agent->m_cont); + contp->do_io_close(); + agent = agent->next(); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::kill_this() +// +// This function has two phases. One before we call the asynchronous +// clean up routines (api and list removal) and one after. +// The state about which phase we are in is kept in +// HttpSM::kill_this_async_done +// +////////////////////////////////////////////////////////////////////////// +void +HttpSM::kill_this() +{ + ink_release_assert(reentrancy_count == 1); + tunnel.deallocate_redirect_postdata_buffers(); + enable_redirection = false; + + if (kill_this_async_done == false) { + //////////////////////////////// + // cancel uncompleted actions // + //////////////////////////////// + // The action should be cancelled only if + // the state machine is in HTTP_API_NO_CALLOUT + // state. This is because we are depending on the + // callout to complete for the state machine to + // get killed. + if (callout_state == HTTP_API_NO_CALLOUT && pending_action) { + pending_action->cancel(); + pending_action = NULL; + } + + cache_sm.end_both(); + if (second_cache_sm) + second_cache_sm->end_both(); + transform_cache_sm.end_both(); + vc_table.cleanup_all(); + + // tunnel.deallocate_buffers(); + // Why don't we just kill the tunnel? Might still be + // active if the state machine is going down hard, + // and we should clean it up. + tunnel.kill_tunnel(); + + // It possible that a plugin added transform hook + // but the hook never executed due to a client abort + // In that case, we need to manually close all the + // transforms to prevent memory leaks (INKqa06147) + if (hooks_set) { + transform_cleanup(TS_HTTP_RESPONSE_TRANSFORM_HOOK, &transform_info); + transform_cleanup(TS_HTTP_REQUEST_TRANSFORM_HOOK, &post_transform_info); + plugin_agents_cleanup(); + } + // It's also possible that the plugin_tunnel vc was never + // executed due to not contacting the server + if (plugin_tunnel) { + plugin_tunnel->kill_no_connect(); + plugin_tunnel = NULL; + } + + ua_session = NULL; + server_session = NULL; + + // So we don't try to nuke the state machine + // if the plugin receives event we must reset + // the terminate_flag + terminate_sm = false; + t_state.api_next_action = HttpTransact::SM_ACTION_API_SM_SHUTDOWN; + do_api_callout(); + } + // The reentrancy_count is still valid up to this point since + // the api shutdown hook is asynchronous and double frees can + // happen if the reentrancy count is not still valid until + // after all asynch callouts have completed + // + // Once we get to this point, we could be waiting for async + // completion in which case we need to decrement the reentrancy + // count since the entry points can't do it for us since they + // don't know if the state machine has been destroyed. In the + // case we really are done with asynch callouts, decrement the + // reentrancy count since it seems tacky to destruct a state + // machine with non-zero count + reentrancy_count--; + ink_release_assert(reentrancy_count == 0); + + // If the api shutdown & list removeal was synchronous + // then the value of kill_this_async_done has changed so + // we must check it again + if (kill_this_async_done == true) { + // In the async state, the plugin could have been + // called resulting in the creation of a plugin_tunnel. + // So it needs to be deleted now. + if (plugin_tunnel) { + plugin_tunnel->kill_no_connect(); + plugin_tunnel = NULL; + } + + if (t_state.pCongestionEntry != NULL) { + if (t_state.congestion_congested_or_failed != 1) { + t_state.pCongestionEntry->go_alive(); + } + } + + ink_assert(pending_action == NULL); + ink_release_assert(vc_table.is_table_clear() == true); + ink_release_assert(tunnel.is_tunnel_active() == false); + + HTTP_SM_SET_DEFAULT_HANDLER(NULL); + + if (t_state.http_config_param->enable_http_stats) + update_stats(); + + if (!t_state.cop_test_page || t_state.http_config_param->record_cop_page) { + ////////////// + // Log Data // + ////////////// + DebugSM("http_seq", "[HttpSM::update_stats] Logging transaction"); + if (Log::transaction_logging_enabled() && t_state.api_info.logging_enabled) { + LogAccessHttp accessor(this); + + int ret = Log::access(&accessor); + + if (ret & Log::FULL) { + DebugSM("http", "[update_stats] Logging system indicates FULL."); + } + if (ret & Log::FAIL) { + Log::error("failed to log transaction for at least one log object"); + } + } + } + + if (redirect_url != NULL) { + ats_free((void *)redirect_url); + redirect_url = NULL; + redirect_url_len = 0; + } + +#ifdef USE_HTTP_DEBUG_LISTS + ink_mutex_acquire(&debug_sm_list_mutex); + debug_sm_list.remove(this); + ink_mutex_release(&debug_sm_list_mutex); +#endif + + DebugSM("http", "[%" PRId64 "] deallocating sm", sm_id); + // authAdapter.destroyState(); + destroy(); + } +} + +void +HttpSM::update_stats() +{ + milestones[TS_MILESTONE_SM_FINISH] = Thread::get_hrtime(); + + if (t_state.cop_test_page && !t_state.http_config_param->record_cop_page) { + DebugSM("http_seq", "Skipping cop heartbeat logging & stats due to config"); + return; + } + + if (is_action_tag_set("bad_length_state_dump")) { + if (t_state.hdr_info.client_response.valid() && t_state.hdr_info.client_response.status_get() == HTTP_STATUS_OK) { + int64_t p_resp_cl = t_state.hdr_info.client_response.get_content_length(); + int64_t resp_size = client_response_body_bytes; + if (!((p_resp_cl == -1 || p_resp_cl == resp_size || resp_size == 0))) { + Error("[%" PRId64 "] Truncated content detected", sm_id); + dump_state_on_assert(); + } + } else if (client_request_hdr_bytes == 0) { + Error("[%" PRId64 "] Zero length request header received", sm_id); + dump_state_on_assert(); + } + } + + if (is_action_tag_set("assert_jtest_length")) { + if (t_state.hdr_info.client_response.valid() && t_state.hdr_info.client_response.status_get() == HTTP_STATUS_OK) { + int64_t p_resp_cl = t_state.hdr_info.client_response.get_content_length(); + int64_t resp_size = client_response_body_bytes; + ink_release_assert(p_resp_cl == -1 || p_resp_cl == resp_size || resp_size == 0); + } + } + + ink_hrtime total_time = milestones.elapsed(TS_MILESTONE_SM_START, TS_MILESTONE_SM_FINISH); + + // ua_close will not be assigned properly in some exceptional situation. + // TODO: Assign ua_close with suitable value when HttpTunnel terminates abnormally. + if (milestones[TS_MILESTONE_UA_CLOSE] == 0 && milestones[TS_MILESTONE_UA_READ_HEADER_DONE] > 0) + milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime(); + + // request_process_time = The time after the header is parsed to the completion of the transaction + ink_hrtime request_process_time = milestones[TS_MILESTONE_UA_CLOSE] - milestones[TS_MILESTONE_UA_READ_HEADER_DONE]; + + HttpTransact::client_result_stat(&t_state, total_time, request_process_time); + + ink_hrtime ua_write_time; + if (milestones[TS_MILESTONE_UA_BEGIN_WRITE] != 0 && milestones[TS_MILESTONE_UA_CLOSE] != 0) { + ua_write_time = milestones.elapsed(TS_MILESTONE_UA_BEGIN_WRITE, TS_MILESTONE_UA_CLOSE); + } else { + ua_write_time = -1; + } + + ink_hrtime os_read_time; + if (milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] != 0 && milestones[TS_MILESTONE_SERVER_CLOSE] != 0) { + os_read_time = milestones.elapsed(TS_MILESTONE_SERVER_READ_HEADER_DONE, TS_MILESTONE_SERVER_CLOSE); + } else { + os_read_time = -1; + } + + HttpTransact::update_size_and_time_stats( + &t_state, total_time, ua_write_time, os_read_time, client_request_hdr_bytes, client_request_body_bytes, + client_response_hdr_bytes, client_response_body_bytes, server_request_hdr_bytes, server_request_body_bytes, + server_response_hdr_bytes, server_response_body_bytes, pushed_response_hdr_bytes, pushed_response_body_bytes, milestones); + /* + if (is_action_tag_set("http_handler_times")) { + print_all_http_handler_times(); + } + */ + + // print slow requests if the threshold is set (> 0) and if we are over the time threshold + if (t_state.txn_conf->slow_log_threshold != 0 && ink_hrtime_from_msec(t_state.txn_conf->slow_log_threshold) < total_time) { + URL *url = t_state.hdr_info.client_request.url_get(); + char url_string[256] = ""; + int offset = 0; + int skip = 0; + + t_state.hdr_info.client_request.url_print(url_string, sizeof(url_string) - 1, &offset, &skip); + url_string[offset] = 0; // NULL terminate the string + + // unique id + char unique_id_string[128] = ""; + // [amc] why do we check the URL to get a MIME field? + if (0 != url && url->valid()) { + int length = 0; + const char *field = t_state.hdr_info.client_request.value_get(MIME_FIELD_X_ID, MIME_LEN_X_ID, &length); + if (field != NULL) { + ink_strlcpy(unique_id_string, field, sizeof(unique_id_string)); + } + } + + // set the fd for the request + int fd = 0; + NetVConnection *vc = NULL; + if (ua_session != NULL) { + vc = ua_session->get_netvc(); + if (vc != NULL) { + fd = vc->get_socket(); + } else { + fd = -1; + } + } + // get the status code, lame that we have to check to see if it is valid or we will assert in the method call + int status = 0; + if (t_state.hdr_info.client_response.valid()) { + status = t_state.hdr_info.client_response.status_get(); + } + char client_ip[INET6_ADDRSTRLEN]; + ats_ip_ntop(&t_state.client_info.src_addr, client_ip, sizeof(client_ip)); + Error("[%" PRId64 "] Slow Request: " + "client_ip: %s:%u " + "url: %s " + "status: %d " + "unique id: %s " + "redirection_tries: %d " + "bytes: %" PRId64 " " + "fd: %d " + "client state: %d " + "server state: %d " + "ua_begin: %.3f " + "ua_first_read: %.3f " + "ua_read_header_done: %.3f " + "cache_open_read_begin: %.3f " + "cache_open_read_end: %.3f " + "dns_lookup_begin: %.3f " + "dns_lookup_end: %.3f " + "server_connect: %.3f " + "server_first_read: %.3f " + "server_read_header_done: %.3f " + "server_close: %.3f " + "ua_close: %.3f " + "sm_finish: %.3f " + "plugin_active: %.3f " + "plugin_total: %.3f", + sm_id, client_ip, t_state.client_info.src_addr.host_order_port(), url_string, status, unique_id_string, redirection_tries, + client_response_body_bytes, fd, t_state.client_info.state, t_state.server_info.state, + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_FIRST_READ), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_READ_HEADER_DONE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_CACHE_OPEN_READ_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_CACHE_OPEN_READ_END), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_DNS_LOOKUP_BEGIN), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_DNS_LOOKUP_END), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_CONNECT), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_FIRST_READ), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_READ_HEADER_DONE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SERVER_CLOSE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_UA_CLOSE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_SM_FINISH), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_PLUGIN_ACTIVE), + milestones.difference_sec(TS_MILESTONE_SM_START, TS_MILESTONE_PLUGIN_TOTAL)); + } +} + +// +// void HttpSM::dump_state_on_assert +// Debugging routine to dump the state machine's history +// and other state on an assertion failure +// We use Diags::Status instead of stderr since +// Diags works both on UNIX & NT +// +void +HttpSM::dump_state_on_assert() +{ + Error("[%" PRId64 "] ------- begin http state dump -------", sm_id); + + int hist_size = this->history_pos; + if (this->history_pos > HISTORY_SIZE) { + hist_size = HISTORY_SIZE; + Error(" History Wrap around. history_pos: %d", this->history_pos); + } + // Loop through the history and dump it + for (int i = 0; i < hist_size; i++) { + int r = history[i].reentrancy; + int e = history[i].event; + Error("%d %d %s", e, r, history[i].fileline); + } + + // Dump the via string + Error("Via String: [%s]\n", t_state.via_string); + + // Dump header info + dump_state_hdr(&t_state.hdr_info.client_request, "Client Request"); + dump_state_hdr(&t_state.hdr_info.server_request, "Server Request"); + dump_state_hdr(&t_state.hdr_info.server_response, "Server Response"); + dump_state_hdr(&t_state.hdr_info.transform_response, "Transform Response"); + dump_state_hdr(&t_state.hdr_info.client_response, "Client Response"); + + Error("[%" PRId64 "] ------- end http state dump ---------", sm_id); +} + +void +HttpSM::dump_state_hdr(HTTPHdr *h, const char *s) +{ + // Dump the client request if available + if (h->valid()) { + int l = h->length_get(); + char *hdr_buf = (char *)ats_malloc(l + 1); + int index = 0; + int offset = 0; + + h->print(hdr_buf, l, &index, &offset); + + hdr_buf[l] = '\0'; + Error(" ---- %s [%" PRId64 "] ----\n%s\n", s, sm_id, hdr_buf); + ats_free(hdr_buf); + } +} + +/***************************************************************************** + ***************************************************************************** + **** **** + **** HttpTransact Interface **** + **** **** + ***************************************************************************** + *****************************************************************************/ +////////////////////////////////////////////////////////////////////////// +// +// HttpSM::call_transact_and_set_next_state(f) +// +// This routine takes an HttpTransact function , calls the function +// to perform some actions on the current HttpTransact::State, and +// then uses the HttpTransact return action code to set the next +// handler (state) for the state machine. HttpTransact could have +// returned the handler directly, but returns action codes in hopes of +// making a cleaner separation between the state machine and the +// HttpTransact logic. +// +////////////////////////////////////////////////////////////////////////// + +// Where is the goatherd? + +void +HttpSM::call_transact_and_set_next_state(TransactEntryFunc_t f) +{ + last_action = t_state.next_action; // remember where we were + + // The callee can either specify a method to call in to Transact, + // or call with NULL which indicates that Transact should use + // its stored entry point. + if (f == NULL) { + ink_release_assert(t_state.transact_return_point != NULL); + t_state.transact_return_point(&t_state); + } else { + f(&t_state); + } + + DebugSM("http", "[%" PRId64 "] State Transition: %s -> %s", sm_id, HttpDebugNames::get_action_name(last_action), + HttpDebugNames::get_action_name(t_state.next_action)); + + set_next_state(); + + return; +} + +////////////////////////////////////////////////////////////////////////////// +// +// HttpSM::set_next_state() +// +// call_transact_and_set_next_state() was broken into two parts, one +// which calls the HttpTransact method and the second which sets the +// next state. In a case which set_next_state() was not completed, +// the state function calls set_next_state() to retry setting the +// state. +// +////////////////////////////////////////////////////////////////////////////// +void +HttpSM::set_next_state() +{ + /////////////////////////////////////////////////////////////////////// + // Use the returned "next action" code to set the next state handler // + /////////////////////////////////////////////////////////////////////// + switch (t_state.next_action) { + case HttpTransact::SM_ACTION_API_PRE_REMAP: + case HttpTransact::SM_ACTION_API_POST_REMAP: + case HttpTransact::SM_ACTION_API_READ_REQUEST_HDR: + case HttpTransact::SM_ACTION_API_OS_DNS: + case HttpTransact::SM_ACTION_API_SEND_REQUEST_HDR: + case HttpTransact::SM_ACTION_API_READ_CACHE_HDR: + case HttpTransact::SM_ACTION_API_READ_RESPONSE_HDR: + case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR: + case HttpTransact::SM_ACTION_API_CACHE_LOOKUP_COMPLETE: { + t_state.api_next_action = t_state.next_action; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_POST_REMAP_SKIP: { + call_transact_and_set_next_state(NULL); + break; + } + + case HttpTransact::SM_ACTION_REMAP_REQUEST: { + if (!remapProcessor.using_separate_thread()) { + do_remap_request(true); /* run inline */ + DebugSM("url_rewrite", "completed inline remapping request for [%" PRId64 "]", sm_id); + t_state.url_remap_success = remapProcessor.finish_remap(&t_state); + call_transact_and_set_next_state(NULL); + } else { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_remap_request); + do_remap_request(false); /* dont run inline (iow on another thread) */ + } + break; + } + + case HttpTransact::SM_ACTION_DNS_LOOKUP: { + sockaddr const *addr; + + if ((strncmp(t_state.dns_info.lookup_name, "127.0.0.1", 9) == 0 || strncmp(t_state.dns_info.lookup_name, "::1", 3) == 0) && + ats_ip_pton(t_state.dns_info.lookup_name, t_state.host_db_info.ip()) == 0) { + // If it's 127.0.0.1 or ::1 don't bother with hostdb + DebugSM("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for %s because it's loopback", + t_state.dns_info.lookup_name); + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(NULL); + break; + } else if (t_state.api_server_addr_set) { + /* If the API has set the server address before the OS DNS lookup + * then we can skip the lookup + */ + ip_text_buffer ipb; + DebugSM("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for API supplied target %s.\n", + ats_ip_ntop(&t_state.server_info.dst_addr, ipb, sizeof(ipb))); + // this seems wasteful as we will just copy it right back + ats_ip_copy(t_state.host_db_info.ip(), &t_state.server_info.dst_addr); + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(NULL); + break; + } else if (url_remap_mode == HttpTransact::URL_REMAP_FOR_OS && t_state.first_dns_lookup) { + DebugSM("cdn", "Skipping DNS Lookup"); + // skip the DNS lookup + t_state.first_dns_lookup = false; + call_transact_and_set_next_state(HttpTransact::HandleFiltering); + break; + } else if (t_state.http_config_param->use_client_target_addr == 2 && !t_state.url_remap_success && + t_state.parent_result.result != PARENT_SPECIFIED && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_ADDR_TRY_DEFAULT && + ats_is_ip(addr = t_state.state_machine->ua_session->get_netvc()->get_local_addr())) { + /* If the connection is client side transparent and the URL + * was not remapped/directed to parent proxy, we can use the + * client destination IP address instead of doing a DNS + * lookup. This is controlled by the 'use_client_target_addr' + * configuration parameter. + */ + if (is_debug_tag_set("dns")) { + ip_text_buffer ipb; + DebugSM("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup for client supplied target %s.\n", + ats_ip_ntop(addr, ipb, sizeof(ipb))); + } + ats_ip_copy(t_state.host_db_info.ip(), addr); + if (t_state.hdr_info.client_request.version_get() == HTTPVersion(0, 9)) + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_09; + else if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 0)) + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_10; + else + t_state.host_db_info.app.http_data.http_version = HostDBApplicationInfo::HTTP_VERSION_11; + + t_state.dns_info.lookup_success = true; + // cache this result so we don't have to unreliably duplicate the + // logic later if the connect fails. + t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_ADDR_TRY_CLIENT; + call_transact_and_set_next_state(NULL); + break; + } else if (t_state.parent_result.result == PARENT_UNDEFINED && t_state.dns_info.lookup_success) { + // Already set, and we don't have a parent proxy to lookup + ink_assert(ats_is_ip(t_state.host_db_info.ip())); + DebugSM("dns", "[HttpTransact::HandleRequest] Skipping DNS lookup, provided by plugin"); + call_transact_and_set_next_state(NULL); + break; + } else if (t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent) { + if (t_state.cop_test_page) + ats_ip_copy(t_state.host_db_info.ip(), t_state.state_machine->ua_session->get_netvc()->get_local_addr()); + + t_state.dns_info.lookup_success = true; + call_transact_and_set_next_state(NULL); + break; + } + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_hostdb_lookup); + + ink_assert(t_state.dns_info.looking_up != HttpTransact::UNDEFINED_LOOKUP); + do_hostdb_lookup(); + break; + } + + case HttpTransact::SM_ACTION_DNS_REVERSE_LOOKUP: { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_hostdb_reverse_lookup); + do_hostdb_reverse_lookup(); + break; + } + + case HttpTransact::SM_ACTION_CACHE_LOOKUP: { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_cache_open_read); + do_cache_lookup_and_read(); + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_OPEN: { + if (congestionControlEnabled && (t_state.congest_saved_next_action == HttpTransact::SM_ACTION_UNDEFINED)) { + t_state.congest_saved_next_action = HttpTransact::SM_ACTION_ORIGIN_SERVER_OPEN; + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_congestion_control_lookup); + if (!do_congestion_control_lookup()) + break; + } + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_http_server_open); + + // We need to close the previous attempt + if (server_entry) { + ink_assert(server_entry->vc_type == HTTP_SERVER_VC); + vc_table.cleanup_entry(server_entry); + server_entry = NULL; + server_session = NULL; + } else { + // Now that we have gotten the user agent request, we can cancel + // the inactivity timeout associated with it. Note, however, that + // we must not cancel the inactivity timeout if the message + // contains a body (as indicated by the non-zero request_content_length + // field). This indicates that a POST operation is taking place and + // that the client is still sending data to the origin server. The + // origin server cannot reply until the entire request is received. In + // light of this dependency, TS must ensure that the client finishes + // sending its request and for this reason, the inactivity timeout + // cannot be cancelled. + if (ua_session && !t_state.hdr_info.request_content_length) { + NetVConnection *vc = ua_session->get_netvc(); + if (vc) { + ua_session->cancel_inactivity_timeout(); + } else { + terminate_sm = true; + return; // Give up if there is no session netvc + } + } else if (!ua_session) { + terminate_sm = true; + return; // Give up if there is no session + } + } + + do_http_server_open(); + break; + } + + case HttpTransact::SM_ACTION_SERVER_PARSE_NEXT_HDR: { + setup_server_read_response_header(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_100_RESPONSE: { + setup_100_continue_transfer(); + break; + } + + case HttpTransact::SM_ACTION_SERVER_READ: { + t_state.source = HttpTransact::SOURCE_HTTP_ORIGIN_SERVER; + + if (transform_info.vc) { + ink_assert(t_state.hdr_info.client_response.valid() == 0); + ink_assert((t_state.hdr_info.transform_response.valid() ? true : false) == true); + HttpTunnelProducer *p = setup_server_transfer_to_transform(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } else { + ink_assert((t_state.hdr_info.client_response.valid() ? true : false) == true); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + + // check to see if we are going to handle the redirection from server response and if there is a plugin hook set + if (hooks_set && is_redirect_required() == false) { + do_api_callout_internal(); + } else { + do_redirect(); + handle_api_return(); + } + } + break; + } + + case HttpTransact::SM_ACTION_SERVE_FROM_CACHE: { + ink_assert(t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE || + t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE_AND_DELETE || + t_state.cache_info.action == HttpTransact::CACHE_DO_SERVE_AND_UPDATE); + release_server_session(true); + t_state.source = HttpTransact::SOURCE_CACHE; + + if (transform_info.vc) { + ink_assert(t_state.hdr_info.client_response.valid() == 0); + ink_assert((t_state.hdr_info.transform_response.valid() ? true : false) == true); + t_state.hdr_info.cache_response.create(HTTP_TYPE_RESPONSE); + t_state.hdr_info.cache_response.copy(&t_state.hdr_info.transform_response); + + HttpTunnelProducer *p = setup_cache_transfer_to_transform(); + perform_cache_write_action(); + tunnel.tunnel_run(p); + } else { + ink_assert((t_state.hdr_info.client_response.valid() ? true : false) == true); + t_state.hdr_info.cache_response.create(HTTP_TYPE_RESPONSE); + t_state.hdr_info.cache_response.copy(&t_state.hdr_info.client_response); + + perform_cache_write_action(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + + // check to see if we are going to handle the redirection from server response and if there is a plugin hook set + if (hooks_set && is_redirect_required() == false) { + do_api_callout_internal(); + } else { + do_redirect(); + handle_api_return(); + } + } + break; + } + + case HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE: { + ink_assert((cache_sm.cache_write_vc == NULL) || t_state.redirect_info.redirect_in_process); + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_cache_open_write); + + do_cache_prepare_write(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_WRITE: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_NOOP: { + if (server_entry == NULL || server_entry->in_tunnel == false) { + release_server_session(); + } + // If we're in state SEND_API_RESPONSE_HDR, it means functions + // registered to hook SEND_RESPONSE_HDR have already been called. So we do not + // need to call do_api_callout. Otherwise TS loops infinitely in this state ! + if (t_state.api_next_action == HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR) { + handle_api_return(); + } else { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + } + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_DELETE: { + // Nuke all the alternates since this is mostly likely + // the result of a delete method + cache_sm.end_both(); + do_cache_delete_all_alts(NULL); + + release_server_session(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS: { + issue_cache_update(); + cache_sm.close_read(); + + release_server_session(); + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_SEND_ERROR_CACHE_NOOP: { + setup_error_transfer(); + break; + } + + case HttpTransact::SM_ACTION_INTERNAL_REQUEST: { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_handle_stat_page); + Action *action_handle = statPagesManager.handle_http(this, &t_state.hdr_info.client_request); + + if (action_handle != ACTION_RESULT_DONE) { + pending_action = action_handle; + historical_action = pending_action; + } + + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN: { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_mark_os_down); + + ink_assert(t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER); + + // TODO: This might not be optimal (or perhaps even correct), but it will + // effectively mark the host as down. What's odd is that state_mark_os_down + // above isn't triggering. + HttpSM::do_hostdb_update_if_necessary(); + + do_hostdb_lookup(); + break; + } + + case HttpTransact::SM_ACTION_SSL_TUNNEL: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_ORIGIN_SERVER_RAW_OPEN: { + if (congestionControlEnabled && (t_state.congest_saved_next_action == HttpTransact::SM_ACTION_UNDEFINED)) { + t_state.congest_saved_next_action = HttpTransact::SM_ACTION_ORIGIN_SERVER_RAW_OPEN; + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_congestion_control_lookup); + if (!do_congestion_control_lookup()) + break; + } + + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_raw_http_server_open); + + ink_assert(server_entry == NULL); + do_http_server_open(true); + break; + } + + case HttpTransact::SM_ACTION_ICP_QUERY: { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_icp_lookup); + do_icp_lookup(); + break; + } + + case HttpTransact::SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM: { + ink_assert(t_state.cache_info.transform_action == HttpTransact::CACHE_PREPARE_TO_WRITE); + + if (transform_cache_sm.cache_write_vc) { + // We've already got the write_vc that + // didn't use for the untransformed copy + ink_assert(cache_sm.cache_write_vc == NULL); + ink_assert(t_state.api_info.cache_untransformed == false); + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_SUCCESS; + call_transact_and_set_next_state(NULL); + } else { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_cache_open_write); + + do_cache_prepare_write_transform(); + } + break; + } + + case HttpTransact::SM_ACTION_TRANSFORM_READ: { + t_state.api_next_action = HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR; + do_api_callout(); + break; + } + + case HttpTransact::SM_ACTION_READ_PUSH_HDR: { + setup_push_read_response_header(); + break; + } + + case HttpTransact::SM_ACTION_STORE_PUSH_BODY: { + // This can return NULL - do we really want to run the tunnel in that case? + // But that's how it was before this change. + HttpTunnelProducer *p = setup_push_transfer_to_cache(); + tunnel.tunnel_run(p); + break; + } + + case HttpTransact::SM_ACTION_CACHE_PREPARE_UPDATE: { + ink_assert(t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE); + do_cache_prepare_update(); + break; + } + case HttpTransact::SM_ACTION_CACHE_ISSUE_UPDATE: { + if (t_state.api_update_cached_object == HttpTransact::UPDATE_CACHED_OBJECT_ERROR) { + t_state.cache_info.object_read = NULL; + cache_sm.close_read(); + } + issue_cache_update(); + call_transact_and_set_next_state(NULL); + break; + } + +#ifdef PROXY_DRAIN + case HttpTransact::SM_ACTION_DRAIN_REQUEST_BODY: { + do_drain_request_body(); + break; + } +#endif /* PROXY_DRAIN */ + + case HttpTransact::SM_ACTION_CONTINUE: { + ink_release_assert(!"Not implemented"); + } + + default: { + ink_release_assert("!Unknown next action"); + } + } +} + +void +clear_http_handler_times() +{ +} + +bool +HttpSM::do_congestion_control_lookup() +{ + ink_assert(pending_action == NULL); + + Action *congestion_control_action_handle = get_congest_entry(this, &t_state.request_data, &t_state.pCongestionEntry); + if (congestion_control_action_handle != ACTION_RESULT_DONE) { + pending_action = congestion_control_action_handle; + historical_action = pending_action; + return false; + } + return true; +} + +int +HttpSM::state_congestion_control_lookup(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_congestion_control_lookup, event); + if (event == CONGESTION_EVENT_CONTROL_LOOKUP_DONE) { + pending_action = NULL; + t_state.next_action = t_state.congest_saved_next_action; + t_state.transact_return_point = NULL; + set_next_state(); + } else { + if (pending_action != NULL) { + pending_action->cancel(); + pending_action = NULL; + } + if (t_state.congest_saved_next_action == HttpTransact::SM_ACTION_ORIGIN_SERVER_OPEN) { + return state_http_server_open(event, data); + } else if (t_state.congest_saved_next_action == HttpTransact::SM_ACTION_ORIGIN_SERVER_RAW_OPEN) { + return state_raw_http_server_open(event, data); + } + } + return 0; +} + +// YTS Team, yamsat Plugin + +void +HttpSM::do_redirect() +{ + DebugSM("http_redirect", "[HttpSM::do_redirect]"); + if (!enable_redirection || redirection_tries >= t_state.txn_conf->number_of_redirections) { + tunnel.deallocate_redirect_postdata_buffers(); + return; + } + + // if redirect_url is set by an user's plugin, yts will redirect to this url anyway. + if (is_redirect_required()) { + if (redirect_url != NULL || t_state.hdr_info.client_response.field_find(MIME_FIELD_LOCATION, MIME_LEN_LOCATION)) { + if (Log::transaction_logging_enabled() && t_state.api_info.logging_enabled) { + LogAccessHttp accessor(this); + if (redirect_url == NULL) { + if (t_state.squid_codes.log_code == SQUID_LOG_TCP_HIT) + t_state.squid_codes.log_code = SQUID_LOG_TCP_HIT_REDIRECT; + else + t_state.squid_codes.log_code = SQUID_LOG_TCP_MISS_REDIRECT; + } else { + if (t_state.squid_codes.log_code == SQUID_LOG_TCP_HIT) + t_state.squid_codes.log_code = SQUID_LOG_TCP_HIT_X_REDIRECT; + else + t_state.squid_codes.log_code = SQUID_LOG_TCP_MISS_X_REDIRECT; + } + + int ret = Log::access(&accessor); + + if (ret & Log::FULL) { + DebugSM("http", "[update_stats] Logging system indicates FULL."); + } + if (ret & Log::FAIL) { + Log::error("failed to log transaction for at least one log object"); + } + } + + if (redirect_url != NULL) { + redirect_request(redirect_url, redirect_url_len); + ats_free((void *)redirect_url); + redirect_url = NULL; + redirect_url_len = 0; + HTTP_INCREMENT_DYN_STAT(http_total_x_redirect_stat); + } else { + // get the location header and setup the redirect + int redir_len; + char *redir_url = (char *)t_state.hdr_info.client_response.value_get(MIME_FIELD_LOCATION, MIME_LEN_LOCATION, &redir_len); + redirect_request(redir_url, redir_len); + } + + } else { + enable_redirection = false; + } + } else { + enable_redirection = false; + } +} + +void +HttpSM::redirect_request(const char *redirect_url, const int redirect_len) +{ + DebugSM("http_redirect", "[HttpSM::redirect_request]"); + // get a reference to the client request header and client url and check to see if the url is valid + HTTPHdr &clientRequestHeader = t_state.hdr_info.client_request; + URL &clientUrl = *clientRequestHeader.url_get(); + if (!clientUrl.valid()) { + return; + } + + bool valid_origHost = true; + int origHost_len, origMethod_len; + char origHost[MAXDNAME]; + char origMethod[255]; + int origPort = 80; + + if (t_state.hdr_info.server_request.valid()) { + char *tmpOrigHost; + + origPort = t_state.hdr_info.server_request.port_get(); + tmpOrigHost = (char *)t_state.hdr_info.server_request.value_get(MIME_FIELD_HOST, MIME_LEN_HOST, &origHost_len); + + if (tmpOrigHost) { + memcpy(origHost, tmpOrigHost, origHost_len); + origHost[origHost_len] = '\0'; + } else { + valid_origHost = false; + } + + char *tmpOrigMethod = (char *)t_state.hdr_info.server_request.method_get(&origMethod_len); + if (tmpOrigMethod) { + memcpy(origMethod, tmpOrigMethod, origMethod_len); + } else { + valid_origHost = false; + } + } else { + DebugSM("http_redir_error", "t_state.hdr_info.server_request not valid"); + valid_origHost = false; + } + + t_state.redirect_info.redirect_in_process = true; + + // set the passed in location url and parse it + URL &redirectUrl = t_state.redirect_info.redirect_url; + if (!redirectUrl.valid()) { + redirectUrl.create(NULL); + } + + // reset the path from previous redirects (if any) + t_state.redirect_info.redirect_url.path_set(NULL, 0); + + // redirectUrl.user_set(redirect_url, redirect_len); + redirectUrl.parse(redirect_url, redirect_len); + + // copy the client url to the original url + URL &origUrl = t_state.redirect_info.original_url; + if (!origUrl.valid()) { + origUrl.create(NULL); + origUrl.copy(&clientUrl); + } + // copy the redirect url to the client url + clientUrl.copy(&redirectUrl); + + //(bug 2540703) Clear the previous response if we will attempt the redirect + if (t_state.hdr_info.client_response.valid()) { + // XXX - doing a destroy() for now, we can do a fileds_clear() if we have performance issue + t_state.hdr_info.client_response.destroy(); + } + + int scheme = t_state.next_hop_scheme; + int scheme_len = hdrtoken_index_to_length(scheme); + const char *next_hop_scheme = hdrtoken_index_to_wks(scheme); + char scheme_str[scheme_len + 1]; + + if (next_hop_scheme) + memcpy(scheme_str, next_hop_scheme, scheme_len); + else + valid_origHost = false; + + t_state.hdr_info.server_request.destroy(); + + // we want to close the server session + // will do that in handle_api_return under the + // HttpTransact::SM_ACTION_REDIRECT_READ state + t_state.parent_result.reset(); + t_state.request_sent_time = 0; + t_state.response_received_time = 0; + t_state.cache_info.write_lock_state = HttpTransact::CACHE_WL_INIT; + t_state.next_action = HttpTransact::SM_ACTION_REDIRECT_READ; + // we have a new OS and need to have DNS lookup the new OS + t_state.dns_info.lookup_success = false; + t_state.force_dns = false; + + if (t_state.txn_conf->cache_http) { + t_state.cache_info.object_read = NULL; + } + + bool noPortInHost = HttpConfig::m_master.redirection_host_no_port; + + // check to see if the client request passed a host header, if so copy the host and port from the redirect url and + // make a new host header + if (t_state.hdr_info.client_request.presence(MIME_PRESENCE_HOST)) { + int host_len; + const char *host = clientUrl.host_get(&host_len); + + if (host != NULL) { + int port = clientUrl.port_get(); + int redirectSchemeLen; + const char *redirectScheme = clientUrl.scheme_get(&redirectSchemeLen); + + if (redirectScheme == NULL) { + clientUrl.scheme_set(scheme_str, scheme_len); + DebugSM("http_redirect", "[HttpSM::redirect_request] URL without scheme %.*s", redirectSchemeLen, redirectScheme); + } + + if (noPortInHost) { + int redirectSchemeIdx = clientUrl.scheme_get_wksidx(); + bool defaultPort = + (((redirectSchemeIdx == URL_WKSIDX_HTTP) && (port == 80)) || ((redirectSchemeIdx == URL_WKSIDX_HTTPS) && (port == 443))); + + if (!defaultPort) { + noPortInHost = false; + } + } + + if (!noPortInHost) { + char buf[host_len + 7]; // 5 + 1 + 1 ("12345" + ':' + '\0') + + host_len = snprintf(buf, host_len + 7, "%.*s:%d", host_len, host, port); + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, buf, host_len); + } else { + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, host, host_len); + } + t_state.hdr_info.client_request.m_target_cached = false; + } else { + // the client request didn't have a host, so use the current origin host + if (valid_origHost) { + char *saveptr = NULL; + + // the client request didn't have a host, so use the current origin host + DebugSM("http_redirect", "[HttpSM::redirect_request] keeping client request host %s://%s", next_hop_scheme, origHost); + char *origHostNoPort = strtok_r(origHost, ":", &saveptr); + + if (origHostNoPort == NULL) { + goto LhostError; + } + + host_len = strlen(origHostNoPort); + if (noPortInHost) { + int redirectSchemeIdx = t_state.next_hop_scheme; + bool defaultPort = (((redirectSchemeIdx == URL_WKSIDX_HTTP) && (origPort == 80)) || + ((redirectSchemeIdx == URL_WKSIDX_HTTPS) && (origPort == 443))); + + if (!defaultPort) + noPortInHost = false; + } + + if (!noPortInHost) { + char buf[host_len + 7]; // 5 + 1 + 1 ("12345" + ':' + '\0') + + host_len = snprintf(buf, host_len + 7, "%s:%d", origHostNoPort, origPort); + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, buf, host_len); + } else { + t_state.hdr_info.client_request.value_set(MIME_FIELD_HOST, MIME_LEN_HOST, origHostNoPort, host_len); + } + + // Cleanup of state etc. + url_nuke_proxy_stuff(clientUrl.m_url_impl); + url_nuke_proxy_stuff(t_state.hdr_info.client_request.m_url_cached.m_url_impl); + t_state.hdr_info.client_request.method_set(origMethod, origMethod_len); + t_state.hdr_info.client_request.m_target_cached = false; + clientUrl.scheme_set(scheme_str, scheme_len); + } else { + LhostError: + // the server request didn't have a host, so remove it from the headers + t_state.hdr_info.client_request.field_delete(MIME_FIELD_HOST, MIME_LEN_HOST); + } + } + } + + if (!t_state.cop_test_page) { + DUMP_HEADER("http_hdrs", &t_state.hdr_info.client_request, sm_id, "Framed Client Request..checking"); + } +} + +void +HttpSM::set_http_schedule(Continuation *contp) +{ + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::get_http_schedule); + schedule_cont = contp; +} + +int +HttpSM::get_http_schedule(int event, void * /* data ATS_UNUSED */) +{ + bool plugin_lock; + Ptr plugin_mutex; + if (schedule_cont->mutex) { + plugin_mutex = schedule_cont->mutex; + plugin_lock = MUTEX_TAKE_TRY_LOCK(schedule_cont->mutex, mutex->thread_holding); + + if (!plugin_lock) { + HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::get_http_schedule); + ink_assert(pending_action == NULL); + pending_action = mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(10)); + return 0; + } + } else { + plugin_lock = false; + } + + // handle Mutex; + schedule_cont->handleEvent(event, this); + if (plugin_lock) { + Mutex_unlock(plugin_mutex, mutex->thread_holding); + } + + return 0; +} + +bool +HttpSM::set_server_session_private(bool private_session) +{ + if (server_session != NULL) { + server_session->private_session = private_session; + return true; + } + return false; +} + +inline bool +HttpSM::is_private() +{ + bool res = false; + if (server_session) { + res = server_session->private_session; + } else if (ua_session) { + HttpServerSession *ss = ua_session->get_server_session(); + if (ss) { + res = ss->private_session; + } else if (will_be_private_ss) { + res = will_be_private_ss; + } + } + return res; +} + +// check to see if redirection is enabled and less than max redirections tries or if a plugin enabled redirection +inline bool +HttpSM::is_redirect_required() +{ + bool redirect_required = (enable_redirection && (redirection_tries <= t_state.txn_conf->number_of_redirections)); + + DebugSM("http_redirect", "is_redirect_required %u", redirect_required); + + if (redirect_required == true) { + HTTPStatus status = t_state.hdr_info.client_response.status_get(); + // check to see if the response from the orgin was a 301, 302, or 303 + switch (status) { + case HTTP_STATUS_MULTIPLE_CHOICES: // 300 + case HTTP_STATUS_MOVED_PERMANENTLY: // 301 + case HTTP_STATUS_MOVED_TEMPORARILY: // 302 + case HTTP_STATUS_SEE_OTHER: // 303 + case HTTP_STATUS_USE_PROXY: // 305 + case HTTP_STATUS_TEMPORARY_REDIRECT: // 307 + redirect_required = true; + break; + default: + redirect_required = false; + break; + } + + // if redirect_url is set by an user's plugin, ats will redirect to this url anyway. + if (redirect_url != NULL) { + redirect_required = true; + } + } + return redirect_required; +} diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h index 4c066e6766c..967134a6515 100644 --- a/proxy/http/HttpSM.h +++ b/proxy/http/HttpSM.h @@ -353,9 +353,6 @@ class HttpSM : public Continuation int tunnel_handler_100_continue(int event, void *data); int tunnel_handler_cache_fill(int event, void *data); -#ifdef PROXY_DRAIN - int state_drain_client_request_body(int event, void *data); -#endif /* PROXY_DRAIN */ int state_read_client_request_header(int event, void *data); int state_watch_for_client_abort(int event, void *data); int state_read_push_response_header(int event, void *data); @@ -421,9 +418,7 @@ class HttpSM : public Continuation void do_api_callout_internal(); void do_redirect(); void redirect_request(const char *redirect_url, const int redirect_len); -#ifdef PROXY_DRAIN void do_drain_request_body(); -#endif bool do_congestion_control_lookup(); diff --git a/proxy/http/HttpSM.h.orig b/proxy/http/HttpSM.h.orig new file mode 100644 index 00000000000..4c066e6766c --- /dev/null +++ b/proxy/http/HttpSM.h.orig @@ -0,0 +1,662 @@ +/** @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. + */ + +/**************************************************************************** + + HttpSM.h + + Description: + + + ****************************************************************************/ + +#ifndef _HTTP_SM_H_ +#define _HTTP_SM_H_ + +#include "ts/ink_platform.h" +#include "P_EventSystem.h" +#include "HttpCacheSM.h" +#include "HttpTransact.h" +#include "HttpTunnel.h" +#include "InkAPIInternal.h" +#include "../ProxyClientTransaction.h" +#include "HdrUtils.h" +//#include "AuthHttpAdapter.h" + +/* Enable LAZY_BUF_ALLOC to delay allocation of buffers until they + * are actually required. + * Enabling LAZY_BUF_ALLOC, stop Http code from allocation space + * for header buffer and tunnel buffer. The allocation is done by + * the net code in read_from_net when data is actually written into + * the buffer. By allocating memory only when it is required we can + * reduce the memory consumed by TS process. + * + * IMPORTANT NOTE: enable/disable LAZY_BUF_ALLOC in HttpServerSession.h + * as well. + */ +#define LAZY_BUF_ALLOC + +#define HTTP_API_CONTINUE (INK_API_EVENT_EVENTS_START + 0) +#define HTTP_API_ERROR (INK_API_EVENT_EVENTS_START + 1) + +// The default size for http header buffers when we don't +// need to include extra space for the document +static size_t const HTTP_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; + +// We want to use a larger buffer size when reading response +// headers from the origin server since we want to get +// as much of the document as possible on the first read +// Marco benchmarked about 3% ops/second improvement using +// the larger buffer size +static size_t const HTTP_SERVER_RESP_HDR_BUFFER_INDEX = BUFFER_SIZE_INDEX_8K; + +class HttpServerSession; +class AuthHttpAdapter; + +class HttpSM; +typedef int (HttpSM::*HttpSMHandler)(int event, void *data); + +enum HttpVC_t { + HTTP_UNKNOWN = 0, + HTTP_UA_VC, + HTTP_SERVER_VC, + HTTP_TRANSFORM_VC, + HTTP_CACHE_READ_VC, + HTTP_CACHE_WRITE_VC, + HTTP_RAW_SERVER_VC +}; + +enum BackgroundFill_t { + BACKGROUND_FILL_NONE = 0, + BACKGROUND_FILL_STARTED, + BACKGROUND_FILL_ABORTED, + BACKGROUND_FILL_COMPLETED, +}; + +extern ink_mutex debug_sm_list_mutex; + +struct HttpVCTableEntry { + VConnection *vc; + MIOBuffer *read_buffer; + MIOBuffer *write_buffer; + VIO *read_vio; + VIO *write_vio; + HttpSMHandler vc_handler; + HttpVC_t vc_type; + bool eos; + bool in_tunnel; +}; + +struct HttpVCTable { + static const int vc_table_max_entries = 4; + HttpVCTable(); + + HttpVCTableEntry *new_entry(); + HttpVCTableEntry *find_entry(VConnection *); + HttpVCTableEntry *find_entry(VIO *); + void remove_entry(HttpVCTableEntry *); + void cleanup_entry(HttpVCTableEntry *); + void cleanup_all(); + bool is_table_clear() const; + +private: + HttpVCTableEntry vc_table[vc_table_max_entries]; +}; + +inline bool +HttpVCTable::is_table_clear() const +{ + for (int i = 0; i < vc_table_max_entries; i++) { + if (vc_table[i].vc != NULL) { + return false; + } + } + return true; +} + +struct HttpTransformInfo { + HttpVCTableEntry *entry; + VConnection *vc; + + HttpTransformInfo() : entry(NULL), vc(NULL) {} +}; +#define HISTORY_SIZE 64 + +enum { + HTTP_SM_MAGIC_ALIVE = 0x0000FEED, + HTTP_SM_MAGIC_DEAD = 0xDEADFEED, +}; + +enum { + HTTP_SM_POST_UNKNOWN = 0, + HTTP_SM_POST_UA_FAIL = 1, + HTTP_SM_POST_SERVER_FAIL = 2, + HTTP_SM_POST_SUCCESS = 3, +}; + +enum { + HTTP_SM_TRANSFORM_OPEN = 0, + HTTP_SM_TRANSFORM_CLOSED = 1, + HTTP_SM_TRANSFORM_FAIL = 2, +}; + +enum HttpApiState_t { + HTTP_API_NO_CALLOUT, + HTTP_API_IN_CALLOUT, + HTTP_API_DEFERED_CLOSE, + HTTP_API_DEFERED_SERVER_ERROR, +}; + +enum HttpPluginTunnel_t { + HTTP_NO_PLUGIN_TUNNEL = 0, + HTTP_PLUGIN_AS_SERVER, + HTTP_PLUGIN_AS_INTERCEPT, +}; + +class CoreUtils; +class PluginVCCore; + +class HttpSM : public Continuation +{ + friend class HttpPagesHandler; + friend class CoreUtils; + +public: + HttpSM(); + void cleanup(); + virtual void destroy(); + + static HttpSM *allocate(); + HttpCacheSM &get_cache_sm(); // Added to get the object of CacheSM YTS Team, yamsat + HttpVCTableEntry *get_ua_entry(); // Added to get the ua_entry pointer - YTS-TEAM + + void init(); + + void attach_client_session(ProxyClientTransaction *client_vc_arg, IOBufferReader *buffer_reader); + + // Called by httpSessionManager so that we can reset + // the session timeouts and initiate a read while + // holding the lock for the server session + void attach_server_session(HttpServerSession *s); + + // Used to read attributes of + // the current active server session + HttpServerSession * + get_server_session() + { + return server_session; + } + + // Called by transact. Updates are fire and forget + // so there are no callbacks and are safe to do + // directly from transact + void do_hostdb_update_if_necessary(); + + // Called by transact. Decide if cached response supports Range and + // setup Range transfomration if so. + // return true when the Range is unsatisfiable + void do_range_setup_if_necessary(); + + void do_range_parse(MIMEField *range_field); + void calculate_output_cl(int64_t, int64_t); + void parse_range_and_compare(MIMEField *, int64_t); + + // Called by transact to prevent reset problems + // failed PUSH requests + void set_ua_half_close_flag(); + + // Called by either state_hostdb_lookup() or directly + // by the HostDB in the case of inline completion + // Handles the setting of all state necessary before + // calling transact to process the hostdb lookup + // A NULL 'r' argument indicates the hostdb lookup failed + void process_hostdb_info(HostDBInfo *r); + void process_srv_info(HostDBInfo *r); + + // Called by transact. Synchronous. + VConnection *do_transform_open(); + VConnection *do_post_transform_open(); + + // Called from InkAPI.cc which acquires the state machine lock + // before calling + int state_api_callback(int event, void *data); + int state_api_callout(int event, void *data); + + // Used for Http Stat Pages + HttpTunnel * + get_tunnel() + { + return &tunnel; + } + + // Debugging routines to dump the SM history, hdrs + void dump_state_on_assert(); + void dump_state_hdr(HTTPHdr *h, const char *s); + + // Functions for manipulating api hooks + void txn_hook_append(TSHttpHookID id, INKContInternal *cont); + void txn_hook_prepend(TSHttpHookID id, INKContInternal *cont); + APIHook *txn_hook_get(TSHttpHookID id); + + void add_history_entry(const char *fileline, int event, int reentrant); + void add_cache_sm(); + bool is_private(); + bool is_redirect_required(); + + int64_t sm_id; + unsigned int magic; + + // YTS Team, yamsat Plugin + bool enable_redirection; // To check if redirection is enabled + char *redirect_url; // url for force redirect (provide users a functionality to redirect to another url when needed) + int redirect_url_len; + int redirection_tries; // To monitor number of redirections + int64_t transfered_bytes; // Added to calculate POST data + bool post_failed; // Added to identify post failure + bool debug_on; // Transaction specific debug flag + + // Tunneling request to plugin + HttpPluginTunnel_t plugin_tunnel_type; + PluginVCCore *plugin_tunnel; + + HttpTransact::State t_state; + +protected: + int reentrancy_count; + + struct History { + const char *fileline; + unsigned short event; + short reentrancy; + }; + History history[HISTORY_SIZE]; + int history_pos; + + HttpTunnel tunnel; + + HttpVCTable vc_table; + + HttpVCTableEntry *ua_entry; + void remove_ua_entry(); + +public: + ProxyClientTransaction *ua_session; + BackgroundFill_t background_fill; + // AuthHttpAdapter authAdapter; + void set_http_schedule(Continuation *); + int get_http_schedule(int event, void *data); + +protected: + IOBufferReader *ua_buffer_reader; + IOBufferReader *ua_raw_buffer_reader; + + HttpVCTableEntry *server_entry; + HttpServerSession *server_session; + + /* Because we don't want to take a session from a shared pool if we know that it will be private, + * but we cannot set it to private until we have an attached server session. + * So we use this variable to indicate that + * we should create a new connection and then once we attach the session we'll mark it as private. + */ + bool will_be_private_ss; + int shared_session_retries; + IOBufferReader *server_buffer_reader; + void remove_server_entry(); + + HttpTransformInfo transform_info; + HttpTransformInfo post_transform_info; + /// Set if plugin client / user agents are active. + /// Need primarily for cleanup. + bool has_active_plugin_agents; + + HttpCacheSM cache_sm; + HttpCacheSM transform_cache_sm; + HttpCacheSM *second_cache_sm; + + HttpSMHandler default_handler; + Action *pending_action; + Action *historical_action; + Continuation *schedule_cont; + + HTTPParser http_parser; + void start_sub_sm(); + + int main_handler(int event, void *data); + int tunnel_handler(int event, void *data); + int tunnel_handler_push(int event, void *data); + int tunnel_handler_post(int event, void *data); + + // YTS Team, yamsat Plugin + int tunnel_handler_for_partial_post(int event, void *data); + + void tunnel_handler_post_or_put(HttpTunnelProducer *p); + + int tunnel_handler_100_continue(int event, void *data); + int tunnel_handler_cache_fill(int event, void *data); +#ifdef PROXY_DRAIN + int state_drain_client_request_body(int event, void *data); +#endif /* PROXY_DRAIN */ + int state_read_client_request_header(int event, void *data); + int state_watch_for_client_abort(int event, void *data); + int state_read_push_response_header(int event, void *data); + int state_srv_lookup(int event, void *data); + int state_hostdb_lookup(int event, void *data); + int state_hostdb_reverse_lookup(int event, void *data); + int state_mark_os_down(int event, void *data); + int state_handle_stat_page(int event, void *data); + int state_icp_lookup(int event, void *data); + int state_auth_callback(int event, void *data); + int state_add_to_list(int event, void *data); + int state_remove_from_list(int event, void *data); + int state_congestion_control_lookup(int event, void *data); + + // Y! ebalsa: remap handlers + int state_remap_request(int event, void *data); + void do_remap_request(bool); + + // Cache Handlers + int state_cache_open_read(int event, void *data); + int state_cache_open_write(int event, void *data); + + // Http Server Handlers + int state_http_server_open(int event, void *data); + int state_raw_http_server_open(int event, void *data); + int state_send_server_request_header(int event, void *data); + int state_acquire_server_read(int event, void *data); + int state_read_server_response_header(int event, void *data); + + // API + int state_request_wait_for_transform_read(int event, void *data); + int state_response_wait_for_transform_read(int event, void *data); + int state_common_wait_for_transform_read(HttpTransformInfo *t_info, HttpSMHandler tunnel_handler, int event, void *data); + + // Tunnel event handlers + int tunnel_handler_server(int event, HttpTunnelProducer *p); + int tunnel_handler_ua(int event, HttpTunnelConsumer *c); + int tunnel_handler_ua_push(int event, HttpTunnelProducer *p); + int tunnel_handler_100_continue_ua(int event, HttpTunnelConsumer *c); + int tunnel_handler_cache_write(int event, HttpTunnelConsumer *c); + int tunnel_handler_cache_read(int event, HttpTunnelProducer *p); + int tunnel_handler_post_ua(int event, HttpTunnelProducer *c); + int tunnel_handler_post_server(int event, HttpTunnelConsumer *c); + int tunnel_handler_ssl_producer(int event, HttpTunnelProducer *p); + int tunnel_handler_ssl_consumer(int event, HttpTunnelConsumer *p); + int tunnel_handler_transform_write(int event, HttpTunnelConsumer *c); + int tunnel_handler_transform_read(int event, HttpTunnelProducer *p); + int tunnel_handler_plugin_agent(int event, HttpTunnelConsumer *c); + + void do_hostdb_lookup(); + void do_hostdb_reverse_lookup(); + void do_cache_lookup_and_read(); + void do_http_server_open(bool raw = false); + void do_setup_post_tunnel(HttpVC_t to_vc_type); + void do_cache_prepare_write(); + void do_cache_prepare_write_transform(); + void do_cache_prepare_update(); + void do_cache_prepare_action(HttpCacheSM *c_sm, CacheHTTPInfo *object_read_info, bool retry, bool allow_multiple = false); + void do_cache_delete_all_alts(Continuation *cont); + void do_icp_lookup(); + void do_auth_callout(); + void do_api_callout(); + void do_api_callout_internal(); + void do_redirect(); + void redirect_request(const char *redirect_url, const int redirect_len); +#ifdef PROXY_DRAIN + void do_drain_request_body(); +#endif + + bool do_congestion_control_lookup(); + + virtual void handle_api_return(); + void handle_server_setup_error(int event, void *data); + void handle_http_server_open(); + void handle_post_failure(); + void mark_host_failure(HostDBInfo *info, time_t time_down); + void mark_server_down_on_client_abort(); + void release_server_session(bool serve_from_cache = false); + void set_ua_abort(HttpTransact::AbortState_t ua_abort, int event); + int write_header_into_buffer(HTTPHdr *h, MIOBuffer *b); + int write_response_header_into_buffer(HTTPHdr *h, MIOBuffer *b); + void setup_blind_tunnel_port(); + void setup_client_header_nca(); + void setup_client_read_request_header(); + void setup_push_read_response_header(); + void setup_server_read_response_header(); + void setup_cache_lookup_complete_api(); + void setup_server_send_request(); + void setup_server_send_request_api(); + HttpTunnelProducer *setup_server_transfer(); + void setup_server_transfer_to_cache_only(); + HttpTunnelProducer *setup_cache_read_transfer(); + void setup_internal_transfer(HttpSMHandler handler); + void setup_error_transfer(); + void setup_100_continue_transfer(); + HttpTunnelProducer *setup_push_transfer_to_cache(); + void setup_transform_to_server_transfer(); + void setup_cache_write_transfer(HttpCacheSM *c_sm, VConnection *source_vc, HTTPInfo *store_info, int64_t skip_bytes, + const char *name); + void issue_cache_update(); + void perform_cache_write_action(); + void perform_transform_cache_write_action(); + void perform_nca_cache_action(); + void setup_blind_tunnel(bool send_response_hdr); + HttpTunnelProducer *setup_server_transfer_to_transform(); + HttpTunnelProducer *setup_transfer_from_transform(); + HttpTunnelProducer *setup_cache_transfer_to_transform(); + HttpTunnelProducer *setup_transfer_from_transform_to_cache_only(); + void setup_plugin_agents(HttpTunnelProducer *p); + + HttpTransact::StateMachineAction_t last_action; + int (HttpSM::*m_last_state)(int event, void *data); + virtual void set_next_state(); + void call_transact_and_set_next_state(TransactEntryFunc_t f); + + bool is_http_server_eos_truncation(HttpTunnelProducer *); + bool is_bg_fill_necessary(HttpTunnelConsumer *c); + int find_server_buffer_size(); + int find_http_resp_buffer_size(int64_t cl); + int64_t server_transfer_init(MIOBuffer *buf, int hdr_size); + +public: + // Stats & Logging Info + int client_request_hdr_bytes; + int64_t client_request_body_bytes; + int server_request_hdr_bytes; + int64_t server_request_body_bytes; + int server_response_hdr_bytes; + int64_t server_response_body_bytes; + int client_response_hdr_bytes; + int64_t client_response_body_bytes; + int cache_response_hdr_bytes; + int64_t cache_response_body_bytes; + int pushed_response_hdr_bytes; + int64_t pushed_response_body_bytes; + bool client_tcp_reused; + // Info about client's SSL connection. + bool client_ssl_reused; + bool client_connection_is_ssl; + const char *client_sec_protocol; + const char *client_cipher_suite; + int server_transact_count; + bool server_connection_is_ssl; + + TransactionMilestones milestones; + ink_hrtime api_timer; + // The next two enable plugins to tag the state machine for + // the purposes of logging so the instances can be correlated + // with the source plugin. + char const *plugin_tag; + int64_t plugin_id; + + // hooks_set records whether there are any hooks relevant + // to this transaction. Used to avoid costly calls + // do_api_callout_internal() + bool hooks_set; + +protected: + TSHttpHookID cur_hook_id; + APIHook *cur_hook; + + // + // Continuation time keeper + int64_t prev_hook_start_time; + + int cur_hooks; + HttpApiState_t callout_state; + + // api_hooks must not be changed directly + // Use txn_hook_{ap,pre}pend so hooks_set is + // updated + HttpAPIHooks api_hooks; + + // The terminate flag is set by handlers and checked by the + // main handler who will terminate the state machine + // when the flag is set + bool terminate_sm; + bool kill_this_async_done; + bool parse_range_done; + virtual int kill_this_async_hook(int event, void *data); + void kill_this(); + void update_stats(); + void transform_cleanup(TSHttpHookID hook, HttpTransformInfo *info); + bool is_transparent_passthrough_allowed(); + void plugin_agents_cleanup(); + +public: + LINK(HttpSM, debug_link); + +public: + bool set_server_session_private(bool private_session); + bool + is_dying() const + { + return terminate_sm; + } +}; + +// Function to get the cache_sm object - YTS Team, yamsat +inline HttpCacheSM & +HttpSM::get_cache_sm() +{ + return cache_sm; +} + +// Function to get the ua_entry pointer - YTS Team, yamsat +inline HttpVCTableEntry * +HttpSM::get_ua_entry() +{ + return ua_entry; +} + +inline HttpSM * +HttpSM::allocate() +{ + extern ClassAllocator httpSMAllocator; + return httpSMAllocator.alloc(); +} + +inline void +HttpSM::remove_ua_entry() +{ + vc_table.remove_entry(ua_entry); + ua_entry = NULL; +} + +inline void +HttpSM::remove_server_entry() +{ + if (server_entry) { + vc_table.remove_entry(server_entry); + server_entry = NULL; + } +} + +inline int +HttpSM::write_response_header_into_buffer(HTTPHdr *h, MIOBuffer *b) +{ + if (t_state.client_info.http_version == HTTPVersion(0, 9)) { + return 0; + } else { + return write_header_into_buffer(h, b); + } +} + +inline void +HttpSM::add_history_entry(const char *fileline, int event, int reentrant) +{ + int pos = history_pos++ % HISTORY_SIZE; + history[pos].fileline = fileline; + history[pos].event = (unsigned short)event; + history[pos].reentrancy = (short)reentrant; +} + +inline int +HttpSM::find_server_buffer_size() +{ + return find_http_resp_buffer_size(t_state.hdr_info.response_content_length); +} + +inline void +HttpSM::txn_hook_append(TSHttpHookID id, INKContInternal *cont) +{ + api_hooks.append(id, cont); + hooks_set = 1; +} + +inline void +HttpSM::txn_hook_prepend(TSHttpHookID id, INKContInternal *cont) +{ + api_hooks.prepend(id, cont); + hooks_set = 1; +} + +inline APIHook * +HttpSM::txn_hook_get(TSHttpHookID id) +{ + return api_hooks.get(id); +} + +inline void +HttpSM::add_cache_sm() +{ + if (second_cache_sm == NULL) { + second_cache_sm = new HttpCacheSM; + second_cache_sm->init(this, mutex); + if (t_state.cache_info.object_read != NULL) { + second_cache_sm->cache_read_vc = cache_sm.cache_read_vc; + cache_sm.cache_read_vc = NULL; + second_cache_sm->read_locked = cache_sm.read_locked; + t_state.cache_info.second_object_read = t_state.cache_info.object_read; + t_state.cache_info.object_read = NULL; + } + } +} + +inline bool +HttpSM::is_transparent_passthrough_allowed() +{ + return (t_state.client_info.is_transparent && ua_session->is_transparent_passthrough_allowed() && + ua_session->get_transact_count() == 1); +} + +#endif diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h index 858426bbd9d..0d2173128a5 100644 --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -480,10 +480,6 @@ class HttpTransact SM_ACTION_INTERNAL_REQUEST, SM_ACTION_SEND_ERROR_CACHE_NOOP, -#ifdef PROXY_DRAIN - SM_ACTION_DRAIN_REQUEST_BODY, -#endif /* PROXY_DRAIN */ - SM_ACTION_SERVE_FROM_CACHE, SM_ACTION_SERVER_READ, SM_ACTION_SERVER_PARSE_NEXT_HDR, diff --git a/proxy/http/HttpTransact.h.orig b/proxy/http/HttpTransact.h.orig new file mode 100644 index 00000000000..858426bbd9d --- /dev/null +++ b/proxy/http/HttpTransact.h.orig @@ -0,0 +1,1374 @@ +/** @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. + */ + +#if !defined(_HttpTransact_h_) +#define _HttpTransact_h_ + +#include "ts/ink_platform.h" +#include "P_HostDB.h" +#include "P_Net.h" +#include "HttpConfig.h" +#include "HTTP.h" +#include "HttpTransactCache.h" +#include "ControlMatcher.h" +#include "CacheControl.h" +#include "ParentSelection.h" +#include "ProxyConfig.h" +#include "Transform.h" +#include "Milestones.h" +//#include "HttpAuthParams.h" +#include "api/ts/remap.h" +#include "RemapPluginInfo.h" +#include "UrlMapping.h" +#include + +#include "congest/Congestion.h" + +#define MAX_DNS_LOOKUPS 2 + +#define HTTP_RELEASE_ASSERT(X) ink_release_assert(X) +// #define ink_cluster_time(X) time(X) + +#define ACQUIRE_PRINT_LOCK() // ink_mutex_acquire(&print_lock); +#define RELEASE_PRINT_LOCK() // ink_mutex_release(&print_lock); + +#define DUMP_HEADER(T, H, I, S) \ + { \ + if (diags->on(T)) { \ + ACQUIRE_PRINT_LOCK() \ + fprintf(stderr, "+++++++++ %s +++++++++\n", S); \ + fprintf(stderr, "-- State Machine Id: %" PRId64 "\n", I); \ + char b[4096]; \ + int used, tmp, offset; \ + int done; \ + offset = 0; \ + if ((H)->valid()) { \ + do { \ + used = 0; \ + tmp = offset; \ + done = (H)->print(b, 4095, &used, &tmp); \ + offset += used; \ + b[used] = '\0'; \ + fprintf(stderr, "%s", b); \ + } while (!done); \ + } \ + RELEASE_PRINT_LOCK() \ + } \ + } + +#define TRANSACT_SETUP_RETURN(n, r) \ + s->next_action = n; \ + s->transact_return_point = r; \ + DebugSpecific((s->state_machine && s->state_machine->debug_on), "http_trans", "Next action %s; %s", #n, #r); + +#define TRANSACT_RETURN(n, r) \ + TRANSACT_SETUP_RETURN(n, r) \ + return; + +#define TRANSACT_RETURN_VAL(n, r, v) \ + TRANSACT_SETUP_RETURN(n, r) \ + return v; + +#define SET_UNPREPARE_CACHE_ACTION(C) \ + { \ + if (C.action == HttpTransact::CACHE_PREPARE_TO_DELETE) { \ + C.action = HttpTransact::CACHE_DO_DELETE; \ + } else if (C.action == HttpTransact::CACHE_PREPARE_TO_UPDATE) { \ + C.action = HttpTransact::CACHE_DO_UPDATE; \ + } else { \ + C.action = HttpTransact::CACHE_DO_WRITE; \ + } \ + } + +typedef time_t ink_time_t; + +struct HttpConfigParams; +class HttpSM; + +#include "ts/InkErrno.h" +#define UNKNOWN_INTERNAL_ERROR (INK_START_ERRNO - 1) + +enum ViaStringIndex_t { + // + // General information + VIA_CLIENT = 0, + VIA_CLIENT_REQUEST, + VIA_CACHE, + VIA_CACHE_RESULT, + VIA_SERVER, + VIA_SERVER_RESULT, + VIA_CACHE_FILL, + VIA_CACHE_FILL_ACTION, + VIA_PROXY, + VIA_PROXY_RESULT, + VIA_ERROR, + VIA_ERROR_TYPE, + // + // State Machine specific details + VIA_DETAIL_SEPARATOR, + VIA_DETAIL_TUNNEL_DESCRIPTOR, + VIA_DETAIL_TUNNEL, + VIA_DETAIL_CACHE_DESCRIPTOR, + VIA_DETAIL_CACHE_TYPE, + VIA_DETAIL_CACHE_LOOKUP, + VIA_DETAIL_ICP_DESCRIPTOR, + VIA_DETAIL_ICP_CONNECT, + VIA_DETAIL_PP_DESCRIPTOR, + VIA_DETAIL_PP_CONNECT, + VIA_DETAIL_SERVER_DESCRIPTOR, + VIA_DETAIL_SERVER_CONNECT, + // + // Total + MAX_VIA_INDICES +}; + +enum ViaString_t { + // client stuff + VIA_CLIENT_STRING = 'u', + VIA_CLIENT_ERROR = 'E', + VIA_CLIENT_IMS = 'I', + VIA_CLIENT_NO_CACHE = 'N', + VIA_CLIENT_COOKIE = 'C', + VIA_CLIENT_SIMPLE = 'S', + // cache lookup stuff + VIA_CACHE_STRING = 'c', + VIA_CACHE_MISS = 'M', + VIA_IN_CACHE_NOT_ACCEPTABLE = 'A', + VIA_IN_CACHE_STALE = 'S', + VIA_IN_CACHE_FRESH = 'H', + VIA_IN_RAM_CACHE_FRESH = 'R', + // server stuff + VIA_SERVER_STRING = 's', + VIA_SERVER_ERROR = 'E', + VIA_SERVER_NOT_MODIFIED = 'N', + VIA_SERVER_SERVED = 'S', + // cache fill stuff + VIA_CACHE_FILL_STRING = 'f', + VIA_CACHE_DELETED = 'D', + VIA_CACHE_WRITTEN = 'W', + VIA_CACHE_UPDATED = 'U', + // proxy stuff + VIA_PROXY_STRING = 'p', + VIA_PROXY_NOT_MODIFIED = 'N', + VIA_PROXY_SERVED = 'S', + VIA_PROXY_SERVER_REVALIDATED = 'R', + // errors + VIA_ERROR_STRING = 'e', + VIA_ERROR_NO_ERROR = 'N', + VIA_ERROR_AUTHORIZATION = 'A', + VIA_ERROR_CONNECTION = 'C', + VIA_ERROR_DNS_FAILURE = 'D', + VIA_ERROR_FORBIDDEN = 'F', + VIA_ERROR_HEADER_SYNTAX = 'H', + VIA_ERROR_SERVER = 'S', + VIA_ERROR_TIMEOUT = 'T', + VIA_ERROR_CACHE_READ = 'R', + VIA_ERROR_MOVED_TEMPORARILY = 'M', + // + // Now the detailed stuff + // + VIA_DETAIL_SEPARATOR_STRING = ':', + // tunnelling + VIA_DETAIL_TUNNEL_DESCRIPTOR_STRING = 't', + VIA_DETAIL_TUNNEL_HEADER_FIELD = 'F', + VIA_DETAIL_TUNNEL_METHOD = 'M', + VIA_DETAIL_TUNNEL_CACHE_OFF = 'O', + VIA_DETAIL_TUNNEL_URL = 'U', + VIA_DETAIL_TUNNEL_NO_FORWARD = 'N', + VIA_DETAIL_TUNNEL_AUTHORIZATION = 'A', + // cache type + VIA_DETAIL_CACHE_DESCRIPTOR_STRING = 'c', + VIA_DETAIL_CACHE = 'C', + VIA_DETAIL_CLUSTER = 'L', + VIA_DETAIL_ICP = 'I', + VIA_DETAIL_PARENT = 'P', + VIA_DETAIL_SERVER = 'S', + // result of cache lookup + VIA_DETAIL_HIT_CONDITIONAL = 'N', + VIA_DETAIL_HIT_SERVED = 'H', + VIA_DETAIL_MISS_CONDITIONAL = 'I', + VIA_DETAIL_MISS_NOT_CACHED = 'M', + VIA_DETAIL_MISS_EXPIRED = 'S', + VIA_DETAIL_MISS_CONFIG = 'C', + VIA_DETAIL_MISS_CLIENT = 'U', + VIA_DETAIL_MISS_METHOD = 'D', + VIA_DETAIL_MISS_COOKIE = 'K', + // result of icp suggested host lookup + VIA_DETAIL_ICP_DESCRIPTOR_STRING = 'i', + VIA_DETAIL_ICP_SUCCESS = 'S', + VIA_DETAIL_ICP_FAILURE = 'F', + // result of pp suggested host lookup + VIA_DETAIL_PP_DESCRIPTOR_STRING = 'p', + VIA_DETAIL_PP_SUCCESS = 'S', + VIA_DETAIL_PP_FAILURE = 'F', + // result of server suggested host lookup + VIA_DETAIL_SERVER_DESCRIPTOR_STRING = 's', + VIA_DETAIL_SERVER_SUCCESS = 'S', + VIA_DETAIL_SERVER_FAILURE = 'F' +}; + +struct HttpApiInfo { + char *parent_proxy_name; + int parent_proxy_port; + bool cache_untransformed; + bool cache_transformed; + bool logging_enabled; + bool retry_intercept_failures; + + HttpApiInfo() + : parent_proxy_name(NULL), + parent_proxy_port(-1), + cache_untransformed(false), + cache_transformed(true), + logging_enabled(true), + retry_intercept_failures(false) + { + } +}; + +enum { + HTTP_UNDEFINED_CL = -1, +}; + +////////////////////////////////////////////////////////////////////////////// +// +// HttpTransact +// +// The HttpTransact class is purely used for scoping and should +// not be instantiated. +// +////////////////////////////////////////////////////////////////////////////// +#define SET_VIA_STRING(I, S) s->via_string[I] = S; +#define GET_VIA_STRING(I) (s->via_string[I]) + +class HttpTransact +{ +public: + enum UrlRemapMode_t { + URL_REMAP_DEFAULT = 0, // which is the same as URL_REMAP_ALL + URL_REMAP_ALL, + URL_REMAP_FOR_OS + }; + + enum AbortState_t { + ABORT_UNDEFINED = 0, + DIDNOT_ABORT, + MAYBE_ABORTED, + ABORTED, + }; + + enum Authentication_t { + AUTHENTICATION_SUCCESS = 0, + AUTHENTICATION_MUST_REVALIDATE, + AUTHENTICATION_MUST_PROXY, + AUTHENTICATION_CACHE_AUTH + }; + + enum CacheAction_t { + CACHE_DO_UNDEFINED = 0, + CACHE_DO_NO_ACTION, + CACHE_DO_DELETE, + CACHE_DO_LOOKUP, + CACHE_DO_REPLACE, + CACHE_DO_SERVE, + CACHE_DO_SERVE_AND_DELETE, + CACHE_DO_SERVE_AND_UPDATE, + CACHE_DO_UPDATE, + CACHE_DO_WRITE, + CACHE_PREPARE_TO_DELETE, + CACHE_PREPARE_TO_UPDATE, + CACHE_PREPARE_TO_WRITE, + TOTAL_CACHE_ACTION_TYPES + }; + + enum CacheOpenWriteFailAction_t { + CACHE_WL_FAIL_ACTION_DEFAULT = 0x00, + CACHE_WL_FAIL_ACTION_ERROR_ON_MISS = 0x01, + CACHE_WL_FAIL_ACTION_STALE_ON_REVALIDATE = 0x02, + CACHE_WL_FAIL_ACTION_ERROR_ON_MISS_STALE_ON_REVALIDATE = 0x03, + CACHE_WL_FAIL_ACTION_ERROR_ON_MISS_OR_REVALIDATE = 0x04, + TOTAL_CACHE_WL_FAIL_ACTION_TYPES + }; + + enum CacheWriteLock_t { + CACHE_WL_INIT, + CACHE_WL_SUCCESS, + CACHE_WL_FAIL, + CACHE_WL_READ_RETRY, + }; + + enum ClientTransactionResult_t { + CLIENT_TRANSACTION_RESULT_UNDEFINED, + CLIENT_TRANSACTION_RESULT_HIT_FRESH, + CLIENT_TRANSACTION_RESULT_HIT_REVALIDATED, + CLIENT_TRANSACTION_RESULT_MISS_COLD, + CLIENT_TRANSACTION_RESULT_MISS_CHANGED, + CLIENT_TRANSACTION_RESULT_MISS_CLIENT_NO_CACHE, + CLIENT_TRANSACTION_RESULT_MISS_UNCACHABLE, + CLIENT_TRANSACTION_RESULT_ERROR_ABORT, + CLIENT_TRANSACTION_RESULT_ERROR_POSSIBLE_ABORT, + CLIENT_TRANSACTION_RESULT_ERROR_CONNECT_FAIL, + CLIENT_TRANSACTION_RESULT_ERROR_OTHER + }; + + enum Freshness_t { + FRESHNESS_FRESH = 0, // Fresh enough, serve it + FRESHNESS_WARNING, // Stale, but client says OK + FRESHNESS_STALE // Stale, don't use + }; + + enum HostNameExpansionError_t { + RETRY_EXPANDED_NAME, + EXPANSION_FAILED, + EXPANSION_NOT_ALLOWED, + DNS_ATTEMPTS_EXHAUSTED, + NO_PARENT_PROXY_EXPANSION, + TOTAL_HOST_NAME_EXPANSION_TYPES + }; + + enum HttpTransactMagic_t { + HTTP_TRANSACT_MAGIC_ALIVE = 0x00001234, + HTTP_TRANSACT_MAGIC_DEAD = 0xDEAD1234, + HTTP_TRANSACT_MAGIC_SEPARATOR = 0x12345678 + }; + + enum ParentOriginRetry_t { + PARENT_ORIGIN_UNDEFINED_RETRY = 0x0, + PARENT_ORIGIN_SIMPLE_RETRY = 0x1, + PARENT_ORIGIN_UNAVAILABLE_SERVER_RETRY = 0x2 + }; + + enum LookingUp_t { + ORIGIN_SERVER, + UNDEFINED_LOOKUP, + ICP_SUGGESTED_HOST, + PARENT_PROXY, + INCOMING_ROUTER, + HOST_NONE, + }; + + enum ProxyMode_t { + UNDEFINED_MODE, + GENERIC_PROXY, + TUNNELLING_PROXY, + }; + + enum RequestError_t { + NO_REQUEST_HEADER_ERROR, + BAD_HTTP_HEADER_SYNTAX, + BAD_CONNECT_PORT, + FAILED_PROXY_AUTHORIZATION, + METHOD_NOT_SUPPORTED, + MISSING_HOST_FIELD, + NO_POST_CONTENT_LENGTH, + NO_REQUEST_SCHEME, + NON_EXISTANT_REQUEST_HEADER, + SCHEME_NOT_SUPPORTED, + UNACCEPTABLE_TE_REQUIRED, + INVALID_POST_CONTENT_LENGTH, + TOTAL_REQUEST_ERROR_TYPES + }; + + enum ResponseError_t { + NO_RESPONSE_HEADER_ERROR, + BOGUS_OR_NO_DATE_IN_RESPONSE, + CONNECTION_OPEN_FAILED, + MISSING_REASON_PHRASE, + MISSING_STATUS_CODE, + NON_EXISTANT_RESPONSE_HEADER, + NOT_A_RESPONSE_HEADER, + STATUS_CODE_SERVER_ERROR, + TOTAL_RESPONSE_ERROR_TYPES + }; + + // Please do not forget to fix TSServerState (ts/ts.h) + // in case of any modifications in ServerState_t + enum ServerState_t { + STATE_UNDEFINED = 0, + ACTIVE_TIMEOUT, + BAD_INCOMING_RESPONSE, + CONNECTION_ALIVE, + CONNECTION_CLOSED, + CONNECTION_ERROR, + INACTIVE_TIMEOUT, + OPEN_RAW_ERROR, + PARSE_ERROR, + TRANSACTION_COMPLETE, + CONGEST_CONTROL_CONGESTED_ON_F, + CONGEST_CONTROL_CONGESTED_ON_M, + PARENT_ORIGIN_RETRY + }; + + enum CacheWriteStatus_t { + NO_CACHE_WRITE = 0, + CACHE_WRITE_LOCK_MISS, + CACHE_WRITE_IN_PROGRESS, + CACHE_WRITE_ERROR, + CACHE_WRITE_COMPLETE + }; + + enum HttpRequestFlavor_t { + REQ_FLAVOR_INTERCEPTED = 0, + REQ_FLAVOR_REVPROXY = 1, + REQ_FLAVOR_FWDPROXY = 2, + REQ_FLAVOR_SCHEDULED_UPDATE = 3 + }; + + //////////// + // source // + //////////// + enum Source_t { + SOURCE_NONE = 0, + SOURCE_HTTP_ORIGIN_SERVER, + SOURCE_RAW_ORIGIN_SERVER, + SOURCE_CACHE, + SOURCE_TRANSFORM, + SOURCE_INTERNAL // generated from text buffer + }; + + //////////////////////////////////////////////// + // HttpTransact fills a StateMachineAction_t // + // to tell the state machine what to do next. // + //////////////////////////////////////////////// + enum StateMachineAction_t { + SM_ACTION_UNDEFINED = 0, + + // SM_ACTION_AUTH_LOOKUP, + SM_ACTION_DNS_LOOKUP, + SM_ACTION_DNS_REVERSE_LOOKUP, + + SM_ACTION_CACHE_LOOKUP, + SM_ACTION_CACHE_ISSUE_WRITE, + SM_ACTION_CACHE_ISSUE_WRITE_TRANSFORM, + SM_ACTION_CACHE_PREPARE_UPDATE, + SM_ACTION_CACHE_ISSUE_UPDATE, + + SM_ACTION_ICP_QUERY, + + SM_ACTION_ORIGIN_SERVER_OPEN, + SM_ACTION_ORIGIN_SERVER_RAW_OPEN, + SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN, + + SM_ACTION_READ_PUSH_HDR, + SM_ACTION_STORE_PUSH_BODY, + + SM_ACTION_INTERNAL_CACHE_DELETE, + SM_ACTION_INTERNAL_CACHE_NOOP, + SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS, + SM_ACTION_INTERNAL_CACHE_WRITE, + SM_ACTION_INTERNAL_100_RESPONSE, + SM_ACTION_INTERNAL_REQUEST, + SM_ACTION_SEND_ERROR_CACHE_NOOP, + +#ifdef PROXY_DRAIN + SM_ACTION_DRAIN_REQUEST_BODY, +#endif /* PROXY_DRAIN */ + + SM_ACTION_SERVE_FROM_CACHE, + SM_ACTION_SERVER_READ, + SM_ACTION_SERVER_PARSE_NEXT_HDR, + SM_ACTION_TRANSFORM_READ, + SM_ACTION_SSL_TUNNEL, + SM_ACTION_CONTINUE, + + SM_ACTION_API_SM_START, + SM_ACTION_API_READ_REQUEST_HDR, + SM_ACTION_API_PRE_REMAP, + SM_ACTION_API_POST_REMAP, + SM_ACTION_API_OS_DNS, + SM_ACTION_API_SEND_REQUEST_HDR, + SM_ACTION_API_READ_CACHE_HDR, + SM_ACTION_API_CACHE_LOOKUP_COMPLETE, + SM_ACTION_API_READ_RESPONSE_HDR, + SM_ACTION_API_SEND_RESPONSE_HDR, + SM_ACTION_API_SM_SHUTDOWN, + + SM_ACTION_REMAP_REQUEST, + SM_ACTION_POST_REMAP_SKIP, + SM_ACTION_REDIRECT_READ + }; + + enum TransferEncoding_t { + NO_TRANSFER_ENCODING = 0, + CHUNKED_ENCODING, + DEFLATE_ENCODING, + }; + + enum Variability_t { + VARIABILITY_NONE = 0, + VARIABILITY_SOME, + VARIABILITY_ALL, + }; + + struct StatRecord_t { + uint16_t index; + int64_t increment; + }; + + enum CacheLookupResult_t { + CACHE_LOOKUP_NONE, + CACHE_LOOKUP_MISS, + CACHE_LOOKUP_DOC_BUSY, + CACHE_LOOKUP_HIT_STALE, + CACHE_LOOKUP_HIT_WARNING, + CACHE_LOOKUP_HIT_FRESH, + CACHE_LOOKUP_SKIPPED + }; + + enum UpdateCachedObject_t { + UPDATE_CACHED_OBJECT_NONE, + UPDATE_CACHED_OBJECT_PREPARE, + UPDATE_CACHED_OBJECT_CONTINUE, + UPDATE_CACHED_OBJECT_ERROR, + UPDATE_CACHED_OBJECT_SUCCEED, + UPDATE_CACHED_OBJECT_FAIL + }; + + enum LockUrl_t { + LOCK_URL_FIRST = 0, + LOCK_URL_SECOND, + LOCK_URL_ORIGINAL, + LOCK_URL_DONE, + LOCK_URL_QUIT, + }; + + enum RangeSetup_t { + RANGE_NONE = 0, + RANGE_REQUESTED, + RANGE_NOT_SATISFIABLE, + RANGE_NOT_HANDLED, + RANGE_NOT_TRANSFORM_REQUESTED, + }; + + enum CacheAuth_t { + CACHE_AUTH_NONE = 0, + // CACHE_AUTH_TRUE, + CACHE_AUTH_FRESH, + CACHE_AUTH_STALE, + CACHE_AUTH_SERVE + }; + + struct State; + typedef void (*TransactFunc_t)(HttpTransact::State *); + + typedef struct _CacheDirectives { + bool does_client_permit_lookup; + bool does_client_permit_storing; + bool does_client_permit_dns_storing; + bool does_config_permit_lookup; + bool does_config_permit_storing; + bool does_server_permit_lookup; + bool does_server_permit_storing; + + _CacheDirectives() + : does_client_permit_lookup(true), + does_client_permit_storing(true), + does_client_permit_dns_storing(true), + does_config_permit_lookup(true), + does_config_permit_storing(true), + does_server_permit_lookup(true), + does_server_permit_storing(true) + { + } + } CacheDirectives; + + typedef struct _CacheLookupInfo { + HttpTransact::CacheAction_t action; + HttpTransact::CacheAction_t transform_action; + + HttpTransact::CacheWriteStatus_t write_status; + HttpTransact::CacheWriteStatus_t transform_write_status; + + URL *lookup_url; + URL lookup_url_storage; + URL original_url; + HTTPInfo *object_read; + HTTPInfo *second_object_read; + HTTPInfo object_store; + HTTPInfo transform_store; + CacheLookupHttpConfig config; + CacheDirectives directives; + int open_read_retries; + int open_write_retries; + CacheWriteLock_t write_lock_state; + int lookup_count; + SquidHitMissCode hit_miss_code; + + _CacheLookupInfo() + : action(CACHE_DO_UNDEFINED), + transform_action(CACHE_DO_UNDEFINED), + write_status(NO_CACHE_WRITE), + transform_write_status(NO_CACHE_WRITE), + lookup_url(NULL), + lookup_url_storage(), + original_url(), + object_read(NULL), + second_object_read(NULL), + object_store(), + transform_store(), + config(), + directives(), + open_read_retries(0), + open_write_retries(0), + write_lock_state(CACHE_WL_INIT), + lookup_count(0), + hit_miss_code(SQUID_MISS_NONE) + { + } + } CacheLookupInfo; + + typedef struct _RedirectInfo { + bool redirect_in_process; + URL original_url; + URL redirect_url; + + _RedirectInfo() : redirect_in_process(false), original_url(), redirect_url() {} + } RedirectInfo; + + struct ConnectionAttributes { + HTTPVersion http_version; + HTTPKeepAlive keep_alive; + + // The following variable is true if the client expects to + // received a chunked response. + bool receive_chunked_response; + bool pipeline_possible; + bool proxy_connect_hdr; + /// @c errno from the most recent attempt to connect. + /// zero means no failure (not attempted, succeeded). + int connect_result; + char *name; + TransferEncoding_t transfer_encoding; + + /** This is the source address of the connection from the point of view of the transaction. + It is the address of the source of the request. + */ + IpEndpoint src_addr; + /** This is the destination address of the connection from the point of view of the transaction. + It is the address of the target of the request. + */ + IpEndpoint dst_addr; + + ServerState_t state; + AbortState_t abort; + HttpProxyPort::TransportType port_attribute; + + /// @c true if the connection is transparent. + bool is_transparent; + + bool + had_connect_fail() const + { + return 0 != connect_result; + } + void + clear_connect_fail() + { + connect_result = 0; + } + void + set_connect_fail(int e) + { + connect_result = e; + } + + ConnectionAttributes() + : http_version(), + keep_alive(HTTP_KEEPALIVE_UNDEFINED), + receive_chunked_response(false), + pipeline_possible(false), + proxy_connect_hdr(false), + connect_result(0), + name(NULL), + transfer_encoding(NO_TRANSFER_ENCODING), + state(STATE_UNDEFINED), + abort(ABORT_UNDEFINED), + port_attribute(HttpProxyPort::TRANSPORT_DEFAULT), + is_transparent(false) + { + memset(&src_addr, 0, sizeof(src_addr)); + memset(&dst_addr, 0, sizeof(dst_addr)); + } + }; + + typedef struct _CurrentInfo { + ProxyMode_t mode; + LookingUp_t request_to; + ConnectionAttributes *server; + ink_time_t now; + ServerState_t state; + unsigned attempts; + unsigned simple_retry_attempts; + unsigned unavailable_server_retry_attempts; + ParentOriginRetry_t retry_type; + + _CurrentInfo() + : mode(UNDEFINED_MODE), + request_to(UNDEFINED_LOOKUP), + server(NULL), + now(0), + state(STATE_UNDEFINED), + attempts(1), + simple_retry_attempts(0), + unavailable_server_retry_attempts(0), + retry_type(PARENT_ORIGIN_UNDEFINED_RETRY){}; + } CurrentInfo; + + typedef struct _DNSLookupInfo { + int attempts; + /** Origin server address source selection. + + If config says to use CTA (client target addr) state is + OS_ADDR_TRY_CLIENT, otherwise it remains the default. If the + connect fails then we switch to a USE. We go to USE_HOSTDB if + (1) the HostDB lookup is successful and (2) some address other + than the CTA is available to try. Otherwise we keep retrying + on the CTA (USE_CLIENT) up to the max retry value. In essence + we try to treat the CTA as if it were another RR value in the + HostDB record. + */ + enum { + OS_ADDR_TRY_DEFAULT, ///< Initial state, use what config says. + OS_ADDR_TRY_HOSTDB, ///< Try HostDB data. + OS_ADDR_TRY_CLIENT, ///< Try client target addr. + OS_ADDR_USE_HOSTDB, ///< Force use of HostDB target address. + OS_ADDR_USE_CLIENT ///< Use client target addr, no fallback. + } os_addr_style; + + bool lookup_success; + char *lookup_name; + char srv_hostname[MAXDNAME]; + LookingUp_t looking_up; + bool srv_lookup_success; + short srv_port; + HostDBApplicationInfo srv_app; + /*** Set to true by default. If use_client_target_address is set + * to 1, this value will be set to false if the client address is + * not in the DNS pool */ + bool lookup_validated; + + _DNSLookupInfo() + : attempts(0), + os_addr_style(OS_ADDR_TRY_DEFAULT), + lookup_success(false), + lookup_name(NULL), + looking_up(UNDEFINED_LOOKUP), + srv_lookup_success(false), + srv_port(0), + lookup_validated(true) + { + srv_hostname[0] = '\0'; + srv_app.allotment.application1 = 0; + srv_app.allotment.application2 = 0; + } + } DNSLookupInfo; + + typedef struct _HeaderInfo { + HTTPHdr client_request; + HTTPHdr client_response; + HTTPHdr server_request; + HTTPHdr server_response; + HTTPHdr transform_response; + HTTPHdr cache_response; + int64_t request_content_length; + int64_t response_content_length; + int64_t transform_request_cl; + int64_t transform_response_cl; + bool client_req_is_server_style; + bool trust_response_cl; + ResponseError_t response_error; + bool extension_method; + + _HeaderInfo() + : client_request(), + client_response(), + server_request(), + server_response(), + transform_response(), + cache_response(), + request_content_length(HTTP_UNDEFINED_CL), + response_content_length(HTTP_UNDEFINED_CL), + transform_request_cl(HTTP_UNDEFINED_CL), + transform_response_cl(HTTP_UNDEFINED_CL), + client_req_is_server_style(false), + trust_response_cl(false), + response_error(NO_RESPONSE_HEADER_ERROR), + extension_method(false) + { + } + } HeaderInfo; + + typedef struct _SquidLogInfo { + SquidLogCode log_code; + SquidHierarchyCode hier_code; + SquidHitMissCode hit_miss_code; + + _SquidLogInfo() : log_code(SQUID_LOG_ERR_UNKNOWN), hier_code(SQUID_HIER_EMPTY), hit_miss_code(SQUID_MISS_NONE) {} + } SquidLogInfo; + +#define HTTP_TRANSACT_STATE_MAX_XBUF_SIZE (1024 * 2) /* max size of plugin exchange buffer */ + + struct State { + HttpTransactMagic_t m_magic; + + HttpSM *state_machine; + + Arena arena; + + HttpConfigParams *http_config_param; + CacheLookupInfo cache_info; + DNSLookupInfo dns_info; + bool force_dns; + RedirectInfo redirect_info; + unsigned int updated_server_version; + unsigned int cache_open_write_fail_action; + bool is_revalidation_necessary; // Added to check if revalidation is necessary - YTS Team, yamsat + bool request_will_not_selfloop; // To determine if process done - YTS Team, yamsat + ConnectionAttributes client_info; + ConnectionAttributes icp_info; + ConnectionAttributes parent_info; + ConnectionAttributes server_info; + // ConnectionAttributes router_info; + + Source_t source; + Source_t pre_transform_source; + HttpRequestFlavor_t req_flavor; + + CurrentInfo current; + HeaderInfo hdr_info; + SquidLogInfo squid_codes; + HttpApiInfo api_info; + // To handle parent proxy case, we need to be + // able to defer some work in building the request + TransactFunc_t pending_work; + + // Sandbox of Variables + StateMachineAction_t cdn_saved_next_action; + void (*cdn_saved_transact_return_point)(State *s); + bool cdn_remap_complete; + bool first_dns_lookup; + + bool backdoor_request; // internal + bool cop_test_page; // internal + HttpRequestData request_data; + ParentConfigParams *parent_params; + ParentResult parent_result; + CacheControlResult cache_control; + CacheLookupResult_t cache_lookup_result; + // FilterResult content_control; + + StateMachineAction_t next_action; // out + StateMachineAction_t api_next_action; // out + void (*transact_return_point)(HttpTransact::State *s); // out + + // We keep this so we can jump back to the upgrade handler after remap is complete + void (*post_remap_upgrade_return_point)(HttpTransact::State *s); // out + const char *upgrade_token_wks; + bool is_upgrade_request; + + // Some WebSocket state + bool is_websocket; + bool did_upgrade_succeed; + + // Some queue info + bool origin_request_queued; + + char *internal_msg_buffer; // out + char *internal_msg_buffer_type; // out + int64_t internal_msg_buffer_size; // out + int64_t internal_msg_buffer_fast_allocator_size; + + struct sockaddr_in icp_ip_result; // in + bool icp_lookup_success; // in + + int scheme; // out + int next_hop_scheme; // out + int orig_scheme; // pre-mapped scheme + int method; + int cause_of_death_errno; // in + HostDBInfo host_db_info; // in + + ink_time_t client_request_time; // internal + ink_time_t request_sent_time; // internal + ink_time_t response_received_time; // internal + ink_time_t plugin_set_expire_time; + + char via_string[MAX_VIA_INDICES + 1]; + + int64_t state_machine_id; + + // HttpAuthParams auth_params; + + // new ACL filtering result (calculated immediately after remap) + bool client_connection_enabled; + bool acl_filtering_performed; + + // for negative caching + bool negative_caching; + // for srv_lookup + bool srv_lookup; + // for authenticated content caching + CacheAuth_t www_auth_content; + + // INK API/Remap API plugin interface + void *remap_plugin_instance; + void *user_args[HTTP_SSN_TXN_MAX_USER_ARG]; + remap_plugin_info::_tsremap_os_response *fp_tsremap_os_response; + HTTPStatus http_return_code; + + int api_txn_active_timeout_value; + int api_txn_connect_timeout_value; + int api_txn_dns_timeout_value; + int api_txn_no_activity_timeout_value; + + // Used by INKHttpTxnCachedReqGet and INKHttpTxnCachedRespGet SDK functions + // to copy part of HdrHeap (only the writable portion) for cached response headers + // and request headers + // These ptrs are deallocate when transaction is over. + HdrHeapSDKHandle *cache_req_hdr_heap_handle; + HdrHeapSDKHandle *cache_resp_hdr_heap_handle; + bool api_cleanup_cache_read; + bool api_server_response_no_store; + bool api_server_response_ignore; + bool api_http_sm_shutdown; + bool api_modifiable_cached_resp; + bool api_server_request_body_set; + bool api_req_cacheable; + bool api_resp_cacheable; + bool api_server_addr_set; + bool stale_icp_lookup; + UpdateCachedObject_t api_update_cached_object; + LockUrl_t api_lock_url; + StateMachineAction_t saved_update_next_action; + CacheAction_t saved_update_cache_action; + + // Remap plugin processor support + UrlMappingContainer url_map; + host_hdr_info hh_info; + + // congestion control + CongestionEntry *pCongestionEntry; + StateMachineAction_t congest_saved_next_action; + int congestion_control_crat; // 'client retry after' + int congestion_congested_or_failed; + int congestion_connection_opened; + + unsigned int filter_mask; + char *remap_redirect; + bool reverse_proxy; + bool url_remap_success; + + bool api_skip_all_remapping; + + bool already_downgraded; + URL pristine_url; // pristine url is the url before remap + + // Http Range: related variables + RangeSetup_t range_setup; + int64_t num_range_fields; + int64_t range_output_cl; + RangeRecord *ranges; + + OverridableHttpConfigParams *txn_conf; + OverridableHttpConfigParams my_txn_conf; // Storage for plugins, to avoid malloc + + bool transparent_passthrough; + bool range_in_cache; + + // Methods + void + init() + { + parent_params = ParentConfig::acquire(); + } + + // Constructor + State() + : m_magic(HTTP_TRANSACT_MAGIC_ALIVE), + state_machine(NULL), + http_config_param(NULL), + force_dns(false), + updated_server_version(HostDBApplicationInfo::HTTP_VERSION_UNDEFINED), + cache_open_write_fail_action(0), + is_revalidation_necessary(false), + request_will_not_selfloop(false), // YTS Team, yamsat + source(SOURCE_NONE), + pre_transform_source(SOURCE_NONE), + req_flavor(REQ_FLAVOR_FWDPROXY), + pending_work(NULL), + cdn_saved_next_action(SM_ACTION_UNDEFINED), + cdn_saved_transact_return_point(NULL), + cdn_remap_complete(false), + first_dns_lookup(true), + backdoor_request(false), + cop_test_page(false), + parent_params(NULL), + cache_lookup_result(CACHE_LOOKUP_NONE), + next_action(SM_ACTION_UNDEFINED), + api_next_action(SM_ACTION_UNDEFINED), + transact_return_point(NULL), + post_remap_upgrade_return_point(NULL), + upgrade_token_wks(NULL), + is_upgrade_request(false), + is_websocket(false), + did_upgrade_succeed(false), + origin_request_queued(false), + internal_msg_buffer(NULL), + internal_msg_buffer_type(NULL), + internal_msg_buffer_size(0), + internal_msg_buffer_fast_allocator_size(-1), + icp_lookup_success(false), + scheme(-1), + next_hop_scheme(scheme), + orig_scheme(scheme), + method(0), + cause_of_death_errno(-UNKNOWN_INTERNAL_ERROR), + client_request_time(UNDEFINED_TIME), + request_sent_time(UNDEFINED_TIME), + response_received_time(UNDEFINED_TIME), + plugin_set_expire_time(UNDEFINED_TIME), + state_machine_id(0), + client_connection_enabled(true), + acl_filtering_performed(false), + negative_caching(false), + srv_lookup(false), + www_auth_content(CACHE_AUTH_NONE), + remap_plugin_instance(0), + fp_tsremap_os_response(NULL), + http_return_code(HTTP_STATUS_NONE), + api_txn_active_timeout_value(-1), + api_txn_connect_timeout_value(-1), + api_txn_dns_timeout_value(-1), + api_txn_no_activity_timeout_value(-1), + cache_req_hdr_heap_handle(NULL), + cache_resp_hdr_heap_handle(NULL), + api_cleanup_cache_read(false), + api_server_response_no_store(false), + api_server_response_ignore(false), + api_http_sm_shutdown(false), + api_modifiable_cached_resp(false), + api_server_request_body_set(false), + api_req_cacheable(false), + api_resp_cacheable(false), + api_server_addr_set(false), + stale_icp_lookup(false), + api_update_cached_object(UPDATE_CACHED_OBJECT_NONE), + api_lock_url(LOCK_URL_FIRST), + saved_update_next_action(SM_ACTION_UNDEFINED), + saved_update_cache_action(CACHE_DO_UNDEFINED), + url_map(), + pCongestionEntry(NULL), + congest_saved_next_action(SM_ACTION_UNDEFINED), + congestion_control_crat(0), + congestion_congested_or_failed(0), + congestion_connection_opened(0), + filter_mask(0), + remap_redirect(NULL), + reverse_proxy(false), + url_remap_success(false), + api_skip_all_remapping(false), + already_downgraded(false), + pristine_url(), + range_setup(RANGE_NONE), + num_range_fields(0), + range_output_cl(0), + ranges(NULL), + txn_conf(NULL), + transparent_passthrough(false), + range_in_cache(false) + { + int i; + char *via_ptr = via_string; + + for (i = 0; i < MAX_VIA_INDICES; i++) { + *via_ptr++ = ' '; + } + + via_string[VIA_CLIENT] = VIA_CLIENT_STRING; + via_string[VIA_CACHE] = VIA_CACHE_STRING; + via_string[VIA_SERVER] = VIA_SERVER_STRING; + via_string[VIA_CACHE_FILL] = VIA_CACHE_FILL_STRING; + via_string[VIA_PROXY] = VIA_PROXY_STRING; + via_string[VIA_ERROR] = VIA_ERROR_STRING; + via_string[VIA_ERROR_TYPE] = VIA_ERROR_NO_ERROR; + via_string[VIA_DETAIL_SEPARATOR] = VIA_DETAIL_SEPARATOR_STRING; + via_string[VIA_DETAIL_TUNNEL_DESCRIPTOR] = VIA_DETAIL_TUNNEL_DESCRIPTOR_STRING; + via_string[VIA_DETAIL_CACHE_DESCRIPTOR] = VIA_DETAIL_CACHE_DESCRIPTOR_STRING; + via_string[VIA_DETAIL_ICP_DESCRIPTOR] = VIA_DETAIL_ICP_DESCRIPTOR_STRING; + via_string[VIA_DETAIL_PP_DESCRIPTOR] = VIA_DETAIL_PP_DESCRIPTOR_STRING; + via_string[VIA_DETAIL_SERVER_DESCRIPTOR] = VIA_DETAIL_SERVER_DESCRIPTOR_STRING; + via_string[MAX_VIA_INDICES] = '\0'; + + memset(user_args, 0, sizeof(user_args)); + memset(&host_db_info, 0, sizeof(host_db_info)); + } + + void + destroy() + { + m_magic = HTTP_TRANSACT_MAGIC_DEAD; + + free_internal_msg_buffer(); + ats_free(internal_msg_buffer_type); + + ParentConfig::release(parent_params); + parent_params = NULL; + + hdr_info.client_request.destroy(); + hdr_info.client_response.destroy(); + hdr_info.server_request.destroy(); + hdr_info.server_response.destroy(); + hdr_info.transform_response.destroy(); + hdr_info.cache_response.destroy(); + cache_info.lookup_url_storage.destroy(); + cache_info.original_url.destroy(); + cache_info.object_store.destroy(); + cache_info.transform_store.destroy(); + redirect_info.original_url.destroy(); + redirect_info.redirect_url.destroy(); + + if (pCongestionEntry) { + if (congestion_connection_opened == 1) { + pCongestionEntry->connection_closed(); + congestion_connection_opened = 0; + } + pCongestionEntry->put(), pCongestionEntry = NULL; + } + + url_map.clear(); + arena.reset(); + pristine_url.clear(); + + delete[] ranges; + ranges = NULL; + range_setup = RANGE_NONE; + return; + } + + // Little helper function to setup the per-transaction configuration copy + void + setup_per_txn_configs() + { + if (txn_conf != &my_txn_conf) { + // Make sure we copy it first. + memcpy(&my_txn_conf, &http_config_param->oride, sizeof(my_txn_conf)); + txn_conf = &my_txn_conf; + } + } + + void + free_internal_msg_buffer() + { + if (internal_msg_buffer) { + if (internal_msg_buffer_fast_allocator_size >= 0) { + ioBufAllocator[internal_msg_buffer_fast_allocator_size].free_void(internal_msg_buffer); + } else { + ats_free(internal_msg_buffer); + } + internal_msg_buffer = NULL; + } + internal_msg_buffer_size = 0; + } + }; // End of State struct. + + static void HandleBlindTunnel(State *s); + static void StartRemapRequest(State *s); + static void RemapRequest(State *s); + static void EndRemapRequest(State *s); + static void PerformRemap(State *s); + static void ModifyRequest(State *s); + static void HandleRequest(State *s); + static bool handleIfRedirect(State *s); + + static void StartAccessControl(State *s); + static void StartAuth(State *s); + static void HandleRequestAuthorized(State *s); + static void BadRequest(State *s); + static void HandleFiltering(State *s); + static void DecideCacheLookup(State *s); + static void LookupSkipOpenServer(State *s); + + static void CallOSDNSLookup(State *s); + static void OSDNSLookup(State *s); + static void ReDNSRoundRobin(State *s); + static void PPDNSLookup(State *s); + static void HandleAuth(State *s); + static void HandleAuthFailed(State *s); + static void OriginServerRawOpen(State *s); + static void HandleCacheOpenRead(State *s); + static void HandleCacheOpenReadHitFreshness(State *s); + static void HandleCacheOpenReadHit(State *s); + static void HandleCacheOpenReadMiss(State *s); + static void build_response_from_cache(State *s, HTTPWarningCode warning_code); + static void handle_cache_write_lock(State *s); + static void HandleICPLookup(State *s); + static void HandleResponse(State *s); + static void HandleUpdateCachedObject(State *s); + static void HandleUpdateCachedObjectContinue(State *s); + static void HandleStatPage(State *s); + static void handle_100_continue_response(State *s); + static void handle_transform_ready(State *s); + static void handle_transform_cache_write(State *s); + static void handle_response_from_icp_suggested_host(State *s); + static void handle_response_from_parent(State *s); + static void handle_response_from_server(State *s); + static void delete_server_rr_entry(State *s, int max_retries); + static void retry_server_connection_not_open(State *s, ServerState_t conn_state, unsigned max_retries); + static void handle_server_connection_not_open(State *s); + static void handle_forward_server_connection_open(State *s); + static void handle_cache_operation_on_forward_server_response(State *s); + static void handle_no_cache_operation_on_forward_server_response(State *s); + static void merge_and_update_headers_for_cache_update(State *s); + static void set_headers_for_cache_write(State *s, HTTPInfo *cache_info, HTTPHdr *request, HTTPHdr *response); + static void set_header_for_transform(State *s, HTTPHdr *base_header); + static void merge_response_header_with_cached_header(HTTPHdr *cached_header, HTTPHdr *response_header); + static void merge_warning_header(HTTPHdr *cached_header, HTTPHdr *response_header); + static void SetCacheFreshnessLimit(State *s); + static void HandleApiErrorJump(State *); + static void handle_websocket_upgrade_pre_remap(State *s); + static void handle_websocket_upgrade_post_remap(State *s); + static bool handle_upgrade_request(State *s); + static void handle_websocket_connection(State *s); + + static void HandleCacheOpenReadPush(State *s, bool read_successful); + static void HandlePushResponseHdr(State *s); + static void HandlePushCacheWrite(State *s); + static void HandlePushTunnelSuccess(State *s); + static void HandlePushTunnelFailure(State *s); + static void HandlePushError(State *s, const char *reason); + static void HandleBadPushRespHdr(State *s); + + // Utility Methods + static void issue_revalidate(State *s); + static bool get_ka_info_from_config(State *s, ConnectionAttributes *server_info); + static void get_ka_info_from_host_db(State *s, ConnectionAttributes *server_info, ConnectionAttributes *client_info, + HostDBInfo *host_db_info); + static bool service_transaction_in_proxy_only_mode(State *s); + static void setup_plugin_request_intercept(State *s); + static void add_client_ip_to_outgoing_request(State *s, HTTPHdr *request); + static RequestError_t check_request_validity(State *s, HTTPHdr *incoming_hdr); + static ResponseError_t check_response_validity(State *s, HTTPHdr *incoming_hdr); + static bool delete_all_document_alternates_and_return(State *s, bool cache_hit); + static bool did_forward_server_send_0_9_response(State *s); + static bool does_client_request_permit_cached_response(const OverridableHttpConfigParams *p, CacheControlResult *c, HTTPHdr *h, + char *via_string); + static bool does_client_request_permit_dns_caching(CacheControlResult *c, HTTPHdr *h); + static bool does_client_request_permit_storing(CacheControlResult *c, HTTPHdr *h); + static bool handle_internal_request(State *s, HTTPHdr *incoming_hdr); + static bool handle_trace_and_options_requests(State *s, HTTPHdr *incoming_hdr); + static void bootstrap_state_variables_from_request(State *s, HTTPHdr *incoming_request); + static void initialize_state_variables_for_origin_server(State *s, HTTPHdr *incoming_request, bool second_time); + static void initialize_state_variables_from_request(State *s, HTTPHdr *obsolete_incoming_request); + static void initialize_state_variables_from_response(State *s, HTTPHdr *incoming_response); + static bool is_server_negative_cached(State *s); + static bool is_cache_response_returnable(State *s); + static bool is_stale_cache_response_returnable(State *s); + static bool need_to_revalidate(State *s); + static bool url_looks_dynamic(URL *url); + static bool is_request_cache_lookupable(State *s); + static bool is_request_valid(State *s, HTTPHdr *incoming_request); + static bool is_request_retryable(State *s); + + static bool is_response_cacheable(State *s, HTTPHdr *request, HTTPHdr *response); + static bool is_response_valid(State *s, HTTPHdr *incoming_response); + + static void process_quick_http_filter(State *s, int method); + static bool perform_accept_encoding_filtering(State *s); + + static HostNameExpansionError_t try_to_expand_host_name(State *s); + + static bool setup_auth_lookup(State *s); + static bool will_this_request_self_loop(State *s); + static bool is_request_likely_cacheable(State *s, HTTPHdr *request); + + static void build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_request, HTTPVersion outgoing_version); + static void build_response(State *s, HTTPHdr *base_response, HTTPHdr *outgoing_response, HTTPVersion outgoing_version, + HTTPStatus status_code, const char *reason_phrase = NULL); + static void build_response(State *s, HTTPHdr *base_response, HTTPHdr *outgoing_response, HTTPVersion outgoing_version); + static void build_response(State *s, HTTPHdr *outgoing_response, HTTPVersion outgoing_version, HTTPStatus status_code, + const char *reason_phrase = NULL); + + static void build_response_copy(State *s, HTTPHdr *base_response, HTTPHdr *outgoing_response, HTTPVersion outgoing_version); + static void handle_content_length_header(State *s, HTTPHdr *header, HTTPHdr *base); + static void change_response_header_because_of_range_request(State *s, HTTPHdr *header); + + static void handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPHdr *heads); + static void handle_response_keep_alive_headers(State *s, HTTPVersion ver, HTTPHdr *heads); + static int calculate_document_freshness_limit(State *s, HTTPHdr *response, time_t response_date, bool *heuristic); + static int calculate_freshness_fuzz(State *s, int fresh_limit); + static Freshness_t what_is_document_freshness(State *s, HTTPHdr *client_request, HTTPHdr *cached_obj_response); + static Authentication_t AuthenticationNeeded(const OverridableHttpConfigParams *p, HTTPHdr *client_request, + HTTPHdr *obj_response); + static void handle_parent_died(State *s); + static void handle_server_died(State *s); + static void build_error_response(State *s, HTTPStatus status_code, const char *reason_phrase_or_null, const char *error_body_type, + const char *format, ...); + static void build_redirect_response(State *s); + static void build_upgrade_response(State *s); + static const char *get_error_string(int erno); + + // the stat functions + static void update_size_and_time_stats(State *s, ink_hrtime total_time, ink_hrtime user_agent_write_time, + ink_hrtime origin_server_read_time, int user_agent_request_header_size, + int64_t user_agent_request_body_size, int user_agent_response_header_size, + int64_t user_agent_response_body_size, int origin_server_request_header_size, + int64_t origin_server_request_body_size, int origin_server_response_header_size, + int64_t origin_server_response_body_size, int pushed_response_header_size, + int64_t pushed_response_body_size, const TransactionMilestones &milestones); + static void histogram_request_document_size(State *s, int64_t size); + static void histogram_response_document_size(State *s, int64_t size); + static void user_agent_connection_speed(State *s, ink_hrtime transfer_time, int64_t nbytes); + static void origin_server_connection_speed(State *s, ink_hrtime transfer_time, int64_t nbytes); + static void client_result_stat(State *s, ink_hrtime total_time, ink_hrtime request_process_time); + static void delete_warning_value(HTTPHdr *to_warn, HTTPWarningCode warning_code); + static bool is_connection_collapse_checks_success(State *s); // YTS Team, yamsat +}; + +typedef void (*TransactEntryFunc_t)(HttpTransact::State *s); + +inline bool +is_response_body_precluded(HTTPStatus status_code, int method) +{ + //////////////////////////////////////////////////////// + // the spec says about message body the following: // + // All responses to the HEAD request method MUST NOT // + // include a message-body, even though the presence // + // of entity-header fields might lead one to believe // + // they do. All 1xx (informational), 204 (no content),// + // and 304 (not modified) responses MUST NOT include // + // a message-body. // + //////////////////////////////////////////////////////// + + if (((status_code != HTTP_STATUS_OK) && + ((status_code == HTTP_STATUS_NOT_MODIFIED) || ((status_code < HTTP_STATUS_OK) && (status_code >= HTTP_STATUS_CONTINUE)) || + (status_code == 204))) || + (method == HTTP_WKSIDX_HEAD)) { + return true; + } else { + return false; + } +} + +inkcoreapi extern ink_time_t ink_cluster_time(void); + +#endif diff --git a/proxy/http/remap/UrlRewrite.cc b/proxy/http/remap/UrlRewrite.cc index aab5918c5e5..915dd408e17 100644 --- a/proxy/http/remap/UrlRewrite.cc +++ b/proxy/http/remap/UrlRewrite.cc @@ -402,7 +402,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, url_mapping *map) bool match = true; if (rp->method_restriction_enabled) { - if (method_wksidx != -1) { + if (method_wksidx >= 0 && method_wksidx < HTTP_WKSIDX_METHODS_CNT) { match = rp->standard_method_lookup[method_wksidx]; } else if (!rp->nonstandard_methods.empty()) { int method_str_len; diff --git a/proxy/http/remap/UrlRewrite.cc.orig b/proxy/http/remap/UrlRewrite.cc.orig new file mode 100644 index 00000000000..aab5918c5e5 --- /dev/null +++ b/proxy/http/remap/UrlRewrite.cc.orig @@ -0,0 +1,947 @@ +/** @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. + */ + +#include "UrlRewrite.h" +#include "ProxyConfig.h" +#include "ReverseProxy.h" +#include "UrlMappingPathIndex.h" +#include "RemapConfig.h" +#include "ts/I_Layout.h" +#include "HttpSM.h" + +#define modulePrefix "[ReverseProxy]" + +/** + Determines where we are in a situation where a virtual path is + being mapped to a server home page. If it is, we set a special flag + instructing us to be on the lookout for the need to send a redirect + to if the request URL is a object, opposed to a directory. We need + the redirect for an object so that the browser is aware that it is + real accessing a directory (albeit a virtual one). + +*/ +static void +SetHomePageRedirectFlag(url_mapping *new_mapping, URL &new_to_url) +{ + int fromLen, toLen; + const char *from_path = new_mapping->fromURL.path_get(&fromLen); + const char *to_path = new_to_url.path_get(&toLen); + + new_mapping->homePageRedirect = (from_path && !to_path) ? true : false; +} + +// +// CTOR / DTOR for the UrlRewrite class. +// +UrlRewrite::UrlRewrite() + : nohost_rules(0), + reverse_proxy(0), + mgmt_synthetic_port(0), + ts_name(NULL), + http_default_redirect_url(NULL), + num_rules_forward(0), + num_rules_reverse(0), + num_rules_redirect_permanent(0), + num_rules_redirect_temporary(0), + num_rules_forward_with_recv_port(0), + _valid(false) +{ + ats_scoped_str config_file_path; + + forward_mappings.hash_lookup = reverse_mappings.hash_lookup = permanent_redirects.hash_lookup = temporary_redirects.hash_lookup = + forward_mappings_with_recv_port.hash_lookup = NULL; + + config_file_path = RecConfigReadConfigPath("proxy.config.url_remap.filename", "remap.config"); + if (!config_file_path) { + pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to find proxy.config.url_remap.filename"); + Warning("%s Unable to locate remap.config. No remappings in effect", modulePrefix); + return; + } + + this->ts_name = NULL; + REC_ReadConfigStringAlloc(this->ts_name, "proxy.config.proxy_name"); + if (this->ts_name == NULL) { + pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to read proxy.config.proxy_name"); + Warning("%s Unable to determine proxy name. Incorrect redirects could be generated", modulePrefix); + this->ts_name = ats_strdup(""); + } + + this->http_default_redirect_url = NULL; + REC_ReadConfigStringAlloc(this->http_default_redirect_url, "proxy.config.http.referer_default_redirect"); + if (this->http_default_redirect_url == NULL) { + pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, "Unable to read proxy.config.http.referer_default_redirect"); + Warning("%s Unable to determine default redirect url for \"referer\" filter.", modulePrefix); + this->http_default_redirect_url = ats_strdup("http://www.apache.org"); + } + + REC_ReadConfigInteger(reverse_proxy, "proxy.config.reverse_proxy.enabled"); + REC_ReadConfigInteger(mgmt_synthetic_port, "proxy.config.admin.synthetic_port"); + + if (0 == this->BuildTable(config_file_path)) { + _valid = true; + if (is_debug_tag_set("url_rewrite")) { + Print(); + } + } else { + Warning("something failed during BuildTable() -- check your remap plugins!"); + } +} + +UrlRewrite::~UrlRewrite() +{ + ats_free(this->ts_name); + ats_free(this->http_default_redirect_url); + + DestroyStore(forward_mappings); + DestroyStore(reverse_mappings); + DestroyStore(permanent_redirects); + DestroyStore(temporary_redirects); + DestroyStore(forward_mappings_with_recv_port); + _valid = false; +} + +/** Sets the reverse proxy flag. */ +void +UrlRewrite::SetReverseFlag(int flag) +{ + reverse_proxy = flag; + if (is_debug_tag_set("url_rewrite")) + Print(); +} + +/** + Allocaites via new, and adds a mapping like this map /ink/rh + http://{backdoor}/ink/rh + + These {backdoor} things are then rewritten in a request-hdr hook. (In the + future it might make sense to move the rewriting into HttpSM directly.) + +*/ +url_mapping * +UrlRewrite::SetupBackdoorMapping() +{ + const char from_url[] = "/ink/rh"; + const char to_url[] = "http://{backdoor}/ink/rh"; + + url_mapping *mapping = new url_mapping; + + mapping->fromURL.create(NULL); + mapping->fromURL.parse(from_url, sizeof(from_url) - 1); + mapping->fromURL.scheme_set(URL_SCHEME_HTTP, URL_LEN_HTTP); + + mapping->toUrl.create(NULL); + mapping->toUrl.parse(to_url, sizeof(to_url) - 1); + + return mapping; +} + +/** Deallocated a hash table and all the url_mappings in it. */ +void +UrlRewrite::_destroyTable(InkHashTable *h_table) +{ + InkHashTableEntry *ht_entry; + InkHashTableIteratorState ht_iter; + UrlMappingPathIndex *item; + + if (h_table != NULL) { // Iterate over the hash tabel freeing up the all the url_mappings + // contained with in + for (ht_entry = ink_hash_table_iterator_first(h_table, &ht_iter); ht_entry != NULL;) { + item = (UrlMappingPathIndex *)ink_hash_table_entry_value(h_table, ht_entry); + delete item; + ht_entry = ink_hash_table_iterator_next(h_table, &ht_iter); + } + ink_hash_table_destroy(h_table); + } +} + +/** Debugging Method. */ +void +UrlRewrite::Print() +{ + printf("URL Rewrite table with %d entries\n", num_rules_forward + num_rules_reverse + num_rules_redirect_temporary + + num_rules_redirect_permanent + num_rules_forward_with_recv_port); + printf(" Reverse Proxy is %s\n", (reverse_proxy == 0) ? "Off" : "On"); + + printf(" Forward Mapping Table with %d entries\n", num_rules_forward); + PrintStore(forward_mappings); + + printf(" Reverse Mapping Table with %d entries\n", num_rules_reverse); + PrintStore(reverse_mappings); + + printf(" Permanent Redirect Mapping Table with %d entries\n", num_rules_redirect_permanent); + PrintStore(permanent_redirects); + + printf(" Temporary Redirect Mapping Table with %d entries\n", num_rules_redirect_temporary); + PrintStore(temporary_redirects); + + printf(" Forward Mapping With Recv Port Table with %d entries\n", num_rules_forward_with_recv_port); + PrintStore(forward_mappings_with_recv_port); + + if (http_default_redirect_url != NULL) { + printf(" Referer filter default redirect URL: \"%s\"\n", http_default_redirect_url); + } +} + +/** Debugging method. */ +void +UrlRewrite::PrintStore(MappingsStore &store) +{ + if (store.hash_lookup != NULL) { + InkHashTableEntry *ht_entry; + InkHashTableIteratorState ht_iter; + UrlMappingPathIndex *value; + + for (ht_entry = ink_hash_table_iterator_first(store.hash_lookup, &ht_iter); ht_entry != NULL;) { + value = (UrlMappingPathIndex *)ink_hash_table_entry_value(store.hash_lookup, ht_entry); + value->Print(); + ht_entry = ink_hash_table_iterator_next(store.hash_lookup, &ht_iter); + } + } + + if (!store.regex_list.empty()) { + printf(" Regex mappings:\n"); + forl_LL(RegexMapping, list_iter, store.regex_list) { list_iter->url_map->Print(); } + } +} + +/** + If a remapping is found, returns a pointer to it otherwise NULL is + returned. + +*/ +url_mapping * +UrlRewrite::_tableLookup(InkHashTable *h_table, URL *request_url, int request_port, char *request_host, int request_host_len) +{ + UrlMappingPathIndex *ht_entry; + url_mapping *um = NULL; + int ht_result; + + ht_result = ink_hash_table_lookup(h_table, request_host, (void **)&ht_entry); + + if (likely(ht_result && ht_entry)) { + // for empty host don't do a normal search, get a mapping arbitrarily + um = ht_entry->Search(request_url, request_port, request_host_len ? true : false); + } + return um; +} + +// This is only used for redirects and reverse rules, and the homepageredirect flag +// can never be set. The end result is that request_url is modified per remap container. +void +url_rewrite_remap_request(const UrlMappingContainer &mapping_container, URL *request_url, int method) +{ + URL *map_to = mapping_container.getToURL(); + URL *map_from = mapping_container.getFromURL(); + const char *toHost; + int toHostLen; + + toHost = map_to->host_get(&toHostLen); + + Debug("url_rewrite", "%s: Remapping rule id: %d matched", __func__, mapping_container.getMapping()->map_id); + + request_url->host_set(toHost, toHostLen); + request_url->port_set(map_to->port_get_raw()); + + // With the CONNECT method, we have to avoid messing with the scheme and path, because it's not part of + // the CONNECT request (only host and port is). + if (HTTP_WKSIDX_CONNECT != method) { + const char *toScheme; + int toSchemeLen; + const char *requestPath; + int requestPathLen = 0; + int fromPathLen = 0; + const char *toPath; + int toPathLen; + + toScheme = map_to->scheme_get(&toSchemeLen); + request_url->scheme_set(toScheme, toSchemeLen); + + map_from->path_get(&fromPathLen); + toPath = map_to->path_get(&toPathLen); + requestPath = request_url->path_get(&requestPathLen); + + // Should be +3, little extra padding won't hurt. Use the stack allocation + // for better performance (bummer that arrays of variable length is not supported + // on Solaris CC. + char *newPath = static_cast(alloca(sizeof(char) * ((requestPathLen - fromPathLen) + toPathLen + 8))); + int newPathLen = 0; + + *newPath = 0; + if (toPath) { + memcpy(newPath, toPath, toPathLen); + newPathLen += toPathLen; + } + + // We might need to insert a trailing slash in the new portion of the path + // if more will be added and none is present and one will be needed. + if (!fromPathLen && requestPathLen && newPathLen && toPathLen && *(newPath + newPathLen - 1) != '/') { + *(newPath + newPathLen) = '/'; + newPathLen++; + } + + if (requestPath) { + // avoid adding another trailing slash if the requestPath already had one and so does the toPath + if (requestPathLen < fromPathLen) { + if (toPathLen && requestPath[requestPathLen - 1] == '/' && toPath[toPathLen - 1] == '/') { + fromPathLen++; + } + } else { + if (toPathLen && requestPath[fromPathLen] == '/' && toPath[toPathLen - 1] == '/') { + fromPathLen++; + } + } + + // copy the end of the path past what has been mapped + if ((requestPathLen - fromPathLen) > 0) { + memcpy(newPath + newPathLen, requestPath + fromPathLen, requestPathLen - fromPathLen); + newPathLen += (requestPathLen - fromPathLen); + } + } + + // Skip any leading / in the path when setting the new URL path + if (*newPath == '/') { + request_url->path_set(newPath + 1, newPathLen - 1); + } else { + request_url->path_set(newPath, newPathLen); + } + } +} + +/** Used to do the backwards lookups. */ +#define N_URL_HEADERS 4 +bool +UrlRewrite::ReverseMap(HTTPHdr *response_header) +{ + const char *location_hdr; + URL location_url; + int loc_length; + bool remap_found = false; + const char *host; + int host_len; + char *new_loc_hdr; + int new_loc_length; + int i; + const struct { + const char *const field; + const int len; + } url_headers[N_URL_HEADERS] = {{MIME_FIELD_LOCATION, MIME_LEN_LOCATION}, + {MIME_FIELD_CONTENT_LOCATION, MIME_LEN_CONTENT_LOCATION}, + {"URI", 3}, + {"Destination", 11}}; + + if (unlikely(num_rules_reverse == 0)) { + ink_assert(reverse_mappings.empty()); + return false; + } + + for (i = 0; i < N_URL_HEADERS; ++i) { + location_hdr = response_header->value_get(url_headers[i].field, url_headers[i].len, &loc_length); + + if (location_hdr == NULL) { + continue; + } + + location_url.create(NULL); + location_url.parse(location_hdr, loc_length); + + host = location_url.host_get(&host_len); + + UrlMappingContainer reverse_mapping(response_header->m_heap); + + if (reverseMappingLookup(&location_url, location_url.port_get(), host, host_len, reverse_mapping)) { + if (i == 0) + remap_found = true; + url_rewrite_remap_request(reverse_mapping, &location_url); + new_loc_hdr = location_url.string_get_ref(&new_loc_length); + response_header->value_set(url_headers[i].field, url_headers[i].len, new_loc_hdr, new_loc_length); + } + + location_url.destroy(); + } + return remap_found; +} + +/** Perform fast ACL filtering. */ +void +UrlRewrite::PerformACLFiltering(HttpTransact::State *s, url_mapping *map) +{ + if (unlikely(!s || s->acl_filtering_performed || !s->client_connection_enabled)) { + return; + } + + s->acl_filtering_performed = true; // small protection against reverse mapping + + if (map->filter) { + int method = s->hdr_info.client_request.method_get_wksidx(); + int method_wksidx = (method != -1) ? (method - HTTP_WKSIDX_CONNECT) : -1; + bool client_enabled_flag = true; + + ink_release_assert(ats_is_ip(&s->client_info.src_addr)); + + for (acl_filter_rule *rp = map->filter; rp && client_enabled_flag; rp = rp->next) { + bool match = true; + + if (rp->method_restriction_enabled) { + if (method_wksidx != -1) { + match = rp->standard_method_lookup[method_wksidx]; + } else if (!rp->nonstandard_methods.empty()) { + int method_str_len; + const char *method_str = s->hdr_info.client_request.method_get(&method_str_len); + match = rp->nonstandard_methods.count(std::string(method_str, method_str_len)); + } + } + + if (match && rp->src_ip_valid) { + match = false; + for (int j = 0; j < rp->src_ip_cnt && !match; j++) { + bool in_range = rp->src_ip_array[j].contains(s->client_info.src_addr); + if (rp->src_ip_array[j].invert) { + if (!in_range) { + match = true; + } + } else { + if (in_range) { + match = true; + } + } + } + } + + if (match && rp->in_ip_valid) { + Debug("url_rewrite", "match was true and we have specified a in_ip field"); + match = false; + for (int j = 0; j < rp->in_ip_cnt && !match; j++) { + IpEndpoint incoming_addr; + incoming_addr.assign(s->state_machine->ua_session->get_netvc()->get_local_addr()); + if (is_debug_tag_set("url_rewrite")) { + char buf1[128], buf2[128], buf3[128]; + ats_ip_ntop(incoming_addr, buf1, sizeof(buf1)); + ats_ip_ntop(rp->in_ip_array[j].start, buf2, sizeof(buf2)); + ats_ip_ntop(rp->in_ip_array[j].end, buf3, sizeof(buf3)); + Debug("url_rewrite", "Trying to match incoming address %s in range %s - %s.", buf1, buf2, buf3); + } + bool in_range = rp->in_ip_array[j].contains(incoming_addr); + if (rp->in_ip_array[j].invert) { + if (!in_range) { + match = true; + } + } else { + if (in_range) { + match = true; + } + } + } + } + + if (rp->internal) { + match = s->state_machine->ua_session->get_netvc()->get_is_internal_request(); + Debug("url_rewrite", "%s an internal request", match ? "matched" : "didn't match"); + } + + if (match && client_enabled_flag) { // make sure that a previous filter did not DENY + Debug("url_rewrite", "matched ACL filter rule, %s request", rp->allow_flag ? "allowing" : "denying"); + client_enabled_flag = rp->allow_flag ? true : false; + } else { + if (!client_enabled_flag) { + Debug("url_rewrite", "Previous ACL filter rule denied request, continuing to deny it"); + } else { + Debug("url_rewrite", "did NOT match ACL filter rule, %s request", rp->allow_flag ? "denying" : "allowing"); + client_enabled_flag = rp->allow_flag ? false : true; + } + } + + } /* end of for(rp = map->filter;rp;rp = rp->next) */ + + s->client_connection_enabled = client_enabled_flag; + } +} + +/** + Determines if a redirect is to occur and if so, figures out what the + redirect is. This was plaguiarized from UrlRewrite::Remap. redirect_url + ought to point to the new, mapped URL when the function exits. +*/ +mapping_type +UrlRewrite::Remap_redirect(HTTPHdr *request_header, URL *redirect_url) +{ + URL *request_url; + mapping_type mappingType; + const char *host = NULL; + int host_len = 0, request_port = 0; + bool prt, trt; // existence of permanent and temporary redirect tables, respectively + + prt = (num_rules_redirect_permanent != 0); + trt = (num_rules_redirect_temporary != 0); + + if (prt + trt == 0) + return NONE; + + // Since are called before request validity checking + // occurs, make sure that we have both a valid request + // header and a valid URL + // + if (request_header == NULL) { + Debug("url_rewrite", "request_header was invalid. UrlRewrite::Remap_redirect bailing out."); + return NONE; + } + request_url = request_header->url_get(); + if (!request_url->valid()) { + Debug("url_rewrite", "request_url was invalid. UrlRewrite::Remap_redirect bailing out."); + return NONE; + } + + host = request_url->host_get(&host_len); + request_port = request_url->port_get(); + + if (host_len == 0 && reverse_proxy != 0) { // Server request. Use the host header to figure out where + // it goes. Host header parsing is same as in ::Remap + int host_hdr_len; + const char *host_hdr = request_header->value_get(MIME_FIELD_HOST, MIME_LEN_HOST, &host_hdr_len); + + if (!host_hdr) { + host_hdr = ""; + host_hdr_len = 0; + } + + const char *tmp = (const char *)memchr(host_hdr, ':', host_hdr_len); + + if (tmp == NULL) { + host_len = host_hdr_len; + } else { + host_len = tmp - host_hdr; + request_port = ink_atoi(tmp + 1, host_hdr_len - host_len); + + // If atoi fails, try the default for the + // protocol + if (request_port == 0) { + request_port = request_url->port_get(); + } + } + + host = host_hdr; + } + // Temporary Redirects have precedence over Permanent Redirects + // the rationale behind this is that network administrators might + // want quick redirects and not want to worry about all the existing + // permanent rules + mappingType = NONE; + + UrlMappingContainer redirect_mapping(request_header->m_heap); + + if (trt) { + if (temporaryRedirectLookup(request_url, request_port, host, host_len, redirect_mapping)) { + mappingType = TEMPORARY_REDIRECT; + } + } + if ((mappingType == NONE) && prt) { + if (permanentRedirectLookup(request_url, request_port, host, host_len, redirect_mapping)) { + mappingType = PERMANENT_REDIRECT; + } + } + + if (mappingType != NONE) { + ink_assert((mappingType == PERMANENT_REDIRECT) || (mappingType == TEMPORARY_REDIRECT)); + + // Make a copy of the request url so that we can munge it + // for the redirect + redirect_url->create(NULL); + redirect_url->copy(request_url); + + // Perform the actual URL rewrite + url_rewrite_remap_request(redirect_mapping, redirect_url); + + return mappingType; + } + ink_assert(mappingType == NONE); + + return NONE; +} + +bool +UrlRewrite::_addToStore(MappingsStore &store, url_mapping *new_mapping, RegexMapping *reg_map, const char *src_host, + bool is_cur_mapping_regex, int &count) +{ + bool retval; + + new_mapping->setRank(count); // Use the mapping rules number count for rank + if (is_cur_mapping_regex) { + store.regex_list.enqueue(reg_map); + retval = true; + } else { + retval = TableInsert(store.hash_lookup, new_mapping, src_host); + } + if (retval) { + ++count; + } + return retval; +} + +bool +UrlRewrite::InsertMapping(mapping_type maptype, url_mapping *new_mapping, RegexMapping *reg_map, const char *src_host, + bool is_cur_mapping_regex) +{ + bool success = false; + + // Now add the mapping to appropriate container + switch (maptype) { + case FORWARD_MAP: + case FORWARD_MAP_REFERER: + success = _addToStore(forward_mappings, new_mapping, reg_map, src_host, is_cur_mapping_regex, num_rules_forward); + if (success) { + // @todo: is this applicable to regex mapping too? + SetHomePageRedirectFlag(new_mapping, new_mapping->toUrl); + } + break; + case REVERSE_MAP: + success = _addToStore(reverse_mappings, new_mapping, reg_map, src_host, is_cur_mapping_regex, num_rules_reverse); + new_mapping->homePageRedirect = false; + break; + case PERMANENT_REDIRECT: + success = _addToStore(permanent_redirects, new_mapping, reg_map, src_host, is_cur_mapping_regex, num_rules_redirect_permanent); + break; + case TEMPORARY_REDIRECT: + success = _addToStore(temporary_redirects, new_mapping, reg_map, src_host, is_cur_mapping_regex, num_rules_redirect_temporary); + break; + case FORWARD_MAP_WITH_RECV_PORT: + success = _addToStore(forward_mappings_with_recv_port, new_mapping, reg_map, src_host, is_cur_mapping_regex, + num_rules_forward_with_recv_port); + break; + default: + // 'default' required to avoid compiler warning; unsupported map + // type would have been dealt with much before this + return false; + } + + return success; +} + +bool +UrlRewrite::InsertForwardMapping(mapping_type maptype, url_mapping *mapping, const char *src_host) +{ + bool success; + + if (maptype == FORWARD_MAP_WITH_RECV_PORT) { + success = TableInsert(forward_mappings_with_recv_port.hash_lookup, mapping, src_host); + } else { + success = TableInsert(forward_mappings.hash_lookup, mapping, src_host); + } + + if (success) { + switch (maptype) { + case FORWARD_MAP: + case FORWARD_MAP_REFERER: + case FORWARD_MAP_WITH_RECV_PORT: + SetHomePageRedirectFlag(mapping, mapping->toUrl); + break; + default: + break; + } + + (maptype != FORWARD_MAP_WITH_RECV_PORT) ? ++num_rules_forward : ++num_rules_forward_with_recv_port; + } + + return success; +} + +/** + Reads the configuration file and creates a new hash table. + + @return zero on success and non-zero on failure. + +*/ +int +UrlRewrite::BuildTable(const char *path) +{ + BUILD_TABLE_INFO bti; + + ink_assert(forward_mappings.empty()); + ink_assert(reverse_mappings.empty()); + ink_assert(permanent_redirects.empty()); + ink_assert(temporary_redirects.empty()); + ink_assert(forward_mappings_with_recv_port.empty()); + ink_assert(num_rules_forward == 0); + ink_assert(num_rules_reverse == 0); + ink_assert(num_rules_redirect_permanent == 0); + ink_assert(num_rules_redirect_temporary == 0); + ink_assert(num_rules_forward_with_recv_port == 0); + + forward_mappings.hash_lookup = ink_hash_table_create(InkHashTableKeyType_String); + reverse_mappings.hash_lookup = ink_hash_table_create(InkHashTableKeyType_String); + permanent_redirects.hash_lookup = ink_hash_table_create(InkHashTableKeyType_String); + temporary_redirects.hash_lookup = ink_hash_table_create(InkHashTableKeyType_String); + forward_mappings_with_recv_port.hash_lookup = ink_hash_table_create(InkHashTableKeyType_String); + + if (!remap_parse_config(path, this)) { + // XXX handle file reload error + return 3; + } + + // Destroy unused tables + if (num_rules_forward == 0) { + forward_mappings.hash_lookup = ink_hash_table_destroy(forward_mappings.hash_lookup); + } else { + if (ink_hash_table_isbound(forward_mappings.hash_lookup, "")) { + nohost_rules = 1; + } + } + + if (num_rules_reverse == 0) { + reverse_mappings.hash_lookup = ink_hash_table_destroy(reverse_mappings.hash_lookup); + } + + if (num_rules_redirect_permanent == 0) { + permanent_redirects.hash_lookup = ink_hash_table_destroy(permanent_redirects.hash_lookup); + } + + if (num_rules_redirect_temporary == 0) { + temporary_redirects.hash_lookup = ink_hash_table_destroy(temporary_redirects.hash_lookup); + } + + if (num_rules_forward_with_recv_port == 0) { + forward_mappings_with_recv_port.hash_lookup = ink_hash_table_destroy(forward_mappings_with_recv_port.hash_lookup); + } + + return 0; +} + +/** + Inserts arg mapping in h_table with key src_host chaining the mapping + of existing entries bound to src_host if necessary. + +*/ +bool +UrlRewrite::TableInsert(InkHashTable *h_table, url_mapping *mapping, const char *src_host) +{ + char src_host_tmp_buf[1]; + UrlMappingPathIndex *ht_contents; + + if (!src_host) { + src_host = &src_host_tmp_buf[0]; + src_host_tmp_buf[0] = 0; + } + // Insert the new_mapping into hash table + if (ink_hash_table_lookup(h_table, src_host, (void **)&ht_contents)) { + // There is already a path index for this host + if (ht_contents == NULL) { + // why should this happen? + Warning("Found entry cannot be null!"); + return false; + } + } else { + ht_contents = new UrlMappingPathIndex(); + ink_hash_table_insert(h_table, src_host, ht_contents); + } + if (!ht_contents->Insert(mapping)) { + Warning("Could not insert new mapping"); + return false; + } + return true; +} + +/** First looks up the hash table for "simple" mappings and then the + regex mappings. Only higher-ranked regex mappings are examined if + a hash mapping is found; or else all regex mappings are examined + + Returns highest-ranked mapping on success, NULL on failure +*/ +bool +UrlRewrite::_mappingLookup(MappingsStore &mappings, URL *request_url, int request_port, const char *request_host, + int request_host_len, UrlMappingContainer &mapping_container) +{ + char request_host_lower[TS_MAX_HOST_NAME_LEN]; + + if (!request_host || !request_url || (request_host_len < 0) || (request_host_len >= TS_MAX_HOST_NAME_LEN)) { + Debug("url_rewrite", "Invalid arguments!"); + return false; + } + + // lowercase + for (int i = 0; i < request_host_len; ++i) { + request_host_lower[i] = tolower(request_host[i]); + } + request_host_lower[request_host_len] = 0; + + bool retval = false; + int rank_ceiling = -1; + url_mapping *mapping = _tableLookup(mappings.hash_lookup, request_url, request_port, request_host_lower, request_host_len); + if (mapping != NULL) { + rank_ceiling = mapping->getRank(); + Debug("url_rewrite", "Found 'simple' mapping with rank %d", rank_ceiling); + mapping_container.set(mapping); + retval = true; + } + if (_regexMappingLookup(mappings.regex_list, request_url, request_port, request_host_lower, request_host_len, rank_ceiling, + mapping_container)) { + Debug("url_rewrite", "Using regex mapping with rank %d", (mapping_container.getMapping())->getRank()); + retval = true; + } + return retval; +} + +// does not null terminate return string +int +UrlRewrite::_expandSubstitutions(int *matches_info, const RegexMapping *reg_map, const char *matched_string, char *dest_buf, + int dest_buf_size) +{ + int cur_buf_size = 0; + int token_start = 0; + int n_bytes_needed; + int match_index; + for (int i = 0; i < reg_map->n_substitutions; ++i) { + // first copy preceding bytes + n_bytes_needed = reg_map->substitution_markers[i] - token_start; + if ((cur_buf_size + n_bytes_needed) > dest_buf_size) { + goto lOverFlow; + } + memcpy(dest_buf + cur_buf_size, reg_map->to_url_host_template + token_start, n_bytes_needed); + cur_buf_size += n_bytes_needed; + + // then copy the sub pattern match + match_index = reg_map->substitution_ids[i] * 2; + n_bytes_needed = matches_info[match_index + 1] - matches_info[match_index]; + if ((cur_buf_size + n_bytes_needed) > dest_buf_size) { + goto lOverFlow; + } + memcpy(dest_buf + cur_buf_size, matched_string + matches_info[match_index], n_bytes_needed); + cur_buf_size += n_bytes_needed; + + token_start = reg_map->substitution_markers[i] + 2; // skip the place holder + } + + // copy last few bytes (if any) + if (token_start < reg_map->to_url_host_template_len) { + n_bytes_needed = reg_map->to_url_host_template_len - token_start; + if ((cur_buf_size + n_bytes_needed) > dest_buf_size) { + goto lOverFlow; + } + memcpy(dest_buf + cur_buf_size, reg_map->to_url_host_template + token_start, n_bytes_needed); + cur_buf_size += n_bytes_needed; + } + Debug("url_rewrite_regex", "Expanded substitutions and returning string [%.*s] with length %d", cur_buf_size, dest_buf, + cur_buf_size); + return cur_buf_size; + +lOverFlow: + Warning("Overflow while expanding substitutions"); + return 0; +} + +bool +UrlRewrite::_regexMappingLookup(RegexMappingList ®ex_mappings, URL *request_url, int request_port, const char *request_host, + int request_host_len, int rank_ceiling, UrlMappingContainer &mapping_container) +{ + bool retval = false; + + if (rank_ceiling == -1) { // we will now look at all regex mappings + rank_ceiling = INT_MAX; + Debug("url_rewrite_regex", "Going to match all regexes"); + } else { + Debug("url_rewrite_regex", "Going to match regexes with rank <= %d", rank_ceiling); + } + + int request_scheme_len, reg_map_scheme_len; + const char *request_scheme = request_url->scheme_get(&request_scheme_len), *reg_map_scheme; + + int request_path_len, reg_map_path_len; + const char *request_path = request_url->path_get(&request_path_len), *reg_map_path; + + // If the scheme is empty (e.g. because of a CONNECT method), guess it based on port + // This is equivalent to the logic in UrlMappingPathIndex::_GetTrie(). + if (request_scheme_len == 0) { + request_scheme = request_port == 80 ? URL_SCHEME_HTTP : URL_SCHEME_HTTPS; + request_scheme_len = hdrtoken_wks_to_length(request_scheme); + } + + // Loop over the entire linked list, or until we're satisfied + forl_LL(RegexMapping, list_iter, regex_mappings) + { + int reg_map_rank = list_iter->url_map->getRank(); + + if (reg_map_rank > rank_ceiling) { + break; + } + + reg_map_scheme = list_iter->url_map->fromURL.scheme_get(®_map_scheme_len); + if ((request_scheme_len != reg_map_scheme_len) || strncmp(request_scheme, reg_map_scheme, request_scheme_len)) { + Debug("url_rewrite_regex", "Skipping regex with rank %d as scheme does not match request scheme", reg_map_rank); + continue; + } + + if (list_iter->url_map->fromURL.port_get() != request_port) { + Debug("url_rewrite_regex", "Skipping regex with rank %d as regex map port does not match request port. " + "regex map port: %d, request port %d", + reg_map_rank, list_iter->url_map->fromURL.port_get(), request_port); + continue; + } + + reg_map_path = list_iter->url_map->fromURL.path_get(®_map_path_len); + if ((request_path_len < reg_map_path_len) || + strncmp(reg_map_path, request_path, reg_map_path_len)) { // use the shorter path length here + Debug("url_rewrite_regex", "Skipping regex with rank %d as path does not cover request path", reg_map_rank); + continue; + } + + int matches_info[MAX_REGEX_SUBS * 3]; + bool match_result = list_iter->regular_expression.exec(request_host, request_host_len, matches_info, countof(matches_info)); + + if (match_result == true) { + Debug("url_rewrite_regex", "Request URL host [%.*s] matched regex in mapping of rank %d " + "with %d possible substitutions", + request_host_len, request_host, reg_map_rank, match_result); + + mapping_container.set(list_iter->url_map); + + char buf[4096]; + int buf_len; + + // Expand substitutions in the host field from the stored template + buf_len = _expandSubstitutions(matches_info, list_iter, request_host, buf, sizeof(buf)); + URL *expanded_url = mapping_container.createNewToURL(); + expanded_url->copy(&((list_iter->url_map)->toUrl)); + expanded_url->host_set(buf, buf_len); + + Debug("url_rewrite_regex", "Expanded toURL to [%.*s]", expanded_url->length_get(), expanded_url->string_get_ref()); + retval = true; + break; + } else { + Debug("url_rewrite_regex", "Request URL host [%.*s] did NOT match regex in mapping of rank %d", request_host_len, + request_host, reg_map_rank); + } + } + + return retval; +} + +void +UrlRewrite::_destroyList(RegexMappingList &mappings) +{ + RegexMapping *list_iter; + while ((list_iter = mappings.pop()) != NULL) { + delete list_iter->url_map; + if (list_iter->to_url_host_template) { + ats_free(list_iter->to_url_host_template); + } + delete list_iter; + } + mappings.clear(); +} diff --git a/tests/gold_tests/headers/cache_and_req_body-hit.gold b/tests/gold_tests/headers/cache_and_req_body-hit.gold new file mode 100644 index 00000000000..46791e7b478 --- /dev/null +++ b/tests/gold_tests/headers/cache_and_req_body-hit.gold @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=300 +Content-Length: 3 +Date: `` +Age: `` +Connection: keep-alive +Via: `` +Server: `` +X-Cache: hit-fresh + +xxx diff --git a/tests/gold_tests/headers/cache_and_req_body-hit_close.gold b/tests/gold_tests/headers/cache_and_req_body-hit_close.gold new file mode 100644 index 00000000000..5695558bf23 --- /dev/null +++ b/tests/gold_tests/headers/cache_and_req_body-hit_close.gold @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=300 +Content-Length: 3 +Date: `` +Age: `` +Connection: close +Via: `` +Server: `` +X-Cache: hit-fresh + +xxx diff --git a/tests/gold_tests/headers/cache_and_req_body-miss.gold b/tests/gold_tests/headers/cache_and_req_body-miss.gold new file mode 100644 index 00000000000..41b57d9fd88 --- /dev/null +++ b/tests/gold_tests/headers/cache_and_req_body-miss.gold @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=300 +Content-Length: 3 +Date: `` +Age: 0 +Connection: keep-alive +Via: `` +Server: `` +X-Cache: miss + +xxx diff --git a/tests/gold_tests/headers/cache_and_req_body.test.py b/tests/gold_tests/headers/cache_and_req_body.test.py new file mode 100644 index 00000000000..22a307d4cda --- /dev/null +++ b/tests/gold_tests/headers/cache_and_req_body.test.py @@ -0,0 +1,85 @@ +''' +Test cached responses and requests with bodies +''' +# 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. + +import os +Test.Summary = ''' +Test cached responses and requests with bodies +''' + +# Needs Curl +Test.SkipUnless( + Condition.HasProgram("curl", "curl needs to be installed on system for this test to work"), + Condition.HasProgram("nc", "nc needs to be installed on system for this test to work") +) +Test.ContinueOnFail = True + +# Define default ATS +ts = Test.MakeATSProcess("ts", select_ports=False) +server = Test.MakeOriginServer("server") + +#**testname is required** +testName = "" +request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\nCache-Control: max-age=300\r\n\r\n", "timestamp": "1469733493.993", "body": "xxx"} +server.addResponse("sessionlog.json", request_header, response_header) + +# ATS Configuration +ts.Disk.plugin_config.AddLine('xdebug.so') +ts.Disk.records_config.update({ + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'http', + 'proxy.config.http.response_via_str': 3, + 'proxy.config.http.cache.http': 1, + 'proxy.config.http.wait_for_cache': 1, +}) + +ts.Disk.remap_config.AddLine( + 'map / http://127.0.0.1:{0}'.format(server.Variables.Port) +) + +# Test 1 - 200 response and cache fill +tr = Test.AddTestRun() +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(Test.Processes.ts, ready=1) +tr.Processes.Default.Command = 'curl -s -D - -v --ipv4 --http1.1 -H "x-debug: x-cache,x-cache-key,via" -H "Host: www.example.com" http://localhost:8080/' +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-miss.gold" +tr.StillRunningAfter = ts + +# Test 2 - 200 cached response and using netcat +tr = Test.AddTestRun() +tr.Processes.Default.Command = "printf 'GET / HTTP/1.1\r\n''x-debug: x-cache,x-cache-key,via\r\n''Host: www.example.com\r\n''\r\n'|nc 127.0.0.1 8080" +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit.gold" +tr.StillRunningAfter = ts + +# Test 3 - 200 cached response and trying to hide a request in the body +tr = Test.AddTestRun() +tr.Processes.Default.Command = "printf 'GET / HTTP/1.1\r\n''x-debug: x-cache,x-cache-key,via\r\n''Host: www.example.com\r\n''Content-Length: 71\r\n''\r\n''GET /index.html?evil=zorg810 HTTP/1.1\r\n''Host: dummy-host.example.com\r\n''\r\n'|nc 127.0.0.1 8080" +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit.gold" +tr.StillRunningAfter = ts + +# Test 4 - 200 cached response and Content-Length larger than bytes sent, MUST close +tr = Test.AddTestRun() +tr.Processes.Default.Command = "printf 'GET / HTTP/1.1\r\n''x-debug: x-cache,x-cache-key,via\r\n''Host: dummy-host.example.com\r\n''Cache-control: max-age=300\r\n''Content-Length: 100\r\n''\r\n''GET /index.html?evil=zorg810 HTTP/1.1\r\n''Host: dummy-host.example.com\r\n''\r\n'|nc 127.0.0.1 8080" +tr.Processes.Default.ReturnCode = 0 +tr.Processes.Default.Streams.stdout = "cache_and_req_body-hit_close.gold" +tr.StillRunningAfter = ts +