This is an automated email from the ASF dual-hosted git repository.

maskit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 7708542854 Extract HttpUserAgent class from HttpSM (#9869)
7708542854 is described below

commit 77085428542b470113e6278c413240c0482c587a
Author: JosiahWI <[email protected]>
AuthorDate: Wed Jul 12 15:57:54 2023 -0500

    Extract HttpUserAgent class from HttpSM (#9869)
    
    * Extract HttpUserAgent class from HttpSM
    
    This extracts a new class with 3 fields (entry, raw_buffer_reader,
    and txn). It doesn't have much responsibility yet. But it does
    provide a starting point for moving some behavior out of the
    state machine.
    
    * Move virtual connection stuff into HttpVCTable.h
    
    This introduces HttpVCTable.h and HttpVCTable.cc to hold all the
    stuff related to the HttpVCTable struct.
    
    * Move a bunch of client_ fields into HttpUserAgent
    
    This moves all the client_ fields except the byte counts into
    HttpUserAgent, and sets most of the fields when the transaction
    is set.
    
    * Reset some client fields to defaults in set_txn
    
    This matches the behavior of the original code, and may be
    important.
    
    * WIP Fix incorrect tcp_reused detection
    
    A not (!) operator was missed, which caused tcp_reused to be
    set incorrectly on the user agent. This fixes the issue, and adds
    Catch tests to check for regression.
    
    * Clean up debugging changes
    
    There is one segfault left to fix.
    
    * Fix format
    
    * Fix proxy/http Makefile
    
    * Add test_error_page_selection to CMake
    
    * Fix rebase errors
    
    * Add a getter for the state machine's user agent
    
    * Test against UserAgent instead of HttpSM
    
    * Move test_HttpSM to test_HttpUserAgent
    
    * Do not build test_HttpUserAgent in automake build
    
    * Add new sources back to automake
---
 proxy/http/CMakeLists.txt                   |   1 +
 proxy/http/HttpSM.cc                        | 790 ++++++++++++----------------
 proxy/http/HttpSM.h                         |  99 +---
 proxy/http/HttpTransact.cc                  |  49 +-
 proxy/http/HttpTransactHeaders.cc           |   4 +-
 proxy/http/HttpUserAgent.h                  | 268 ++++++++++
 proxy/http/HttpVCTable.cc                   | 136 +++++
 proxy/http/HttpVCTable.h                    |  83 +++
 proxy/http/Makefile.am                      |   4 +
 proxy/http/remap/UrlRewrite.cc              |   4 +-
 proxy/http/unit_tests/CMakeLists.txt        |  27 +-
 proxy/http/unit_tests/test_HttpUserAgent.cc |  93 ++++
 proxy/http/unit_tests/test_PreWarm.cc       |   1 -
 proxy/logging/LogAccess.cc                  |  16 +-
 src/traffic_server/InkAPI.cc                |  26 +-
 15 files changed, 1008 insertions(+), 593 deletions(-)

diff --git a/proxy/http/CMakeLists.txt b/proxy/http/CMakeLists.txt
index 86c3f83606..d90b525adf 100644
--- a/proxy/http/CMakeLists.txt
+++ b/proxy/http/CMakeLists.txt
@@ -35,6 +35,7 @@ add_library(http STATIC
         HttpTransactCache.cc
         HttpTransactHeaders.cc
         HttpTunnel.cc
+        HttpVCTable.cc
         ConnectingEntry.cc
         ForwardedConfig.cc
         PreWarmConfig.cc
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index e5339a234b..d6cd497d3b 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -33,6 +33,7 @@
 #include "Http2ServerSession.h"
 #include "HttpDebugNames.h"
 #include "HttpSessionManager.h"
+#include "HttpVCTable.h"
 #include "P_Cache.h"
 #include "P_Net.h"
 #include "PreWarmConfig.h"
@@ -146,111 +147,6 @@ do_outbound_proxy_protocol(MIOBuffer *miob, 
NetVConnection *vc_out, NetVConnecti
 
 ClassAllocator<HttpSM> httpSMAllocator("httpSMAllocator");
 
-HttpVCTable::HttpVCTable(HttpSM *mysm)
-{
-  memset(&vc_table, 0, sizeof(vc_table));
-  sm = mysm;
-}
-
-HttpVCTableEntry *
-HttpVCTable::new_entry()
-{
-  for (int i = 0; i < vc_table_max_entries; i++) {
-    if (vc_table[i].vc == nullptr) {
-      vc_table[i].sm = sm;
-      return vc_table + i;
-    }
-  }
-
-  ink_release_assert(0);
-  return nullptr;
-}
-
-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 nullptr;
-}
-
-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 != nullptr);
-      return vc_table + i;
-    }
-  }
-
-  return nullptr;
-}
-
-// 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)
-{
-  e->vc  = nullptr;
-  e->eos = false;
-  if (e->read_buffer) {
-    free_MIOBuffer(e->read_buffer);
-    e->read_buffer = nullptr;
-  }
-  if (e->write_buffer) {
-    free_MIOBuffer(e->write_buffer);
-    e->write_buffer = nullptr;
-  }
-  // Cannot reach in to checkout the netvc
-  // for remaining I/O operations because the netvc
-  // may have been deleted at this point and the pointer
-  // could be stale.
-  e->read_vio         = nullptr;
-  e->write_vio        = nullptr;
-  e->vc_read_handler  = nullptr;
-  e->vc_write_handler = nullptr;
-  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) {
-    if (e->vc_type == HTTP_SERVER_VC) {
-      HTTP_INCREMENT_DYN_STAT(http_origin_shutdown_cleanup_entry);
-    }
-    e->vc->do_io_close();
-    e->vc = nullptr;
-  }
-  remove_entry(e);
-}
-
-void
-HttpVCTable::cleanup_all()
-{
-  for (int i = 0; i < vc_table_max_entries; i++) {
-    if (vc_table[i].vc != nullptr) {
-      cleanup_entry(vc_table + i);
-    }
-  }
-}
-
 void
 initialize_thread_for_connecting_pools(EThread *thread)
 {
@@ -401,7 +297,7 @@ HttpSM::init(bool from_early_data)
 void
 HttpSM::set_ua_half_close_flag()
 {
-  ua_txn->set_half_close_flag(true);
+  _ua.get_txn()->set_half_close_flag(true);
 }
 
 inline int
@@ -439,11 +335,11 @@ HttpSM::state_add_to_list(int event, void * /* data 
ATS_UNUSED */)
   t_state.api_next_action = HttpTransact::SM_ACTION_API_SM_START;
   if (do_api_callout() < 0) {
     // Didn't get the hook continuation lock. Clear the read and wait for next 
event
-    if (ua_entry->read_vio) {
-      // Seems like ua_entry->read_vio->disable(); should work, but that was
+    if (_ua.get_entry()->read_vio) {
+      // Seems like _ua.get_entry()->read_vio->disable(); should work, but 
that was
       // not sufficient to stop the state machine from processing IO events 
until the
       // TXN_START hooks had completed.  Just set the number of bytes to read 
to 0
-      ua_entry->read_vio = ua_entry->vc->do_io_read(this, 0, nullptr);
+      _ua.get_entry()->read_vio = _ua.get_entry()->vc->do_io_read(this, 0, 
nullptr);
     }
     return EVENT_CONT;
   }
@@ -499,77 +395,35 @@ HttpSM::attach_client_session(ProxyTransaction *client_vc)
   if (!netvc) {
     return;
   }
-  ua_txn = client_vc;
-
-  // It seems to be possible that the ua_txn pointer will go stale before log 
entries for this HTTP transaction are
-  // generated.  Therefore, collect information that may be needed for logging 
from the ua_txn object at this point.
-  //
-  _client_transaction_id                  = ua_txn->get_transaction_id();
-  _client_transaction_priority_weight     = 
ua_txn->get_transaction_priority_weight();
-  _client_transaction_priority_dependence = 
ua_txn->get_transaction_priority_dependence();
-  {
-    auto p = ua_txn->get_proxy_ssn();
-
-    if (p) {
-      _client_connection_id = p->connection_id();
-    }
-  }
+  _ua.set_txn(client_vc, milestones);
 
   // Collect log & stats information. We've already verified that the netvc is 
!nullptr above,
-  // and netvc == ua_txn->get_netvc().
-
-  is_internal       = netvc->get_is_internal_request();
-  mptcp_state       = netvc->get_mptcp_state();
-  client_tcp_reused = !(ua_txn->is_first_transaction());
-
-  if (auto tbs = netvc->get_service<TLSBasicSupport>()) {
-    client_connection_is_ssl = true;
-    const char *protocol     = tbs->get_tls_protocol_name();
-    client_sec_protocol      = protocol ? protocol : "-";
-    const char *cipher       = tbs->get_tls_cipher_suite();
-    client_cipher_suite      = cipher ? cipher : "-";
-    const char *curve        = tbs->get_tls_curve();
-    client_curve             = curve ? curve : "-";
-
-    if (!client_tcp_reused) {
-      // Copy along the TLS handshake timings
-      milestones[TS_MILESTONE_TLS_HANDSHAKE_START] = 
tbs->get_tls_handshake_begin_time();
-      milestones[TS_MILESTONE_TLS_HANDSHAKE_END]   = 
tbs->get_tls_handshake_end_time();
-    }
-  }
+  // and netvc == _ua.get_txn()->get_netvc().
 
-  if (auto as = netvc->get_service<ALPNSupport>()) {
-    client_alpn_id = as->get_negotiated_protocol_id();
-  }
-
-  if (auto tsrs = netvc->get_service<TLSSessionResumptionSupport>()) {
-    client_ssl_reused = tsrs->getSSLSessionCacheHit();
-  }
-
-  const char *protocol_str = client_vc->get_protocol_string();
-  client_protocol          = protocol_str ? protocol_str : "-";
+  is_internal = netvc->get_is_internal_request();
+  mptcp_state = netvc->get_mptcp_state();
 
-  ink_release_assert(ua_txn->get_half_close_flag() == false);
+  ink_release_assert(_ua.get_txn()->get_half_close_flag() == false);
   mutex = client_vc->mutex;
-  if (ua_txn->debug()) {
+  if (_ua.get_txn()->debug()) {
     debug_on = true;
   }
 
   t_state.setup_per_txn_configs();
 
-  ink_assert(ua_txn->get_proxy_ssn());
-  ink_assert(ua_txn->get_proxy_ssn()->accept_options);
+  ink_assert(_ua.get_txn()->get_proxy_ssn());
+  ink_assert(_ua.get_txn()->get_proxy_ssn()->accept_options);
 
   // default the upstream IP style host resolution order from inbound
-  t_state.my_txn_conf().host_res_data.order = 
ua_txn->get_proxy_ssn()->accept_options->host_res_preference;
+  t_state.my_txn_conf().host_res_data.order = 
_ua.get_txn()->get_proxy_ssn()->accept_options->host_res_preference;
 
   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;
+  _ua.set_entry(vc_table.new_entry());
+  _ua.get_entry()->vc      = client_vc;
+  _ua.get_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());
@@ -580,22 +434,22 @@ HttpSM::attach_client_session(ProxyTransaction *client_vc)
   hooks_set = client_vc->has_hooks();
 
   // Setup for parsing the header
-  ua_entry->vc_read_handler = &HttpSM::state_read_client_request_header;
+  _ua.get_entry()->vc_read_handler = &HttpSM::state_read_client_request_header;
   t_state.hdr_info.client_request.destroy();
   t_state.hdr_info.client_request.create(HTTP_TYPE_REQUEST);
 
   // Prepare raw reader which will live until we are sure this is HTTP indeed
   auto tts = netvc->get_service<TLSTunnelSupport>();
   if (is_transparent_passthrough_allowed() || (tts && 
tts->is_decryption_needed())) {
-    ua_raw_buffer_reader = ua_txn->get_remote_reader()->clone();
+    _ua.set_raw_buffer_reader(_ua.get_txn()->get_remote_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, 
ua_txn->get_remote_reader()->mbuf);
-  ua_entry->write_vio = client_vc->do_io_write(this, 0, nullptr);
+  _ua.get_entry()->read_vio  = client_vc->do_io_read(this, 0, 
_ua.get_txn()->get_remote_reader()->mbuf);
+  _ua.get_entry()->write_vio = client_vc->do_io_write(this, 0, nullptr);
 
   /////////////////////////
   // set up timeouts     //
@@ -621,18 +475,18 @@ HttpSM::attach_client_session(ProxyTransaction *client_vc)
 void
 HttpSM::setup_client_read_request_header()
 {
-  ink_assert(ua_entry->vc_read_handler == 
&HttpSM::state_read_client_request_header);
+  ink_assert(_ua.get_entry()->vc_read_handler == 
&HttpSM::state_read_client_request_header);
 
-  ua_entry->read_vio = ua_txn->do_io_read(this, INT64_MAX, 
ua_txn->get_remote_reader()->mbuf);
+  _ua.get_entry()->read_vio = _ua.get_txn()->do_io_read(this, INT64_MAX, 
_ua.get_txn()->get_remote_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);
+  handleEvent(VC_EVENT_READ_READY, _ua.get_entry()->read_vio);
 }
 
 void
 HttpSM::setup_blind_tunnel_port()
 {
-  NetVConnection *netvc = ua_txn->get_netvc();
+  NetVConnection *netvc = _ua.get_txn()->get_netvc();
   ink_release_assert(netvc);
   int host_len;
 
@@ -649,7 +503,7 @@ HttpSM::setup_blind_tunnel_port()
   }
 
   TLSTunnelSupport *tts = nullptr;
-  if (!ua_txn->is_outbound_transparent() && (tts = 
netvc->get_service<TLSTunnelSupport>())) {
+  if (!_ua.get_txn()->is_outbound_transparent() && (tts = 
netvc->get_service<TLSTunnelSupport>())) {
     if (!t_state.hdr_info.client_request.url_get()->host_get(&host_len)) {
       if (tts->has_tunnel_destination()) {
         auto tunnel_host = tts->get_tunnel_host();
@@ -680,14 +534,14 @@ 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(_ua.get_entry()->read_vio == (VIO *)data);
   ink_assert(server_entry == nullptr);
   ink_assert(server_txn == nullptr);
 
   int bytes_used = 0;
-  ink_assert(ua_entry->eos == false);
+  ink_assert(_ua.get_entry()->eos == false);
 
-  NetVConnection *netvc = ua_txn->get_netvc();
+  NetVConnection *netvc = _ua.get_txn()->get_netvc();
   if (!netvc && event != VC_EVENT_EOS) {
     return 0;
   }
@@ -699,8 +553,8 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
     break;
 
   case VC_EVENT_EOS:
-    ua_entry->eos = true;
-    if ((client_request_hdr_bytes > 0) && is_transparent_passthrough_allowed() 
&& (ua_raw_buffer_reader != nullptr)) {
+    _ua.get_entry()->eos = true;
+    if ((client_request_hdr_bytes > 0) && is_transparent_passthrough_allowed() 
&& (_ua.get_raw_buffer_reader() != nullptr)) {
       break;
     }
   // Fall through
@@ -709,8 +563,8 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
   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 = nullptr;
+    vc_table.cleanup_entry(_ua.get_entry());
+    _ua.set_entry(nullptr);
     set_ua_abort(HttpTransact::ABORTED, event);
     terminate_sm = true;
     return 0;
@@ -720,17 +574,18 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
   //   time we've been called.  The timeout had been set to
   //   the accept timeout by the ProxyTransaction
   //
-  if ((ua_txn->get_remote_reader()->read_avail() > 0) && 
(client_request_hdr_bytes == 0)) {
+  if ((_ua.get_txn()->get_remote_reader()->read_avail() > 0) && 
(client_request_hdr_bytes == 0)) {
     milestones[TS_MILESTONE_UA_FIRST_READ] = Thread::get_hrtime();
-    
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+    
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
   }
   /////////////////////
   // tokenize header //
   /////////////////////
 
-  ParseResult state = t_state.hdr_info.client_request.parse_req(
-    &http_parser, ua_txn->get_remote_reader(), &bytes_used, ua_entry->eos, 
t_state.http_config_param->strict_uri_parsing,
-    t_state.http_config_param->http_request_line_max_size, 
t_state.http_config_param->http_hdr_field_max_size);
+  ParseResult state = t_state.hdr_info.client_request.parse_req(&http_parser, 
_ua.get_txn()->get_remote_reader(), &bytes_used,
+                                                                
_ua.get_entry()->eos, t_state.http_config_param->strict_uri_parsing,
+                                                                
t_state.http_config_param->http_request_line_max_size,
+                                                                
t_state.http_config_param->http_hdr_field_max_size);
 
   client_request_hdr_bytes += bytes_used;
 
@@ -743,7 +598,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
   // 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 (ua_raw_buffer_reader != nullptr) {
+  if (_ua.get_raw_buffer_reader() != nullptr) {
     bool do_blind_tunnel = false;
     // If we had a parse error and we're done reading data
     // blind tunnel
@@ -753,7 +608,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
       // If we had a GET request that has data after the
       // get request, do blind tunnel
     } else if (state == PARSE_RESULT_DONE && 
t_state.hdr_info.client_request.method_get_wksidx() == HTTP_WKSIDX_GET &&
-               ua_txn->get_remote_reader()->read_avail() > 0 && 
!t_state.hdr_info.client_request.is_keep_alive_set()) {
+               _ua.get_txn()->get_remote_reader()->read_avail() > 0 && 
!t_state.hdr_info.client_request.is_keep_alive_set()) {
       do_blind_tunnel = true;
     }
     if (do_blind_tunnel) {
@@ -781,15 +636,15 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
   }
 
   // Check to see if we are done parsing the header
-  if (state != PARSE_RESULT_CONT || ua_entry->eos || (state == 
PARSE_RESULT_CONT && event == VC_EVENT_READ_COMPLETE)) {
-    if (ua_raw_buffer_reader != nullptr) {
-      ua_raw_buffer_reader->dealloc();
-      ua_raw_buffer_reader = nullptr;
+  if (state != PARSE_RESULT_CONT || _ua.get_entry()->eos || (state == 
PARSE_RESULT_CONT && event == VC_EVENT_READ_COMPLETE)) {
+    if (_ua.get_raw_buffer_reader() != nullptr) {
+      _ua.get_raw_buffer_reader()->dealloc();
+      _ua.set_raw_buffer_reader(nullptr);
     }
     http_parser_clear(&http_parser);
-    ua_entry->vc_read_handler  = &HttpSM::state_watch_for_client_abort;
-    ua_entry->vc_write_handler = &HttpSM::state_watch_for_client_abort;
-    ua_txn->cancel_inactivity_timeout();
+    _ua.get_entry()->vc_read_handler  = &HttpSM::state_watch_for_client_abort;
+    _ua.get_entry()->vc_write_handler = &HttpSM::state_watch_for_client_abort;
+    _ua.get_txn()->cancel_inactivity_timeout();
     milestones[TS_MILESTONE_UA_READ_HEADER_DONE] = Thread::get_hrtime();
   }
 
@@ -798,7 +653,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
     SMDebug("http", "error parsing client request header");
 
     // Disable further I/O on the client
-    ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone;
+    _ua.get_entry()->read_vio->nbytes = _ua.get_entry()->read_vio->ndone;
 
     (bytes_used > t_state.http_config_param->http_request_line_max_size) ?
       t_state.http_return_code = HTTP_STATUS_REQUEST_URI_TOO_LONG :
@@ -812,12 +667,12 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
     break;
 
   case PARSE_RESULT_CONT:
-    if (ua_entry->eos) {
+    if (_ua.get_entry()->eos) {
       SMDebug("http_seq", "EOS before client request parsing finished");
       set_ua_abort(HttpTransact::ABORTED, event);
 
       // Disable further I/O on the client
-      ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone;
+      _ua.get_entry()->read_vio->nbytes = _ua.get_entry()->read_vio->ndone;
 
       call_transact_and_set_next_state(HttpTransact::BadRequest);
       break;
@@ -825,14 +680,14 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
       SMDebug("http_parse", "VC_EVENT_READ_COMPLETE and PARSE CONT state");
       break;
     } else {
-      if (is_transparent_passthrough_allowed() && ua_raw_buffer_reader != 
nullptr &&
-          ua_raw_buffer_reader->get_current_block()->write_avail() <= 0) {
+      if (is_transparent_passthrough_allowed() && _ua.get_raw_buffer_reader() 
!= nullptr &&
+          _ua.get_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 = nullptr;
+        _ua.get_raw_buffer_reader()->dealloc();
+        _ua.set_raw_buffer_reader(nullptr);
       }
-      ua_entry->read_vio->reenable();
+      _ua.get_entry()->read_vio->reenable();
       return VC_EVENT_CONT;
     }
   case PARSE_RESULT_DONE:
@@ -846,10 +701,10 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
 
     if (!is_internal && 
t_state.http_config_param->scheme_proto_mismatch_policy != 0) {
       auto scheme = 
t_state.hdr_info.client_request.url_get()->scheme_get_wksidx();
-      if ((client_connection_is_ssl && (scheme == URL_WKSIDX_HTTP || scheme == 
URL_WKSIDX_WS)) ||
-          (!client_connection_is_ssl && (scheme == URL_WKSIDX_HTTPS || scheme 
== URL_WKSIDX_WSS))) {
+      if ((_ua.get_client_connection_is_ssl() && (scheme == URL_WKSIDX_HTTP || 
scheme == URL_WKSIDX_WS)) ||
+          (!_ua.get_client_connection_is_ssl() && (scheme == URL_WKSIDX_HTTPS 
|| scheme == URL_WKSIDX_WSS))) {
         Warning("scheme [%s] vs. protocol [%s] mismatch", 
hdrtoken_index_to_wks(scheme),
-                client_connection_is_ssl ? "tls" : "plaintext");
+                _ua.get_client_connection_is_ssl() ? "tls" : "plaintext");
         if (t_state.http_config_param->scheme_proto_mismatch_policy == 2) {
           t_state.http_return_code = HTTP_STATUS_BAD_REQUEST;
           call_transact_and_set_next_state(HttpTransact::BadRequest);
@@ -876,7 +731,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
       t_state.hdr_info.client_request.mark_early_data();
     }
 
-    ua_txn->set_session_active();
+    _ua.get_txn()->set_session_active();
 
     if (t_state.hdr_info.client_request.version_get() == HTTP_1_1 &&
         (t_state.hdr_info.client_request.method_get_wksidx() == 
HTTP_WKSIDX_POST ||
@@ -888,15 +743,15 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
         // immediately, before receive the real response from original server.
         if (t_state.http_config_param->send_100_continue_response) {
           int64_t alloc_index = 
buffer_size_to_index(len_100_continue_response, 
t_state.http_config_param->max_payload_iobuf_index);
-          if (ua_entry->write_buffer) {
-            free_MIOBuffer(ua_entry->write_buffer);
-            ua_entry->write_buffer = nullptr;
+          if (_ua.get_entry()->write_buffer) {
+            free_MIOBuffer(_ua.get_entry()->write_buffer);
+            _ua.get_entry()->write_buffer = nullptr;
           }
-          ua_entry->write_buffer    = new_MIOBuffer(alloc_index);
-          IOBufferReader *buf_start = ua_entry->write_buffer->alloc_reader();
+          _ua.get_entry()->write_buffer = new_MIOBuffer(alloc_index);
+          IOBufferReader *buf_start     = 
_ua.get_entry()->write_buffer->alloc_reader();
           SMDebug("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_entry->write_vio = ua_txn->do_io_write(this, nbytes, buf_start);
+          int64_t nbytes             = 
_ua.get_entry()->write_buffer->write(str_100_continue_response, 
len_100_continue_response);
+          _ua.get_entry()->write_vio = _ua.get_txn()->do_io_write(this, 
nbytes, buf_start);
         } else {
           t_state.hdr_info.client_request.m_100_continue_required = true;
         }
@@ -916,7 +771,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
     if (t_state.hdr_info.client_request.get_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();
+      _ua.get_entry()->read_vio->reenable();
     } else if (t_state.hdr_info.client_request.method_get_wksidx() == 
HTTP_WKSIDX_TRACE) {
       // Trace with request body is not allowed
       call_transact_and_set_next_state(HttpTransact::BadRequest);
@@ -926,7 +781,7 @@ HttpSM::state_read_client_request_header(int event, void 
*data)
       //  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;
+      _ua.get_entry()->read_vio->nbytes = _ua.get_entry()->read_vio->ndone;
     }
 
     call_transact_and_set_next_state(HttpTransact::ModifyRequest);
@@ -970,15 +825,16 @@ HttpSM::wait_for_full_body()
   //  header buffer into new buffer
   int64_t post_bytes = chunked ? INT64_MAX : 
t_state.hdr_info.request_content_length;
   client_request_body_bytes =
-    post_buffer->write(ua_txn->get_remote_reader(), chunked ? 
ua_txn->get_remote_reader()->read_avail() : post_bytes);
+    post_buffer->write(_ua.get_txn()->get_remote_reader(), chunked ? 
_ua.get_txn()->get_remote_reader()->read_avail() : post_bytes);
 
-  ua_txn->get_remote_reader()->consume(client_request_body_bytes);
-  p = tunnel.add_producer(ua_entry->vc, post_bytes, buf_start, 
&HttpSM::tunnel_handler_post_ua, HT_BUFFER_READ, "ua post buffer");
+  _ua.get_txn()->get_remote_reader()->consume(client_request_body_bytes);
+  p = tunnel.add_producer(_ua.get_entry()->vc, post_bytes, buf_start, 
&HttpSM::tunnel_handler_post_ua, HT_BUFFER_READ,
+                          "ua post buffer");
   if (chunked) {
     tunnel.set_producer_chunking_action(p, 0, TCA_PASSTHRU_CHUNKED_CONTENT);
   }
-  ua_entry->in_tunnel = true;
-  
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+  _ua.get_entry()->in_tunnel = true;
+  
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
   tunnel.tunnel_run(p);
 }
 
@@ -987,8 +843,8 @@ 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 || ua_entry->write_vio == (VIO 
*)data);
-  ink_assert(ua_entry->vc == ua_txn);
+  ink_assert(_ua.get_entry()->read_vio == (VIO *)data || 
_ua.get_entry()->write_vio == (VIO *)data);
+  ink_assert(_ua.get_entry()->vc == _ua.get_txn());
 
   switch (event) {
   /* EOS means that the client has initiated the connection shut down.
@@ -999,21 +855,21 @@ HttpSM::state_watch_for_client_abort(int event, void 
*data)
   case VC_EVENT_EOS: {
     // We got an early EOS. If the tunnal has cache writer, don't kill it for 
background fill.
     if (!terminate_sm) { // Not done already
-      NetVConnection *netvc = ua_txn->get_netvc();
-      if (ua_txn->allow_half_open() || tunnel.has_consumer_besides_client()) {
+      NetVConnection *netvc = _ua.get_txn()->get_netvc();
+      if (_ua.get_txn()->allow_half_open() || 
tunnel.has_consumer_besides_client()) {
         if (netvc) {
           netvc->do_io_shutdown(IO_SHUTDOWN_READ);
         }
       } else {
-        ua_txn->do_io_close();
-        vc_table.cleanup_entry(ua_entry);
-        ua_entry = nullptr;
+        _ua.get_txn()->do_io_close();
+        vc_table.cleanup_entry(_ua.get_entry());
+        _ua.set_entry(nullptr);
         tunnel.kill_tunnel();
         terminate_sm = true; // Just die already, the requester is gone
         set_ua_abort(HttpTransact::ABORTED, event);
       }
-      if (ua_entry) {
-        ua_entry->eos = true;
+      if (_ua.get_entry()) {
+        _ua.get_entry()->eos = true;
       }
     }
     break;
@@ -1026,7 +882,7 @@ HttpSM::state_watch_for_client_abort(int event, void *data)
       //  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_txn);
+      HttpTunnelConsumer *c = tunnel.get_consumer(_ua.get_txn());
       if (c && c->alive) {
         SMDebug("http", "forwarding event %s to tunnel", 
HttpDebugNames::get_event_name(event));
         tunnel.handleEvent(event, c->write_vio);
@@ -1036,8 +892,8 @@ HttpSM::state_watch_for_client_abort(int event, void *data)
       }
     }
     // Disable further I/O on the client
-    if (ua_entry->read_vio) {
-      ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone;
+    if (_ua.get_entry()->read_vio) {
+      _ua.get_entry()->read_vio->nbytes = _ua.get_entry()->read_vio->ndone;
     }
     milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime();
     set_ua_abort(HttpTransact::ABORTED, event);
@@ -1054,15 +910,15 @@ HttpSM::state_watch_for_client_abort(int event, void 
*data)
   case VC_EVENT_WRITE_READY:
     // 100-continue handler
     ink_assert(t_state.hdr_info.client_request.m_100_continue_required || 
t_state.http_config_param->send_100_continue_response);
-    ua_entry->write_vio->reenable();
+    _ua.get_entry()->write_vio->reenable();
     break;
   case VC_EVENT_WRITE_COMPLETE:
     // 100-continue handler
     ink_assert(t_state.hdr_info.client_request.m_100_continue_required || 
t_state.http_config_param->send_100_continue_response);
-    if (ua_entry->write_buffer) {
-      ink_assert(ua_entry->write_vio && !ua_entry->write_vio->ntodo());
-      free_MIOBuffer(ua_entry->write_buffer);
-      ua_entry->write_buffer = nullptr;
+    if (_ua.get_entry()->write_buffer) {
+      ink_assert(_ua.get_entry()->write_vio && 
!_ua.get_entry()->write_vio->ntodo());
+      free_MIOBuffer(_ua.get_entry()->write_buffer);
+      _ua.get_entry()->write_buffer = nullptr;
     }
     break;
   default:
@@ -1078,11 +934,11 @@ HttpSM::setup_push_read_response_header()
 {
   ink_assert(server_txn == nullptr);
   ink_assert(server_entry == nullptr);
-  ink_assert(ua_txn != nullptr);
+  ink_assert(_ua.get_txn() != nullptr);
   ink_assert(t_state.method == HTTP_WKSIDX_PUSH);
 
   // Set the handler to read the pushed response hdr
-  ua_entry->vc_read_handler = &HttpSM::state_read_push_response_header;
+  _ua.get_entry()->vc_read_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
@@ -1098,17 +954,17 @@ HttpSM::setup_push_read_response_header()
 
   // We already done the READ when we read the client
   //  request header
-  ink_assert(ua_entry->read_vio);
+  ink_assert(_ua.get_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_txn->get_remote_reader()->read_avail() > 0) {
-    if (ua_entry->eos) {
-      resp_hdr_state = state_read_push_response_header(VC_EVENT_EOS, 
ua_entry->read_vio);
+  if (_ua.get_txn()->get_remote_reader()->read_avail() > 0) {
+    if (_ua.get_entry()->eos) {
+      resp_hdr_state = state_read_push_response_header(VC_EVENT_EOS, 
_ua.get_entry()->read_vio);
     } else {
-      resp_hdr_state = state_read_push_response_header(VC_EVENT_READ_READY, 
ua_entry->read_vio);
+      resp_hdr_state = state_read_push_response_header(VC_EVENT_READ_READY, 
_ua.get_entry()->read_vio);
     }
   }
   // It is possible that the entire PUSHed response header was already
@@ -1116,8 +972,8 @@ HttpSM::setup_push_read_response_header()
   //  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_txn->do_io_read(this, INT64_MAX, 
ua_txn->get_remote_reader()->mbuf);
+    ink_assert(_ua.get_entry()->eos == false);
+    _ua.get_entry()->read_vio = _ua.get_txn()->do_io_read(this, INT64_MAX, 
_ua.get_txn()->get_remote_reader()->mbuf);
   }
 }
 
@@ -1125,12 +981,12 @@ 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(_ua.get_entry()->read_vio == (VIO *)data);
   ink_assert(t_state.current.server == nullptr);
 
   switch (event) {
   case VC_EVENT_EOS:
-    ua_entry->eos = true;
+    _ua.get_entry()->eos = true;
     // Fall through
 
   case VC_EVENT_READ_READY:
@@ -1148,10 +1004,10 @@ HttpSM::state_read_push_response_header(int event, void 
*data)
   }
 
   int state = PARSE_RESULT_CONT;
-  while (ua_txn->get_remote_reader()->read_avail() && state == 
PARSE_RESULT_CONT) {
-    const char *start = ua_txn->get_remote_reader()->start();
+  while (_ua.get_txn()->get_remote_reader()->read_avail() && state == 
PARSE_RESULT_CONT) {
+    const char *start = _ua.get_txn()->get_remote_reader()->start();
     const char *tmp   = start;
-    int64_t data_size = ua_txn->get_remote_reader()->block_read_avail();
+    int64_t data_size = _ua.get_txn()->get_remote_reader()->block_read_avail();
     ink_assert(data_size >= 0);
 
     /////////////////////
@@ -1163,7 +1019,7 @@ HttpSM::state_read_push_response_header(int event, void 
*data)
     int64_t bytes_used = tmp - start;
 
     ink_release_assert(bytes_used <= data_size);
-    ua_txn->get_remote_reader()->consume(bytes_used);
+    _ua.get_txn()->get_remote_reader()->consume(bytes_used);
     pushed_response_hdr_bytes += bytes_used;
     client_request_body_bytes += bytes_used;
   }
@@ -1171,8 +1027,8 @@ HttpSM::state_read_push_response_header(int event, void 
*data)
   // 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_txn->get_remote_reader()->start();
+  if (_ua.get_entry()->eos) {
+    const char *end = _ua.get_txn()->get_remote_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_RESULT_DONE || state == 
PARSE_RESULT_ERROR);
@@ -1185,7 +1041,7 @@ HttpSM::state_read_push_response_header(int event, void 
*data)
 
   if (state != PARSE_RESULT_CONT) {
     // Disable further IO
-    ua_entry->read_vio->nbytes = ua_entry->read_vio->ndone;
+    _ua.get_entry()->read_vio->nbytes = _ua.get_entry()->read_vio->ndone;
     http_parser_clear(&http_parser);
     milestones[TS_MILESTONE_SERVER_READ_HEADER_DONE] = Thread::get_hrtime();
   }
@@ -1197,7 +1053,7 @@ HttpSM::state_read_push_response_header(int event, void 
*data)
     break;
 
   case PARSE_RESULT_CONT:
-    ua_entry->read_vio->reenable();
+    _ua.get_entry()->read_vio->reenable();
     return VC_EVENT_CONT;
 
   case PARSE_RESULT_DONE:
@@ -1512,8 +1368,7 @@ plugins required to work with sni_routing.
       u.scheme_set(URL_SCHEME_TUNNEL, URL_LEN_TUNNEL);
       t_state.hdr_info.client_request.url_set(&u);
 
-      NetVConnection *netvc = ua_txn->get_netvc();
-
+      NetVConnection *netvc = _ua.get_txn()->get_netvc();
       if (auto tts = netvc->get_service<TLSTunnelSupport>(); tts) {
         if (tts->has_tunnel_destination()) {
           auto tunnel_host = tts->get_tunnel_host();
@@ -1678,7 +1533,7 @@ HttpSM::handle_api_return()
 {
   switch (t_state.api_next_action) {
   case HttpTransact::SM_ACTION_API_SM_START: {
-    NetVConnection *netvc = ua_txn->get_netvc();
+    NetVConnection *netvc = _ua.get_txn()->get_netvc();
     auto *tts             = netvc->get_service<TLSTunnelSupport>();
     bool forward_dest     = tts != nullptr && tts->is_decryption_needed();
     if (t_state.client_info.port_attribute == 
HttpProxyPort::TRANSPORT_BLIND_TUNNEL || forward_dest) {
@@ -1713,8 +1568,8 @@ HttpSM::handle_api_return()
     return;
   case HttpTransact::SM_ACTION_API_SEND_RESPONSE_HDR:
     // Set back the inactivity timeout
-    if (ua_txn) {
-      
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+    if (_ua.get_txn()) {
+      
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
     }
 
     // We only follow 3xx when redirect_in_process == false. Otherwise the 
redirection has already been launched (in
@@ -1753,12 +1608,12 @@ HttpSM::handle_api_return()
           initial_data = server_txn->get_remote_reader();
         }
 
-        if (ua_txn) {
+        if (_ua.get_txn()) {
           SMDebug("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_txn->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout));
-          
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout));
+          
_ua.get_txn()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_active_timeout));
+          
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->websocket_inactive_timeout));
         }
 
         if (server_txn) {
@@ -2054,9 +1909,9 @@ HttpSM::state_read_server_response_header(int event, void 
*data)
     server_txn->set_inactivity_timeout(get_server_inactivity_timeout());
 
     // For requests that contain a body, we can cancel the ua inactivity 
timeout.
-    if (ua_txn && 
ua_txn->has_request_body(t_state.hdr_info.request_content_length,
-                                           
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
-      ua_txn->cancel_inactivity_timeout();
+    if (_ua.get_txn() && 
_ua.get_txn()->has_request_body(t_state.hdr_info.request_content_length,
+                                                         
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
+      _ua.get_txn()->cancel_inactivity_timeout();
     }
   }
   /////////////////////
@@ -2155,7 +2010,7 @@ HttpSM::state_read_server_response_header(int event, void 
*data)
     // 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.
-    
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+    
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
 
     t_state.current.state         = HttpTransact::CONNECTION_ALIVE;
     t_state.transact_return_point = HttpTransact::HandleResponse;
@@ -2210,8 +2065,8 @@ HttpSM::state_send_server_request_header(int event, void 
*data)
       server_entry->write_buffer = nullptr;
       method                     = 
t_state.hdr_info.server_request.method_get_wksidx();
       if (!t_state.api_server_request_body_set && method != HTTP_WKSIDX_TRACE 
&&
-          ua_txn->has_request_body(t_state.hdr_info.request_content_length,
-                                   t_state.client_info.transfer_encoding == 
HttpTransact::CHUNKED_ENCODING)) {
+          
_ua.get_txn()->has_request_body(t_state.hdr_info.request_content_length,
+                                          
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
         if (post_transform_info.vc) {
           setup_transform_to_server_transfer();
         } else {
@@ -2332,7 +2187,7 @@ HttpSM::add_to_existing_request()
   if (nullptr == ethread->connecting_pool) {
     initialize_thread_for_connecting_pools(ethread);
   }
-  auto my_nh = ((UnixNetVConnection *)(this)->ua_txn->get_netvc())->nh;
+  auto my_nh = ((UnixNetVConnection *)(this)->_ua.get_txn()->get_netvc())->nh;
   ink_release_assert(my_nh == nullptr /* PluginVC */ || my_nh == 
get_NetHandler(this_ethread()));
 
   HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_http_server_open);
@@ -2401,7 +2256,7 @@ HttpSM::process_hostdb_info(HostDBRecord *record)
   t_state.dns_info.set_active(nullptr);
 
   if (use_client_addr) {
-    NetVConnection *vc = ua_txn ? ua_txn->get_netvc() : nullptr;
+    NetVConnection *vc = _ua.get_txn() ? _ua.get_txn()->get_netvc() : nullptr;
     if (vc) {
       t_state.dns_info.set_upstream_address(vc->get_local_addr());
       t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_CLIENT;
@@ -2469,11 +2324,12 @@ HttpSM::state_hostdb_lookup(int event, void *data)
 
     char const *host_name = t_state.dns_info.is_srv() ? 
t_state.dns_info.record->name() : t_state.dns_info.lookup_name;
     HostDBProcessor::Options opt;
-    opt.port           = t_state.dns_info.is_srv() ? 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 = 
ats_host_res_from(ua_txn->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
+    opt.port    = t_state.dns_info.is_srv() ? 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 =
+      
ats_host_res_from(_ua.get_txn()->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
 
     pending_action = hostDBProcessor.getbyname_imm(this, 
(cb_process_result_pfn)&HttpSM::process_hostdb_info, host_name, 0, opt);
     if (pending_action.empty()) {
@@ -2497,7 +2353,7 @@ HttpSM::state_hostdb_reverse_lookup(int event, void *data)
   // 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 != nullptr);
+             t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY || 
_ua.get_entry()->vc != nullptr);
 
   switch (event) {
   case EVENT_HOST_DB_LOOKUP:
@@ -2596,13 +2452,13 @@ 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_txn) {
-    pending_action = ua_txn->adjust_thread(this, event, data);
+  if (_ua.get_txn()) {
+    pending_action = _ua.get_txn()->adjust_thread(this, event, data);
     if (!pending_action.empty()) {
       HTTP_INCREMENT_DYN_STAT(http_cache_open_write_adjust_thread_stat);
       return 0; // Go away if we reschedule
     }
-    NetVConnection *vc = ua_txn->get_netvc();
+    NetVConnection *vc = _ua.get_txn()->get_netvc();
     ink_release_assert(vc && vc->thread == this_ethread());
   }
 
@@ -2870,8 +2726,8 @@ HttpSM::tunnel_handler_post_or_put(HttpTunnelProducer *p)
     tunnel.reset();
     // When the ua completed sending it's data we must have
     //  removed it from the tunnel
-    ua_entry->in_tunnel     = false;
-    server_entry->in_tunnel = false;
+    _ua.get_entry()->in_tunnel = false;
+    server_entry->in_tunnel    = false;
 
     break;
   default:
@@ -2889,7 +2745,7 @@ HttpSM::tunnel_handler_post(int event, void *data)
 {
   STATE_ENTER(&HttpSM::tunnel_handler_post, event);
 
-  HttpTunnelProducer *p = ua_txn != nullptr ? tunnel.get_producer(ua_txn) : 
tunnel.get_producer(HT_HTTP_CLIENT);
+  HttpTunnelProducer *p = _ua.get_txn() != nullptr ? 
tunnel.get_producer(_ua.get_txn()) : tunnel.get_producer(HT_HTTP_CLIENT);
   if (!p) {
     return 0; // Cannot do anything if there is no producer
   }
@@ -2915,11 +2771,11 @@ HttpSM::tunnel_handler_post(int event, void *data)
   case VC_EVENT_EOS:                // SSLNetVC may callback EOS during write 
error (6.0.x or early)
   case VC_EVENT_ERROR:              // Send HTTP 408 error
   case VC_EVENT_WRITE_COMPLETE:     // tunnel_handler_post_ua has sent HTTP 
408 response
-  case VC_EVENT_INACTIVITY_TIMEOUT: // ua_txn timeout during sending the HTTP 
408 response
-  case VC_EVENT_ACTIVE_TIMEOUT:     // ua_txn timeout
-    if (ua_entry->write_buffer) {
-      free_MIOBuffer(ua_entry->write_buffer);
-      ua_entry->write_buffer = nullptr;
+  case VC_EVENT_INACTIVITY_TIMEOUT: // _ua.get_txn() timeout during sending 
the HTTP 408 response
+  case VC_EVENT_ACTIVE_TIMEOUT:     // _ua.get_txn() timeout
+    if (_ua.get_entry()->write_buffer) {
+      free_MIOBuffer(_ua.get_entry()->write_buffer);
+      _ua.get_entry()->write_buffer = nullptr;
     }
     if (!p->handler_state) {
       p->handler_state = HTTP_SM_POST_UA_FAIL;
@@ -3007,15 +2863,16 @@ HttpSM::tunnel_handler_trailer(int event, void *data)
   if (server_txn->is_read_closed()) {
     nbytes = start_bytes;
   }
-  // Signal the ua_txn to get ready for a trailer
-  ua_txn->set_expect_send_trailer();
+  // Signal the _ua.get_txn() to get ready for a trailer
+  _ua.get_txn()->set_expect_send_trailer();
   tunnel.reset();
   HttpTunnelProducer *p = tunnel.add_producer(server_entry->vc, nbytes, 
buf_start, &HttpSM::tunnel_handler_trailer_server,
                                               HT_HTTP_SERVER, "http server 
trailer");
-  tunnel.add_consumer(ua_entry->vc, server_entry->vc, 
&HttpSM::tunnel_handler_trailer_ua, HT_HTTP_CLIENT, "user agent trailer");
+  tunnel.add_consumer(_ua.get_entry()->vc, server_entry->vc, 
&HttpSM::tunnel_handler_trailer_ua, HT_HTTP_CLIENT,
+                      "user agent trailer");
 
-  ua_entry->in_tunnel     = true;
-  server_entry->in_tunnel = true;
+  _ua.get_entry()->in_tunnel = true;
+  server_entry->in_tunnel    = true;
 
   tunnel.tunnel_run(p);
 
@@ -3072,7 +2929,7 @@ HttpSM::tunnel_handler_100_continue(int event, void *data)
   // 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_txn);
+  HttpTunnelConsumer *c = tunnel.get_consumer(_ua.get_txn());
 
   if (c->write_success) {
     // Note: we must use destroy() here since clear()
@@ -3112,7 +2969,7 @@ HttpSM::tunnel_handler_push(int event, void *data)
   ink_assert(data == &tunnel);
 
   // Check to see if the client is still around
-  HttpTunnelProducer *ua = (ua_txn) ? tunnel.get_producer(ua_txn) : 
tunnel.get_producer(HT_HTTP_CLIENT);
+  HttpTunnelProducer *ua = (_ua.get_txn()) ? 
tunnel.get_producer(_ua.get_txn()) : tunnel.get_producer(HT_HTTP_CLIENT);
 
   if (ua && !ua->read_success) {
     // Client failed to send the body, it's gone.  Kill the
@@ -3256,8 +3113,8 @@ HttpSM::tunnel_handler_server(int event, 
HttpTunnelProducer *p)
       // 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_txn = NULL; */
+      /*if (_ua.get_entry()->in_tunnel)
+        _ua.set_txn(nullptr); */
 
       t_state.current.server->abort      = HttpTransact::ABORTED;
       t_state.client_info.keep_alive     = HTTP_NO_KEEPALIVE;
@@ -3363,7 +3220,7 @@ HttpSM::tunnel_handler_server(int event, 
HttpTunnelProducer *p)
        propagate server closes back to the client. Part of that is
        disabling KeepAlive if the server closes.
     */
-    if (ua_txn && ua_txn->is_outbound_transparent() && 
t_state.http_config_param->use_client_source_port) {
+    if (_ua.get_txn() && _ua.get_txn()->is_outbound_transparent() && 
t_state.http_config_param->use_client_source_port) {
       t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE;
     }
   } else {
@@ -3373,9 +3230,10 @@ HttpSM::tunnel_handler_server(int event, 
HttpTunnelProducer *p)
     // be placed into the shared pool if the next incoming request is for a 
different
     // origin server
     bool release_origin_connection = true;
-    if (t_state.txn_conf->attach_server_session_to_client == 1 && ua_txn && 
t_state.client_info.keep_alive == HTTP_KEEPALIVE) {
+    if (t_state.txn_conf->attach_server_session_to_client == 1 && 
_ua.get_txn() &&
+        t_state.client_info.keep_alive == HTTP_KEEPALIVE) {
       SMDebug("http", "attaching server session to the client");
-      if (ua_txn->attach_server_session(static_cast<PoolableSession 
*>(server_txn->get_proxy_ssn()))) {
+      if (_ua.get_txn()->attach_server_session(static_cast<PoolableSession 
*>(server_txn->get_proxy_ssn()))) {
         release_origin_connection = false;
       }
     }
@@ -3478,25 +3336,25 @@ HttpSM::tunnel_handler_100_continue_ua(int event, 
HttpTunnelConsumer *c)
 {
   STATE_ENTER(&HttpSM::tunnel_handler_100_continue_ua, event);
 
-  ink_assert(c->vc == ua_txn);
+  ink_assert(c->vc == _ua.get_txn());
 
   switch (event) {
   case VC_EVENT_EOS:
-    ua_entry->eos = true;
+    _ua.get_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);
-    vc_table.remove_entry(ua_entry);
+    vc_table.remove_entry(_ua.get_entry());
     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;
+    _ua.get_entry()->in_tunnel = false;
+    c->write_success           = true;
 
     // remove the buffer reader from the consumer's vc
     if (c->vc != nullptr) {
@@ -3564,12 +3422,12 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer 
*c)
   HttpTunnelConsumer *selfc = nullptr;
 
   STATE_ENTER(&HttpSM::tunnel_handler_ua, event);
-  ink_assert(c->vc == ua_txn);
+  ink_assert(c->vc == _ua.get_txn());
   milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime();
 
   switch (event) {
   case VC_EVENT_EOS:
-    ua_entry->eos = true;
+    _ua.get_entry()->eos = true;
 
   // FALL-THROUGH
   case VC_EVENT_INACTIVITY_TIMEOUT:
@@ -3598,7 +3456,7 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer 
*c)
 
       // Even with the background fill, the client side should go down
       c->write_vio = nullptr;
-      vc_table.remove_entry(ua_entry);
+      vc_table.remove_entry(_ua.get_entry());
       c->vc->do_io_close(EHTTP_ERROR);
       c->alive = false;
 
@@ -3624,7 +3482,7 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer 
*c)
     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_txn->get_server_session()) {
+      if (t_state.www_auth_content != HttpTransact::CACHE_AUTH_SERVE || 
_ua.get_txn()->get_server_session()) {
         // successful keep-alive
         close_connection = false;
       }
@@ -3671,22 +3529,22 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer 
*c)
     // Don't shutdown if we are still expecting a trailer
   } else if (close_connection) {
     // If the client could be pipelining or is doing a POST, we need to
-    //   set the ua_txn into half close mode
+    //   set the _ua.get_txn() 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) && 
!is_internal);
 
     if (is_eligible_post_request && c->producer->vc_type != HT_STATIC && event 
== VC_EVENT_WRITE_COMPLETE) {
-      ua_txn->set_half_close_flag(true);
+      _ua.get_txn()->set_half_close_flag(true);
     }
 
-    vc_table.remove_entry(this->ua_entry);
-    ink_release_assert(vc_table.find_entry(ua_txn) == nullptr);
-    ua_txn->do_io_close();
+    vc_table.remove_entry(this->_ua.get_entry());
+    ink_release_assert(vc_table.find_entry(_ua.get_txn()) == nullptr);
+    _ua.get_txn()->do_io_close();
   } else {
-    ink_assert(ua_txn->get_remote_reader() != nullptr);
-    vc_table.remove_entry(this->ua_entry);
-    ua_txn->release();
+    ink_assert(_ua.get_txn()->get_remote_reader() != nullptr);
+    vc_table.remove_entry(this->_ua.get_entry());
+    _ua.get_txn()->release();
   }
 
   return 0;
@@ -3699,12 +3557,12 @@ HttpSM::tunnel_handler_trailer_ua(int event, 
HttpTunnelConsumer *c)
   HttpTunnelConsumer *selfc = nullptr;
 
   STATE_ENTER(&HttpSM::tunnel_handler_trailer_ua, event);
-  ink_assert(c->vc == ua_txn);
+  ink_assert(c->vc == _ua.get_txn());
   milestones[TS_MILESTONE_UA_CLOSE] = Thread::get_hrtime();
 
   switch (event) {
   case VC_EVENT_EOS:
-    ua_entry->eos = true;
+    _ua.get_entry()->eos = true;
 
   // FALL-THROUGH
   case VC_EVENT_INACTIVITY_TIMEOUT:
@@ -3745,10 +3603,10 @@ HttpSM::tunnel_handler_trailer_ua(int event, 
HttpTunnelConsumer *c)
     break;
   }
 
-  ink_assert(ua_entry->vc == c->vc);
-  vc_table.remove_entry(this->ua_entry);
-  ua_txn->do_io_close();
-  ink_release_assert(vc_table.find_entry(ua_txn) == nullptr);
+  ink_assert(_ua.get_entry()->vc == c->vc);
+  vc_table.remove_entry(this->_ua.get_entry());
+  _ua.get_txn()->do_io_close();
+  ink_release_assert(vc_table.find_entry(_ua.get_txn()) == nullptr);
   return 0;
 }
 
@@ -3776,8 +3634,8 @@ HttpSM::tunnel_handler_ua_push(int event, 
HttpTunnelProducer *p)
   case VC_EVENT_READ_COMPLETE:
     //
     // The transfer completed successfully
-    p->read_success     = true;
-    ua_entry->in_tunnel = false;
+    p->read_success            = true;
+    _ua.get_entry()->in_tunnel = false;
     break;
 
   case VC_EVENT_READ_READY:
@@ -3900,13 +3758,13 @@ HttpSM::tunnel_handler_post_ua(int event, 
HttpTunnelProducer *p)
       p->handler_state = HTTP_SM_POST_UA_FAIL;
       set_ua_abort(HttpTransact::ABORTED, event);
 
-      SMDebug("http_tunnel", "send 408 response to client to vc %p, tunnel vc 
%p", ua_txn->get_netvc(), p->vc);
+      SMDebug("http_tunnel", "send 408 response to client to vc %p, tunnel vc 
%p", _ua.get_txn()->get_netvc(), p->vc);
 
       tunnel.chain_abort_all(p);
       // Reset the inactivity timeout, otherwise the InactivityCop will 
callback again in the next second.
-      
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+      
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
       // if it is active timeout case, we need to give another chance to send 
408 response;
-      
ua_txn->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_in));
+      
_ua.get_txn()->set_active_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_active_timeout_in));
 
       return 0;
     }
@@ -3924,22 +3782,22 @@ HttpSM::tunnel_handler_post_ua(int event, 
HttpTunnelProducer *p)
     //   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);
+    hsm_release_assert(_ua.get_entry()->in_tunnel == true);
     if (p->consumer_list.head && p->consumer_list.head->vc_type == 
HT_TRANSFORM) {
       hsm_release_assert(post_transform_info.entry->in_tunnel == true);
     } // server side may have completed before the user agent side, so it may 
no longer be in tunnel
 
     // In the error case, start to take down the client session. There should
     // be no reuse here
-    vc_table.remove_entry(this->ua_entry);
-    ua_txn->do_io_close();
+    vc_table.remove_entry(this->_ua.get_entry());
+    _ua.get_txn()->do_io_close();
     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;
+    p->handler_state           = HTTP_SM_POST_SUCCESS;
+    p->read_success            = true;
+    _ua.get_entry()->in_tunnel = false;
 
     if (p->do_dechunking || p->do_chunked_passthru) {
       if (p->chunked_handler.truncation) {
@@ -3951,13 +3809,13 @@ HttpSM::tunnel_handler_post_ua(int event, 
HttpTunnelProducer *p)
 
     // Now that we have communicated the post body, turn off the inactivity 
timeout
     // until the server starts sending data back
-    if (ua_txn) {
-      ua_txn->cancel_inactivity_timeout();
+    if (_ua.get_txn()) {
+      _ua.get_txn()->cancel_inactivity_timeout();
 
       // Initiate another read to catch aborts
-      ua_entry->vc_read_handler  = &HttpSM::state_watch_for_client_abort;
-      ua_entry->vc_write_handler = &HttpSM::state_watch_for_client_abort;
-      ua_entry->read_vio         = p->vc->do_io_read(this, INT64_MAX, 
ua_txn->get_remote_reader()->mbuf);
+      _ua.get_entry()->vc_read_handler  = 
&HttpSM::state_watch_for_client_abort;
+      _ua.get_entry()->vc_write_handler = 
&HttpSM::state_watch_for_client_abort;
+      _ua.get_entry()->read_vio         = p->vc->do_io_read(this, INT64_MAX, 
_ua.get_txn()->get_remote_reader()->mbuf);
     }
     break;
   default:
@@ -4066,21 +3924,21 @@ HttpSM::tunnel_handler_post_server(int event, 
HttpTunnelConsumer *c)
       ua_producer = c->producer;
     }
     ink_assert(ua_producer->vc_type == HT_HTTP_CLIENT);
-    ink_assert(ua_producer->vc == ua_txn);
-    ink_assert(ua_producer->vc == ua_entry->vc);
+    ink_assert(ua_producer->vc == _ua.get_txn());
+    ink_assert(ua_producer->vc == _ua.get_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_read_handler  = &HttpSM::state_watch_for_client_abort;
-    ua_entry->vc_write_handler = &HttpSM::state_watch_for_client_abort;
+    _ua.get_entry()->vc_read_handler  = &HttpSM::state_watch_for_client_abort;
+    _ua.get_entry()->vc_write_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, 
ua_txn->get_remote_reader()->mbuf);
+        _ua.get_entry()->read_vio = ua_producer->vc->do_io_read(this, 
INT64_MAX, _ua.get_txn()->get_remote_reader()->mbuf);
         // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ);
       } else {
         if (ua_producer->vc_type == HT_STATIC && 
t_state.redirect_info.redirect_in_process) {
@@ -4088,7 +3946,7 @@ HttpSM::tunnel_handler_post_server(int event, 
HttpTunnelConsumer *c)
         }
       }
     } else {
-      ua_entry->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, 
ua_txn->get_remote_reader()->mbuf);
+      _ua.get_entry()->read_vio = ua_producer->vc->do_io_read(this, INT64_MAX, 
_ua.get_txn()->get_remote_reader()->mbuf);
       // we should not shutdown read side of the client here to prevent 
sending a reset
       // ua_producer->vc->do_io_shutdown(IO_SHUTDOWN_READ);
     } // end of added logic
@@ -4451,9 +4309,9 @@ HttpSM::check_sni_host()
   int host_len;
   const char *host_name = t_state.hdr_info.client_request.host_get(&host_len);
   if (host_name && host_len) {
-    if (ua_txn->support_sni()) {
+    if (_ua.get_txn()->support_sni()) {
       int host_sni_policy   = t_state.http_config_param->http_host_sni_policy;
-      NetVConnection *netvc = ua_txn->get_netvc();
+      NetVConnection *netvc = _ua.get_txn()->get_netvc();
       if (netvc) {
         IpEndpoint ip = netvc->get_remote_endpoint();
         if (SNIConfig::test_client_action(std::string{host_name, 
static_cast<size_t>(host_len)}.c_str(), netvc->get_local_port(),
@@ -4574,7 +4432,7 @@ HttpSM::do_hostdb_lookup()
                                                                                
      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 =
-        ats_host_res_from(ua_txn->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
+        
ats_host_res_from(_ua.get_txn()->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
 
       pending_action = hostDBProcessor.getbyname_imm(this, 
(cb_process_result_pfn)&HttpSM::process_hostdb_info, host_name, 0, opt);
       if (pending_action.empty()) {
@@ -4606,7 +4464,8 @@ HttpSM::do_hostdb_lookup()
                                                                                
    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 = 
ats_host_res_from(ua_txn->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
+    opt.host_res_style =
+      
ats_host_res_from(_ua.get_txn()->get_netvc()->get_local_addr()->sa_family, 
t_state.txn_conf->host_res_data.order);
 
     pending_action = hostDBProcessor.getbyname_imm(this, 
(cb_process_result_pfn)&HttpSM::process_hostdb_info,
                                                    
t_state.dns_info.lookup_name, 0, opt);
@@ -5246,8 +5105,8 @@ HttpSM::get_outbound_sni() const
   ts::TextView zret;
   ts::TextView policy{t_state.txn_conf->ssl_client_sni_policy, 
ts::TextView::npos};
 
-  if (ua_txn) {
-    if (const NetVConnection *netvc = ua_txn->get_netvc(); 
netvc->options.outbound_sni_policy) {
+  if (_ua.get_txn()) {
+    if (const NetVConnection *netvc = _ua.get_txn()->get_netvc(); 
netvc->options.outbound_sni_policy) {
       policy.assign(netvc->options.outbound_sni_policy.get(), 
ts::TextView::npos);
     }
   }
@@ -5257,8 +5116,8 @@ HttpSM::get_outbound_sni() const
     int len;
     char const *ptr = t_state.hdr_info.server_request.host_get(&len);
     zret.assign(ptr, len);
-  } else if (ua_txn && !strcmp(policy, "server_name"_tv)) {
-    zret.assign(ua_txn->get_netvc()->get_server_name(), ts::TextView::npos);
+  } else if (_ua.get_txn() && !strcmp(policy, "server_name"_tv)) {
+    zret.assign(_ua.get_txn()->get_netvc()->get_server_name(), 
ts::TextView::npos);
   } else if (policy.front() == '@') { // guaranteed non-empty from previous 
clause
     zret = policy.remove_prefix(1);
   } else {
@@ -5280,7 +5139,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
   auto fam_name = ats_ip_family_name(ip_family);
   SMDebug("http_track", "entered inside do_http_server_open ][%.*s]", 
static_cast<int>(fam_name.size()), fam_name.data());
 
-  NetVConnection *vc = ua_txn->get_netvc();
+  NetVConnection *vc = _ua.get_txn()->get_netvc();
   ink_release_assert(vc && vc->thread == this_ethread());
   pending_action = nullptr;
 
@@ -5295,10 +5154,10 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
     server_txn = nullptr;
   }
 
-  // ua_entry can be null if a scheduled update is also a reverse proxy
+  // _ua.get_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_txn != NULL instead of req_flavor value.
-  ink_assert(ua_entry != nullptr || t_state.req_flavor == 
HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE ||
+  // to be based on _ua.get_txn() != NULL instead of req_flavor value.
+  ink_assert(_ua.get_entry() != nullptr || t_state.req_flavor == 
HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE ||
              t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY);
 
   ink_assert(pending_action.empty());
@@ -5378,7 +5237,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
   }
 
   // Check for self loop.
-  if (!ua_txn->is_outbound_transparent() && 
HttpTransact::will_this_request_self_loop(&t_state)) {
+  if (!_ua.get_txn()->is_outbound_transparent() && 
HttpTransact::will_this_request_self_loop(&t_state)) {
     call_transact_and_set_next_state(HttpTransact::SelfLoop);
     return;
   }
@@ -5418,13 +5277,13 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
   bool try_reuse = false;
   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_txn != nullptr) {
+      _ua.get_txn() != nullptr) {
     HSMresult_t shared_result;
     SMDebug("http_ss", "Try to acquire_session for %s", 
t_state.current.server->name);
     shared_result = httpSessionManager.acquire_session(this,                   
              // state machine
                                                        
&t_state.current.server->dst_addr.sa, // ip + port
                                                        
t_state.current.server->name,         // hostname
-                                                       ua_txn                  
              // has ptr to bound ua sessions
+                                                       _ua.get_txn()           
              // has ptr to bound ua sessions
     );
     try_reuse     = true;
 
@@ -5450,8 +5309,8 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
   // 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_txn != nullptr)) {
-    PoolableSession *existing_ss = ua_txn->get_server_session();
+           (_ua.get_txn() != nullptr)) {
+    PoolableSession *existing_ss = _ua.get_txn()->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
@@ -5459,7 +5318,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
       // 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->get_remote_addr(), 
&t_state.current.server->dst_addr.sa)) {
-        ua_txn->attach_server_session(nullptr);
+        _ua.get_txn()->attach_server_session(nullptr);
         existing_ss->set_active();
         this->create_server_txn(existing_ss);
         hsm_release_assert(server_txn != nullptr);
@@ -5470,19 +5329,19 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
         // the existing connection and call connect_re to get a new one
         
existing_ss->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out));
         existing_ss->release(server_txn);
-        ua_txn->attach_server_session(nullptr);
+        _ua.get_txn()->attach_server_session(nullptr);
       }
     }
   }
   // Otherwise, we release the existing connection and call connect_re
   // to get a new one.
-  // ua_txn is null when t_state.req_flavor == REQ_FLAVOR_SCHEDULED_UPDATE
-  else if (ua_txn != nullptr) {
-    PoolableSession *existing_ss = ua_txn->get_server_session();
+  // _ua.get_txn() is null when t_state.req_flavor == 
REQ_FLAVOR_SCHEDULED_UPDATE
+  else if (_ua.get_txn() != nullptr) {
+    PoolableSession *existing_ss = _ua.get_txn()->get_server_session();
     if (existing_ss) {
       
existing_ss->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->keep_alive_no_activity_timeout_out));
       existing_ss->release(server_txn);
-      ua_txn->attach_server_session(nullptr);
+      _ua.get_txn()->attach_server_session(nullptr);
     }
   }
 
@@ -5497,7 +5356,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
     } else if (raw) {
       HTTP_INCREMENT_DYN_STAT(http_origin_raw);
     } else {
-      ink_release_assert(ua_txn == nullptr);
+      ink_release_assert(_ua.get_txn() == nullptr);
     }
   }
 
@@ -5575,8 +5434,8 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
 
   int scheme_to_use = t_state.scheme; // get initial scheme
   bool tls_upstream = scheme_to_use == URL_WKSIDX_HTTPS;
-  if (ua_txn) {
-    auto tts = ua_txn->get_netvc()->get_service<TLSTunnelSupport>();
+  if (_ua.get_txn()) {
+    auto tts = _ua.get_txn()->get_netvc()->get_service<TLSTunnelSupport>();
     if (tts && raw) {
       tls_upstream = tts->is_upstream_tls();
       _tunnel_type = tts->get_tunnel_type();
@@ -5584,7 +5443,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
       // ALPN on TLS Partial Blind Tunnel - set negotiated ALPN id
       int pid = SessionProtocolNameRegistry::INVALID;
       if (tts->get_tunnel_type() == SNIRoutingType::PARTIAL_BLIND) {
-        auto alpns = ua_txn->get_netvc()->get_service<ALPNSupport>();
+        auto alpns = _ua.get_txn()->get_netvc()->get_service<ALPNSupport>();
         ink_assert(alpns);
         pid = alpns->get_negotiated_protocol_id();
         if (pid != SessionProtocolNameRegistry::INVALID) {
@@ -5628,13 +5487,13 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
         SMDebug("http_ss", "no pre-warmed tunnel");
       }
     }
-    opt.local_port = ua_txn->get_outbound_port();
+    opt.local_port = _ua.get_txn()->get_outbound_port();
 
-    const IpAddr &outbound_ip = AF_INET6 == opt.ip_family ? 
ua_txn->get_outbound_ip6() : ua_txn->get_outbound_ip4();
+    const IpAddr &outbound_ip = AF_INET6 == opt.ip_family ? 
_ua.get_txn()->get_outbound_ip6() : _ua.get_txn()->get_outbound_ip4();
     if (outbound_ip.isValid()) {
       opt.addr_binding = NetVCOptions::INTF_ADDR;
       opt.local_ip     = outbound_ip;
-    } else if (ua_txn->is_outbound_transparent()) {
+    } else if (_ua.get_txn()->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
@@ -5643,7 +5502,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
          configuration parameter.
       */
 
-      NetVConnection *client_vc = ua_txn->get_netvc();
+      NetVConnection *client_vc = _ua.get_txn()->get_netvc();
       if (t_state.http_config_param->use_client_source_port && nullptr != 
client_vc) {
         opt.local_port = client_vc->get_remote_port();
       }
@@ -5692,7 +5551,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
       SMDebug("http_ss", "Queue multiplexed request");
       new_entry          = new ConnectingEntry();
       new_entry->mutex   = this->mutex;
-      new_entry->ua_txn  = ua_txn;
+      new_entry->ua_txn  = _ua.get_txn();
       new_entry->handler = 
(ContinuationHandler)&ConnectingEntry::state_http_server_open;
       new_entry->ipaddr.assign(&t_state.current.server->dst_addr.sa);
       new_entry->hostname            = t_state.current.server->name;
@@ -5793,7 +5652,7 @@ HttpSM::do_api_callout_internal()
     ink_assert(!"not reached");
   }
 
-  hook_state.init(cur_hook_id, http_global_hooks, ua_txn ? 
ua_txn->feature_hooks() : nullptr, &api_hooks);
+  hook_state.init(cur_hook_id, http_global_hooks, _ua.get_txn() ? 
_ua.get_txn()->feature_hooks() : nullptr, &api_hooks);
   cur_hook  = nullptr;
   cur_hooks = 0;
   return state_api_callout(0, nullptr);
@@ -5956,7 +5815,7 @@ HttpSM::release_server_session(bool serve_from_cache)
       // 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_txn->attach_server_session(static_cast<PoolableSession 
*>(server_txn->get_proxy_ssn()), false);
+      _ua.get_txn()->attach_server_session(static_cast<PoolableSession 
*>(server_txn->get_proxy_ssn()), false);
     }
   } else {
     server_txn->do_io_close();
@@ -6001,7 +5860,7 @@ HttpSM::handle_post_failure()
 {
   STATE_ENTER(&HttpSM::handle_post_failure, VC_EVENT_NONE);
 
-  ink_assert(ua_entry->vc == ua_txn);
+  ink_assert(_ua.get_entry()->vc == _ua.get_txn());
   ink_assert(is_waiting_for_full_body || server_entry->eos == true);
 
   if (is_waiting_for_full_body) {
@@ -6014,10 +5873,10 @@ HttpSM::handle_post_failure()
   // 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_txn->do_io_read(this, 0, nullptr);
+    _ua.get_entry()->read_vio = _ua.get_txn()->do_io_read(this, 0, nullptr);
   }
-  ua_entry->in_tunnel     = false;
-  server_entry->in_tunnel = false;
+  _ua.get_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
@@ -6119,11 +5978,11 @@ HttpSM::handle_server_setup_error(int event, void *data)
       // Do we need to do additional clean up in the c == NULL case?
       if (c != nullptr) {
         HttpTunnelProducer *ua_producer = c->producer;
-        ink_assert(ua_entry->vc == ua_producer->vc);
+        ink_assert(_ua.get_entry()->vc == ua_producer->vc);
 
-        ua_entry->vc_read_handler  = &HttpSM::state_watch_for_client_abort;
-        ua_entry->vc_write_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.get_entry()->vc_read_handler  = 
&HttpSM::state_watch_for_client_abort;
+        _ua.get_entry()->vc_write_handler = 
&HttpSM::state_watch_for_client_abort;
+        _ua.get_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;
@@ -6254,7 +6113,7 @@ void
 HttpSM::do_drain_request_body(HTTPHdr &response)
 {
   int64_t content_length = 
t_state.hdr_info.client_request.get_content_length();
-  int64_t avail          = ua_txn->get_remote_reader()->read_avail();
+  int64_t avail          = _ua.get_txn()->get_remote_reader()->read_avail();
 
   if (t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING) 
{
     SMDebug("http", "Chunked body, setting the response to non-keepalive");
@@ -6266,7 +6125,7 @@ HttpSM::do_drain_request_body(HTTPHdr &response)
       SMDebug("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_txn->get_remote_reader()->consume(act_on);
+      _ua.get_txn()->get_remote_reader()->consume(act_on);
     } else {
       SMDebug("http", "entire body is not in the buffer, setting the response 
to non-keepalive");
       goto close_connection;
@@ -6276,7 +6135,7 @@ HttpSM::do_drain_request_body(HTTPHdr &response)
 
 close_connection:
   t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE;
-  ua_txn->set_close_connection(response);
+  _ua.get_txn()->set_close_connection(response);
 }
 
 void
@@ -6328,8 +6187,8 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
 
     // Next order of business if copy the remaining data from the
     //  header buffer into new buffer
-    int64_t num_body_bytes =
-      post_buffer->write(ua_txn->get_remote_reader(), chunked ? 
ua_txn->get_remote_reader()->read_avail() : post_bytes);
+    int64_t num_body_bytes = 
post_buffer->write(_ua.get_txn()->get_remote_reader(),
+                                                chunked ? 
_ua.get_txn()->get_remote_reader()->read_avail() : post_bytes);
 
     // If is_using_post_buffer has been used, then client_request_body_bytes
     // will have already been set in wait_for_full_body and there will be
@@ -6338,22 +6197,22 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
     if (client_request_body_bytes == 0) {
       client_request_body_bytes = num_body_bytes;
     }
-    ua_txn->get_remote_reader()->consume(num_body_bytes);
+    _ua.get_txn()->get_remote_reader()->consume(num_body_bytes);
     // The user agent has already sent all it has
-    if (ua_txn->is_read_closed()) {
+    if (_ua.get_txn()->is_read_closed()) {
       post_bytes = num_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");
+    p = tunnel.add_producer(_ua.get_entry()->vc, post_bytes - 
transfered_bytes, buf_start, &HttpSM::tunnel_handler_post_ua,
+                            HT_HTTP_CLIENT, "user agent post");
   }
-  ua_entry->in_tunnel = true;
+  _ua.get_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 != nullptr);
     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,
+    tunnel.add_consumer(post_transform_info.entry->vc, _ua.get_entry()->vc, 
&HttpSM::tunnel_handler_transform_write, HT_TRANSFORM,
                         "post transform");
     post_transform_info.entry->in_tunnel = true;
     break;
@@ -6368,7 +6227,8 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
                           "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");
+      tunnel.add_consumer(server_entry->vc, _ua.get_entry()->vc, 
&HttpSM::tunnel_handler_post_server, HT_HTTP_SERVER,
+                          "http server post");
     }
     server_entry->in_tunnel = true;
     break;
@@ -6381,7 +6241,7 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
 
   // The user agent and origin  may support chunked (HTTP/1.1) or not (HTTP/2)
   if (chunked) {
-    if (ua_txn->is_chunked_encoding_supported()) {
+    if (_ua.get_txn()->is_chunked_encoding_supported()) {
       if (server_txn->is_chunked_encoding_supported()) {
         tunnel.set_producer_chunking_action(p, 0, 
TCA_PASSTHRU_CHUNKED_CONTENT);
       } else {
@@ -6398,7 +6258,7 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
     }
   }
 
-  
ua_txn->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
+  
_ua.get_txn()->set_inactivity_timeout(HRTIME_SECONDS(t_state.txn_conf->transaction_no_activity_timeout_in));
   server_txn->set_inactivity_timeout(get_server_inactivity_timeout());
 
   tunnel.tunnel_run(p);
@@ -6406,7 +6266,7 @@ HttpSM::do_setup_post_tunnel(HttpVC_t to_vc_type)
   // 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.
   // HttpTunnel could broken due to bad chunked data and close all vc by 
chain_abort_all().
-  if (p->handler_state != HTTP_SM_POST_UA_FAIL && 
ua_txn->get_half_close_flag()) {
+  if (p->handler_state != HTTP_SM_POST_UA_FAIL && 
_ua.get_txn()->get_half_close_flag()) {
     p->vc->do_io_shutdown(IO_SHUTDOWN_READ);
   }
 }
@@ -6584,9 +6444,9 @@ HttpSM::attach_server_session()
   }
 
   // Propagate the per client IP debugging
-  if (ua_txn) {
+  if (_ua.get_txn()) {
     
server_txn->get_netvc()->control_flags.set_flags(get_cont_flags().get_flags());
-  } else { // If there is no ua_txn no sense in continuing to attach the 
server session
+  } else { // If there is no _ua.get_txn() no sense in continuing to attach 
the server session
     return;
   }
 
@@ -6645,8 +6505,8 @@ HttpSM::attach_server_session()
   server_txn->set_active_timeout(get_server_active_timeout());
 
   // Do we need Transfer_Encoding?
-  if (ua_txn->has_request_body(t_state.hdr_info.request_content_length,
-                               t_state.client_info.transfer_encoding == 
HttpTransact::CHUNKED_ENCODING)) {
+  if (_ua.get_txn()->has_request_body(t_state.hdr_info.request_content_length,
+                                      t_state.client_info.transfer_encoding == 
HttpTransact::CHUNKED_ENCODING)) {
     if (server_txn->is_chunked_encoding_supported()) {
       // See if we need to insert a chunked header
       if 
(!t_state.hdr_info.server_request.presence(MIME_PRESENCE_CONTENT_LENGTH) &&
@@ -6722,7 +6582,7 @@ HttpSM::setup_server_read_response_header()
   ink_assert(server_txn != nullptr);
   ink_assert(server_entry != nullptr);
   // REQ_FLAVOR_SCHEDULED_UPDATE can be transformed in REQ_FLAVOR_REVPROXY
-  ink_assert(ua_txn != nullptr || t_state.req_flavor == 
HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE ||
+  ink_assert(_ua.get_txn() != nullptr || t_state.req_flavor == 
HttpTransact::REQ_FLAVOR_SCHEDULED_UPDATE ||
              t_state.req_flavor == HttpTransact::REQ_FLAVOR_REVPROXY);
 
   ink_assert(server_txn != nullptr && server_txn->get_remote_reader() != 
nullptr);
@@ -6794,7 +6654,7 @@ HttpSM::setup_cache_read_transfer()
 
   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");
+  tunnel.add_consumer(_ua.get_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
@@ -6802,8 +6662,8 @@ HttpSM::setup_cache_read_transfer()
     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 = nullptr;
+  _ua.get_entry()->in_tunnel = true;
+  cache_sm.cache_read_vc     = nullptr;
   return p;
 }
 
@@ -6877,12 +6737,12 @@ HttpSM::setup_100_continue_transfer()
   // Setup the tunnel to the client
   HttpTunnelProducer *p = tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, 
client_response_hdr_bytes, buf_start,
                                               (HttpProducerHandler) nullptr, 
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,
+  tunnel.add_consumer(_ua.get_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_txn->set_half_close_flag(false);
-  ua_entry->in_tunnel = true;
+  _ua.get_txn()->set_half_close_flag(false);
+  _ua.get_entry()->in_tunnel = true;
   tunnel.tunnel_run(p);
 
   // Set up the header response read again.  Already processed the 100 response
@@ -6918,9 +6778,9 @@ HttpSM::setup_error_transfer()
     do_api_callout();
   } else {
     SMDebug("http", "Now closing connection ...");
-    vc_table.cleanup_entry(ua_entry);
-    ua_entry = nullptr;
-    // ua_txn     = NULL;
+    vc_table.cleanup_entry(_ua.get_entry());
+    _ua.set_entry(nullptr);
+    // _ua.get_txn()     = NULL;
     terminate_sm   = true;
     t_state.source = HttpTransact::SOURCE_INTERNAL;
   }
@@ -7005,7 +6865,7 @@ HttpSM::setup_internal_transfer(HttpSMHandler handler_arg)
 
   HTTP_SM_SET_DEFAULT_HANDLER(handler_arg);
 
-  if (ua_entry && ua_entry->vc) {
+  if (_ua.get_entry() && _ua.get_entry()->vc) {
     // Clear the decks before we setup the new producers
     // As things stand, we cannot have two static producers operating at
     // once
@@ -7014,9 +6874,9 @@ HttpSM::setup_internal_transfer(HttpSMHandler handler_arg)
     // Setup the tunnel to the client
     HttpTunnelProducer *p =
       tunnel.add_producer(HTTP_TUNNEL_STATIC_PRODUCER, nbytes, buf_start, 
(HttpProducerHandler) nullptr, HT_STATIC, "internal msg");
-    tunnel.add_consumer(ua_entry->vc, HTTP_TUNNEL_STATIC_PRODUCER, 
&HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent");
+    tunnel.add_consumer(_ua.get_entry()->vc, HTTP_TUNNEL_STATIC_PRODUCER, 
&HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent");
 
-    ua_entry->in_tunnel = true;
+    _ua.get_entry()->in_tunnel = true;
     tunnel.tunnel_run(p);
   } else {
     (this->*default_handler)(HTTP_TUNNEL_EVENT_DONE, &tunnel);
@@ -7147,10 +7007,10 @@ HttpSM::setup_transfer_from_transform()
                                               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");
+  tunnel.add_consumer(_ua.get_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;
+  _ua.get_entry()->in_tunnel      = true;
 
   this->setup_client_response_plugin_agents(p, client_response_hdr_bytes);
 
@@ -7240,10 +7100,10 @@ HttpSM::setup_server_transfer()
   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");
+  tunnel.add_consumer(_ua.get_entry()->vc, server_entry->vc, 
&HttpSM::tunnel_handler_ua, HT_HTTP_CLIENT, "user agent");
 
-  ua_entry->in_tunnel     = true;
-  server_entry->in_tunnel = true;
+  _ua.get_entry()->in_tunnel = true;
+  server_entry->in_tunnel    = true;
 
   this->setup_client_response_plugin_agents(p, client_response_hdr_bytes);
 
@@ -7265,11 +7125,11 @@ HttpSM::setup_push_transfer_to_cache()
   nbytes = t_state.hdr_info.request_content_length - pushed_response_hdr_bytes;
   ink_release_assert(nbytes >= 0);
 
-  if (ua_entry->eos == true) {
+  if (_ua.get_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_txn->get_remote_reader()->read_avail();
+    int64_t avail = _ua.get_txn()->get_remote_reader()->read_avail();
 
     if (avail < nbytes) {
       // Client failed to send the body, it's gone.  Kill the
@@ -7280,17 +7140,17 @@ HttpSM::setup_push_transfer_to_cache()
   }
   // Next order of business is copy the remaining data from the
   //  header buffer into new buffer.
-  pushed_response_body_bytes = buf->write(ua_txn->get_remote_reader(), nbytes);
-  ua_txn->get_remote_reader()->consume(pushed_response_body_bytes);
+  pushed_response_body_bytes = buf->write(_ua.get_txn()->get_remote_reader(), 
nbytes);
+  _ua.get_txn()->get_remote_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");
+    tunnel.add_producer(_ua.get_entry()->vc, nbytes, buf_start, 
&HttpSM::tunnel_handler_ua_push, HT_HTTP_CLIENT, "user_agent");
+  setup_cache_write_transfer(&cache_sm, _ua.get_entry()->vc, 
&t_state.cache_info.object_store, 0, "cache write");
 
-  ua_entry->in_tunnel = true;
+  _ua.get_entry()->in_tunnel = true;
   return p;
 }
 
@@ -7320,15 +7180,15 @@ HttpSM::setup_blind_tunnel(bool send_response_hdr, 
IOBufferReader *initial)
 
   int64_t nbytes = 0;
   if (t_state.txn_conf->proxy_protocol_out >= 0) {
-    nbytes = do_outbound_proxy_protocol(from_ua_buf, 
static_cast<NetVConnection *>(server_entry->vc), ua_txn->get_netvc(),
+    nbytes = do_outbound_proxy_protocol(from_ua_buf, 
static_cast<NetVConnection *>(server_entry->vc), _ua.get_txn()->get_netvc(),
                                         t_state.txn_conf->proxy_protocol_out);
   }
 
   client_request_body_bytes = nbytes;
-  if (ua_raw_buffer_reader != nullptr) {
-    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 = nullptr;
+  if (_ua.get_raw_buffer_reader() != nullptr) {
+    client_request_body_bytes += 
from_ua_buf->write(_ua.get_raw_buffer_reader(), client_request_hdr_bytes);
+    _ua.get_raw_buffer_reader()->dealloc();
+    _ua.set_raw_buffer_reader(nullptr);
   }
 
   // if pre-warmed connection is used and it has data from origin server, 
forward it to ua
@@ -7339,33 +7199,34 @@ HttpSM::setup_blind_tunnel(bool send_response_hdr, 
IOBufferReader *initial)
 
   // 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_txn->get_remote_reader());
+  client_request_body_bytes += 
from_ua_buf->write(_ua.get_txn()->get_remote_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,
+  c_ua = tunnel.add_consumer(_ua.get_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");
+  p_ua = tunnel.add_producer(_ua.get_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,
+  c_os = tunnel.add_consumer(server_entry->vc, _ua.get_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;
+  _ua.get_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_txn && ua_txn->get_half_close_flag()) {
+  if (_ua.get_txn() && _ua.get_txn()->get_half_close_flag()) {
     p_ua->vc->do_io_shutdown(IO_SHUTDOWN_READ);
   }
 }
@@ -7557,8 +7418,8 @@ HttpSM::kill_this()
       server_txn->transaction_done();
       server_txn = nullptr;
     }
-    if (ua_txn) {
-      ua_txn->transaction_done();
+    if (_ua.get_txn()) {
+      _ua.get_txn()->transaction_done();
     }
 
     // In the async state, the plugin could have been
@@ -7676,8 +7537,8 @@ HttpSM::update_stats()
     // set the fd for the request
     int fd             = 0;
     NetVConnection *vc = nullptr;
-    if (ua_txn != nullptr) {
-      vc = ua_txn->get_netvc();
+    if (_ua.get_txn() != nullptr) {
+      vc = _ua.get_txn()->get_netvc();
       if (vc != nullptr) {
         fd = vc->get_socket();
       } else {
@@ -7722,9 +7583,10 @@ HttpSM::update_stats()
           "sm_finish: %.3f "
           "plugin_active: %.3f "
           "plugin_total: %.3f",
-          sm_id, client_ip, t_state.client_info.src_addr.host_order_port(), 
ua_txn ? ua_txn->get_protocol_string() : "-1",
-          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_TLS_HANDSHAKE_START, 
TS_MILESTONE_TLS_HANDSHAKE_END),
+          sm_id, client_ip, t_state.client_info.src_addr.host_order_port(),
+          _ua.get_txn() ? _ua.get_txn()->get_protocol_string() : "-1", 
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_TLS_HANDSHAKE_START, 
TS_MILESTONE_TLS_HANDSHAKE_END),
           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),
@@ -7906,7 +7768,7 @@ HttpSM::set_next_state()
                               t_state.parent_result.result != PARENT_SPECIFIED 
&&                    // no parent.
                               t_state.client_info.is_transparent &&            
                      // inbound transparent
                               t_state.dns_info.os_addr_style == 
ResolveInfo::OS_Addr::TRY_DEFAULT && // haven't tried anything yet.
-                              ats_is_ip(addr = 
ua_txn->get_netvc()->get_local_addr()))               // valid inbound remote 
address
+                              ats_is_ip(addr = 
_ua.get_txn()->get_netvc()->get_local_addr()))        // valid inbound remote 
address
     {
       /* 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.
@@ -7962,10 +7824,11 @@ HttpSM::set_next_state()
       // 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_txn && 
!ua_txn->has_request_body(t_state.hdr_info.request_content_length,
-                                              
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
-        ua_txn->cancel_inactivity_timeout();
-      } else if (!ua_txn || ua_txn->get_netvc() == nullptr) {
+      if (_ua.get_txn() &&
+          
!_ua.get_txn()->has_request_body(t_state.hdr_info.request_content_length,
+                                           
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
+        _ua.get_txn()->cancel_inactivity_timeout();
+      } else if (!_ua.get_txn() || _ua.get_txn()->get_netvc() == nullptr) {
         terminate_sm = true;
         return; // Give up if there is no session
       }
@@ -8009,10 +7872,11 @@ HttpSM::set_next_state()
       // 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_txn && 
!ua_txn->has_request_body(t_state.hdr_info.request_content_length,
-                                              
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
-        ua_txn->cancel_inactivity_timeout();
-      } else if (!ua_txn) {
+      if (_ua.get_txn() &&
+          
!_ua.get_txn()->has_request_body(t_state.hdr_info.request_content_length,
+                                           
t_state.client_info.transfer_encoding == HttpTransact::CHUNKED_ENCODING)) {
+        _ua.get_txn()->cancel_inactivity_timeout();
+      } else if (!_ua.get_txn()) {
         terminate_sm = true;
         return; // Give up if there is no session
       }
@@ -8652,8 +8516,8 @@ HttpSM::populate_client_protocol(std::string_view 
*result, int n) const
     std::string_view proto = 
HttpSM::find_proto_string(t_state.hdr_info.client_request.version_get());
     if (!proto.empty()) {
       result[retval++] = proto;
-      if (n > retval && ua_txn) {
-        retval += ua_txn->populate_protocol(result + retval, n - retval);
+      if (n > retval && _ua.get_txn()) {
+        retval += _ua.get_txn()->populate_protocol(result + retval, n - 
retval);
       }
     }
   }
@@ -8670,8 +8534,8 @@ HttpSM::client_protocol_contains(std::string_view 
tag_prefix) const
     std::string_view prefix(tag_prefix);
     if (prefix.size() <= proto.size() && 0 == strncmp(proto.data(), 
prefix.data(), prefix.size())) {
       retval = proto.data();
-    } else if (ua_txn) {
-      retval = ua_txn->protocol_contains(prefix);
+    } else if (_ua.get_txn()) {
+      retval = _ua.get_txn()->protocol_contains(prefix);
     }
   }
   return retval;
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index 5c7da14569..accce2428e 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -38,6 +38,8 @@
 #include "I_EventSystem.h"
 #include "HttpCacheSM.h"
 #include "HttpTransact.h"
+#include "HttpUserAgent.h"
+#include "HttpVCTable.h"
 #include "UrlRewrite.h"
 #include "HttpTunnel.h"
 #include "InkAPIInternal.h"
@@ -82,16 +84,6 @@ using HttpSMHandler = int (HttpSM::*)(int, void *);
  */
 int64_t do_outbound_proxy_protocol(MIOBuffer *miob, NetVConnection *vc_out, 
NetVConnection *vc_in, int conf);
 
-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,
@@ -101,48 +93,6 @@ enum BackgroundFill_t {
 
 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_read_handler;
-  HttpSMHandler vc_write_handler;
-  HttpVC_t vc_type;
-  HttpSM *sm;
-  bool eos;
-  bool in_tunnel;
-};
-
-struct HttpVCTable {
-  static const int vc_table_max_entries = 4;
-  explicit HttpVCTable(HttpSM *);
-
-  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];
-  HttpSM *sm = nullptr;
-};
-
-inline bool
-HttpVCTable::is_table_clear() const
-{
-  for (const auto &i : vc_table) {
-    if (i.vc != nullptr) {
-      return false;
-    }
-  }
-  return true;
-}
-
 struct HttpTransformInfo {
   HttpVCTableEntry *entry = nullptr;
   VConnection *vc         = nullptr;
@@ -245,6 +195,7 @@ public:
 
   HTTPVersion get_server_version(HTTPHdr &hdr) const;
 
+  HttpUserAgent const &get_user_agent() const;
   ProxyTransaction *get_ua_txn();
   ProxyTransaction *get_server_txn();
 
@@ -347,7 +298,6 @@ public:
   UrlRewrite *m_remap = nullptr;
 
   History<HISTORY_DEFAULT_SIZE> history;
-  ProxyTransaction *ua_txn = nullptr;
 
   // _postbuf api
   int64_t postbuf_reader_avail();
@@ -535,9 +485,6 @@ public:
   int64_t client_response_body_bytes  = 0;
   int64_t cache_response_body_bytes   = 0;
   int64_t pushed_response_body_bytes  = 0;
-  bool client_tcp_reused              = false;
-  bool client_ssl_reused              = false;
-  bool client_connection_is_ssl       = false;
   bool is_internal                    = false;
   bool server_ssl_reused              = false;
   bool server_connection_is_ssl       = false;
@@ -548,13 +495,8 @@ public:
   //  do_api_callout_internal()
   bool hooks_set = false;
   std::optional<bool> mptcp_state; // Don't initialize, that marks it as "not 
defined".
-  const char *client_protocol     = "-";
-  const char *server_protocol     = "-";
-  const char *client_sec_protocol = "-";
-  const char *client_cipher_suite = "-";
-  const char *client_curve        = "-";
-  int client_alpn_id              = SessionProtocolNameRegistry::INVALID;
-  int server_transact_count       = 0;
+  const char *server_protocol = "-";
+  int server_transact_count   = 0;
 
   TransactionMilestones milestones;
   ink_hrtime api_timer = 0;
@@ -569,9 +511,7 @@ private:
 
   HttpVCTable vc_table;
 
-  IOBufferReader *ua_raw_buffer_reader = nullptr;
-
-  HttpVCTableEntry *ua_entry     = nullptr;
+  HttpUserAgent _ua{};
   HttpVCTableEntry *server_entry = nullptr;
   ProxyTransaction *server_txn   = nullptr;
 
@@ -629,12 +569,8 @@ private:
    */
   bool has_active_response_plugin_agents = false;
 
-  int _client_connection_id                   = -1;
-  int _client_transaction_id                  = -1;
-  int _client_transaction_priority_weight     = -1;
-  int _client_transaction_priority_dependence = -1;
-  SNIRoutingType _tunnel_type                 = SNIRoutingType::NONE;
-  PreWarmSM *_prewarm_sm                      = nullptr;
+  SNIRoutingType _tunnel_type = SNIRoutingType::NONE;
+  PreWarmSM *_prewarm_sm      = nullptr;
   PostDataBuffers _postbuf;
   NetVConnection *_netvc        = nullptr;
   IOBufferReader *_netvc_reader = nullptr;
@@ -679,10 +615,16 @@ HttpTransact::State::state_machine_id() const
   return state_machine->sm_id;
 }
 
+inline HttpUserAgent const &
+HttpSM::get_user_agent() const
+{
+  return _ua;
+}
+
 inline ProxyTransaction *
 HttpSM::get_ua_txn()
 {
-  return ua_txn;
+  return _ua.get_txn();
 }
 
 inline ProxyTransaction *
@@ -712,25 +654,25 @@ HttpSM::is_dying() const
 inline int
 HttpSM::client_connection_id() const
 {
-  return _client_connection_id;
+  return _ua.get_client_connection_id();
 }
 
 inline int
 HttpSM::client_transaction_id() const
 {
-  return _client_transaction_id;
+  return _ua.get_client_transaction_id();
 }
 
 inline int
 HttpSM::client_transaction_priority_weight() const
 {
-  return _client_transaction_priority_weight;
+  return _ua.get_client_transaction_priority_weight();
 }
 
 inline int
 HttpSM::client_transaction_priority_dependence() const
 {
-  return _client_transaction_priority_dependence;
+  return _ua.get_client_transaction_priority_dependence();
 }
 
 // Function to get the cache_sm object - YTS Team, yamsat
@@ -772,7 +714,8 @@ HttpSM::txn_hook_get(TSHttpHookID id)
 inline bool
 HttpSM::is_transparent_passthrough_allowed()
 {
-  return (t_state.client_info.is_transparent && 
ua_txn->is_transparent_passthrough_allowed() && ua_txn->is_first_transaction());
+  return (t_state.client_info.is_transparent && 
_ua.get_txn()->is_transparent_passthrough_allowed() &&
+          _ua.get_txn()->is_first_transaction());
 }
 
 inline int64_t
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index d2a1ba1c9a..321a2aa583 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -1034,8 +1034,8 @@ HttpTransact::EndRemapRequest(State *s)
   int host_len;
   const char *host = incoming_request->host_get(&host_len);
   TxnDebug("http_trans", "EndRemapRequest host is %.*s", host_len, host);
-  if (s->state_machine->ua_txn) {
-    
s->state_machine->ua_txn->set_default_inactivity_timeout(HRTIME_SECONDS(s->txn_conf->default_inactivity_timeout));
+  if (s->state_machine->get_ua_txn()) {
+    
s->state_machine->get_ua_txn()->set_default_inactivity_timeout(HRTIME_SECONDS(s->txn_conf->default_inactivity_timeout));
   }
 
   // Setting enable_redirection according to HttpConfig (master or 
overridable). We
@@ -1164,13 +1164,14 @@ HttpTransact::EndRemapRequest(State *s)
       s->req_flavor = REQ_FLAVOR_REVPROXY;
     }
   }
-  s->reverse_proxy              = true;
-  s->server_info.is_transparent = s->state_machine->ua_txn ? 
s->state_machine->ua_txn->is_outbound_transparent() : false;
+  s->reverse_proxy = true;
+  s->server_info.is_transparent =
+    s->state_machine->get_ua_txn() ? 
s->state_machine->get_ua_txn()->is_outbound_transparent() : false;
 
 done:
   // We now set the active-timeout again, since it might have been changed as 
part of the remap rules.
-  if (s->state_machine->ua_txn) {
-    
s->state_machine->ua_txn->set_active_timeout(HRTIME_SECONDS(s->txn_conf->transaction_active_timeout_in));
+  if (s->state_machine->get_ua_txn()) {
+    
s->state_machine->get_ua_txn()->set_active_timeout(HRTIME_SECONDS(s->txn_conf->transaction_active_timeout_in));
   }
 
   if (is_debug_tag_set("http_chdr_describe") || is_debug_tag_set("http_trans") 
|| is_debug_tag_set("url_rewrite")) {
@@ -1552,8 +1553,8 @@ HttpTransact::HandleRequest(State *s)
       }
     }
     if (s->txn_conf->request_buffer_enabled &&
-        
s->state_machine->ua_txn->has_request_body(s->hdr_info.request_content_length,
-                                                   
s->client_info.transfer_encoding == CHUNKED_ENCODING)) {
+        
s->state_machine->get_ua_txn()->has_request_body(s->hdr_info.request_content_length,
+                                                         
s->client_info.transfer_encoding == CHUNKED_ENCODING)) {
       TRANSACT_RETURN(SM_ACTION_WAIT_FOR_FULL_BODY, nullptr);
     }
   }
@@ -1886,7 +1887,7 @@ HttpTransact::OSDNSLookup(State *s)
       /* Transparent case: We tried to connect to client target address, 
failed and tried to use a different addr
        * but that failed to resolve therefore keep on with the CTA.
        */
-      
s->dns_info.addr.assign(s->state_machine->ua_txn->get_netvc()->get_local_addr());
 // fetch CTA
+      
s->dns_info.addr.assign(s->state_machine->get_ua_txn()->get_netvc()->get_local_addr());
 // fetch CTA
       s->dns_info.resolved_p    = true;
       s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_CLIENT;
       TxnDebug("http_seq", "DNS lookup unsuccessful, using client target 
address");
@@ -5388,9 +5389,9 @@ HttpTransact::check_request_validity(State *s, HTTPHdr 
*incoming_hdr)
     if ((scheme == URL_WKSIDX_HTTP || scheme == URL_WKSIDX_HTTPS) &&
         (method == HTTP_WKSIDX_POST || method == HTTP_WKSIDX_PUSH || method == 
HTTP_WKSIDX_PUT) &&
         s->client_info.transfer_encoding != CHUNKED_ENCODING) {
-      // In normal operation there will always be a ua_txn at this point, but 
in one of the -R1  regression tests a request is
+      // In normal operation there will always be a get_ua_txn() at this 
point, but in one of the -R1  regression tests a request is
       // createdindependent of a transaction and this method is called, so we 
must null check
-      if (!s->state_machine->ua_txn || 
s->state_machine->ua_txn->is_chunked_encoding_supported()) {
+      if (!s->state_machine->get_ua_txn() || 
s->state_machine->get_ua_txn()->is_chunked_encoding_supported()) {
         // See if we need to insert a chunked header
         if (!incoming_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH)) {
           if (s->txn_conf->post_check_content_length_enabled) {
@@ -5685,8 +5686,8 @@ 
HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet
   }
 
   NetVConnection *vc = nullptr;
-  if (s->state_machine->ua_txn) {
-    vc = s->state_machine->ua_txn->get_netvc();
+  if (s->state_machine->get_ua_txn()) {
+    vc = s->state_machine->get_ua_txn()->get_netvc();
   }
 
   if (vc) {
@@ -5822,7 +5823,7 @@ 
HttpTransact::initialize_state_variables_from_response(State *s, HTTPHdr *incomi
 
   if (s->current.server->keep_alive == HTTP_KEEPALIVE) {
     TxnDebug("http_hdrs", "Server is keep-alive.");
-  } else if (s->state_machine->ua_txn && 
s->state_machine->ua_txn->is_outbound_transparent() &&
+  } else if (s->state_machine->get_ua_txn() && 
s->state_machine->get_ua_txn()->is_outbound_transparent() &&
              
s->state_machine->t_state.http_config_param->use_client_source_port) {
     /* If we are reusing the client<->ATS 4-tuple for ATS<->server then if the 
server side is closed, we can't
        re-open it because the 4-tuple may still be in the processing of 
shutting down. So if the server isn't
@@ -6551,8 +6552,8 @@ HttpTransact::process_quick_http_filter(State *s, int 
method)
     return;
   }
 
-  if (s->state_machine->ua_txn) {
-    auto &acl              = s->state_machine->ua_txn->get_acl();
+  if (s->state_machine->get_ua_txn()) {
+    auto &acl              = s->state_machine->get_ua_txn()->get_acl();
     bool deny_request      = !acl.isValid();
     int method_str_len     = 0;
     const char *method_str = nullptr;
@@ -6959,9 +6960,9 @@ HttpTransact::handle_response_keep_alive_headers(State 
*s, HTTPVersion ver, HTTP
 
     // check that the client protocol is HTTP/1.1 and the conf allows chunking 
or
     // the client protocol doesn't support chunked transfer coding (i.e. 
HTTP/1.0, HTTP/2, and HTTP/3)
-    if (s->state_machine->ua_txn && 
s->state_machine->ua_txn->is_chunked_encoding_supported() &&
+    if (s->state_machine->get_ua_txn() && 
s->state_machine->get_ua_txn()->is_chunked_encoding_supported() &&
         s->client_info.http_version == HTTP_1_1 && 
s->txn_conf->chunking_enabled == 1 &&
-        s->state_machine->ua_txn->is_chunked_encoding_supported() &&
+        s->state_machine->get_ua_txn()->is_chunked_encoding_supported() &&
         // if we're not sending a body, don't set a chunked header regardless 
of server response
         !is_response_body_precluded(s->hdr_info.client_response.status_get(), 
s->method) &&
         // we do not need chunked encoding for internal error messages
@@ -6997,8 +6998,8 @@ HttpTransact::handle_response_keep_alive_headers(State 
*s, HTTPVersion ver, HTTP
     // unless we are going to use chunked encoding on HTTP/1.1 or the client 
issued a PUSH request
     if (s->client_info.keep_alive != HTTP_KEEPALIVE) {
       ka_action = KA_DISABLED;
-    } else if (s->hdr_info.trust_response_cl == false && 
s->state_machine->ua_txn &&
-               s->state_machine->ua_txn->is_chunked_encoding_supported() &&
+    } else if (s->hdr_info.trust_response_cl == false && 
s->state_machine->get_ua_txn() &&
+               s->state_machine->get_ua_txn()->is_chunked_encoding_supported() 
&&
                !(s->client_info.receive_chunked_response == true ||
                  (s->method == HTTP_WKSIDX_PUSH && s->client_info.keep_alive 
== HTTP_KEEPALIVE))) {
       ka_action = KA_CLOSE;
@@ -7024,8 +7025,8 @@ HttpTransact::handle_response_keep_alive_headers(State 
*s, HTTPVersion ver, HTTP
     if (s->client_info.keep_alive != HTTP_NO_KEEPALIVE || (ver == HTTP_1_1)) {
       if (s->client_info.proxy_connect_hdr) {
         heads->value_set(c_hdr_field_str, c_hdr_field_len, "close", 5);
-      } else if (s->state_machine->ua_txn != nullptr) {
-        s->state_machine->ua_txn->set_close_connection(*heads);
+      } else if (s->state_machine->get_ua_txn() != nullptr) {
+        s->state_machine->get_ua_txn()->set_close_connection(*heads);
       }
       s->client_info.keep_alive = HTTP_NO_KEEPALIVE;
     }
@@ -7986,7 +7987,7 @@ HttpTransact::build_response(State *s, HTTPHdr 
*base_response, HTTPHdr *outgoing
 
   HttpTransactHeaders::add_server_header_to_response(s->txn_conf, 
outgoing_response);
 
-  if (s->state_machine->ua_txn && 
s->state_machine->ua_txn->get_proxy_ssn()->is_draining()) {
+  if (s->state_machine->get_ua_txn() && 
s->state_machine->get_ua_txn()->get_proxy_ssn()->is_draining()) {
     HttpTransactHeaders::add_connection_close(outgoing_response);
   }
 
@@ -8057,7 +8058,7 @@ HttpTransact::build_error_response(State *s, HTTPStatus 
status_code, const char
   }
   // If transparent and the forward server connection looks unhappy don't
   // keep alive the ua connection.
-  if ((s->state_machine->ua_txn && 
s->state_machine->ua_txn->is_outbound_transparent()) &&
+  if ((s->state_machine->get_ua_txn() && 
s->state_machine->get_ua_txn()->is_outbound_transparent()) &&
       (status_code == HTTP_STATUS_INTERNAL_SERVER_ERROR || status_code == 
HTTP_STATUS_GATEWAY_TIMEOUT ||
        status_code == HTTP_STATUS_BAD_GATEWAY || status_code == 
HTTP_STATUS_SERVICE_UNAVAILABLE)) {
     s->client_info.keep_alive = HTTP_NO_KEEPALIVE;
diff --git a/proxy/http/HttpTransactHeaders.cc 
b/proxy/http/HttpTransactHeaders.cc
index 3b956e1e54..cd8fb48867 100644
--- a/proxy/http/HttpTransactHeaders.cc
+++ b/proxy/http/HttpTransactHeaders.cc
@@ -978,8 +978,8 @@ 
HttpTransactHeaders::add_forwarded_field_to_request(HttpTransact::State *s, HTTP
     ts::LocalBufferWriter<1024> hdr;
 
     IpEndpoint src_addr = s->client_info.src_addr;
-    if (s->state_machine->ua_txn && s->state_machine->ua_txn->get_netvc()) {
-      const ProxyProtocol &pp = 
s->state_machine->ua_txn->get_netvc()->get_proxy_protocol_info();
+    if (s->state_machine->get_ua_txn() && 
s->state_machine->get_ua_txn()->get_netvc()) {
+      const ProxyProtocol &pp = 
s->state_machine->get_ua_txn()->get_netvc()->get_proxy_protocol_info();
 
       if (pp.version != ProxyProtocolVersion::UNDEFINED) {
         src_addr = pp.src_addr;
diff --git a/proxy/http/HttpUserAgent.h b/proxy/http/HttpUserAgent.h
new file mode 100644
index 0000000000..d0dfcdf413
--- /dev/null
+++ b/proxy/http/HttpUserAgent.h
@@ -0,0 +1,268 @@
+/** @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.
+ */
+
+#pragma once
+
+#include "HttpVCTable.h"
+#include "Milestones.h"
+
+#include "I_IOBuffer.h"
+#include "P_ALPNSupport.h"
+#include "ProxyTransaction.h"
+#include "records/I_RecHttp.h"
+#include "TLSBasicSupport.h"
+#include "TLSSessionResumptionSupport.h"
+
+struct ClientTransactionInfo {
+  int id{-1};
+  int priority_weight{-1};
+  int priority_dependence{-1};
+};
+
+struct ClientConnectionInfo {
+  bool tcp_reused{false};
+  bool ssl_reused{false};
+  bool connection_is_ssl{false};
+
+  char const *protocol{"-"};
+  char const *sec_protocol{"-"};
+  char const *cipher_suite{"-"};
+  char const *curve{"-"};
+
+  int alpn_id{SessionProtocolNameRegistry::INVALID};
+};
+
+class HttpUserAgent
+{
+public:
+  HttpVCTableEntry *get_entry() const;
+  void set_entry(HttpVCTableEntry *entry);
+
+  IOBufferReader *get_raw_buffer_reader();
+  void set_raw_buffer_reader(IOBufferReader *raw_buffer_reader);
+
+  ProxyTransaction *get_txn() const;
+  void set_txn(ProxyTransaction *txn, TransactionMilestones &milestones);
+
+  int get_client_connection_id() const;
+
+  int get_client_transaction_id() const;
+
+  int get_client_transaction_priority_weight() const;
+
+  int get_client_transaction_priority_dependence() const;
+
+  bool get_client_tcp_reused() const;
+
+  bool get_client_ssl_reused() const;
+
+  bool get_client_connection_is_ssl() const;
+
+  char const *get_client_protocol() const;
+
+  char const *get_client_sec_protocol() const;
+
+  char const *get_client_cipher_suite() const;
+
+  char const *get_client_curve() const;
+
+  int get_client_alpn_id() const;
+
+private:
+  HttpVCTableEntry *m_entry{nullptr};
+  IOBufferReader *m_raw_buffer_reader{nullptr};
+  ProxyTransaction *m_txn{nullptr};
+
+  ClientConnectionInfo m_conn_info{};
+
+  int m_client_connection_id{-1};
+  ClientTransactionInfo m_txn_info{};
+
+  void save_transaction_info();
+};
+
+inline HttpVCTableEntry *
+HttpUserAgent::get_entry() const
+{
+  return m_entry;
+}
+
+inline void
+HttpUserAgent::set_entry(HttpVCTableEntry *entry)
+{
+  m_entry = entry;
+}
+
+inline IOBufferReader *
+HttpUserAgent::get_raw_buffer_reader()
+{
+  return m_raw_buffer_reader;
+}
+
+inline void
+HttpUserAgent::set_raw_buffer_reader(IOBufferReader *raw_buffer_reader)
+{
+  m_raw_buffer_reader = raw_buffer_reader;
+}
+
+inline ProxyTransaction *
+HttpUserAgent::get_txn() const
+{
+  return m_txn;
+}
+
+inline void
+HttpUserAgent::set_txn(ProxyTransaction *txn, TransactionMilestones 
&milestones)
+{
+  m_txn = txn;
+
+  // It seems to be possible that the m_txn pointer will go stale before log
+  // entries for this HTTP transaction are generated. Therefore, collect
+  // information that may be needed for logging.
+  this->save_transaction_info();
+  if (auto p{txn->get_proxy_ssn()}; p) {
+    m_client_connection_id = p->connection_id();
+  }
+
+  m_conn_info.tcp_reused = !txn->is_first_transaction();
+
+  auto netvc{txn->get_netvc()};
+
+  if (auto tbs = netvc->get_service<TLSBasicSupport>()) {
+    m_conn_info.connection_is_ssl = true;
+    if (auto sec_protocol{tbs->get_tls_protocol_name()}; sec_protocol) {
+      m_conn_info.sec_protocol = sec_protocol;
+    } else {
+      m_conn_info.sec_protocol = "-";
+    }
+    if (auto cipher{tbs->get_tls_cipher_suite()}; cipher) {
+      m_conn_info.cipher_suite = cipher;
+    } else {
+      m_conn_info.cipher_suite = "-";
+    }
+    if (auto curve{tbs->get_tls_curve()}; curve) {
+      m_conn_info.curve = curve;
+    } else {
+      m_conn_info.curve = "-";
+    }
+
+    if (!m_conn_info.tcp_reused) {
+      // Copy along the TLS handshake timings
+      milestones[TS_MILESTONE_TLS_HANDSHAKE_START] = 
tbs->get_tls_handshake_begin_time();
+      milestones[TS_MILESTONE_TLS_HANDSHAKE_END]   = 
tbs->get_tls_handshake_end_time();
+    }
+  }
+
+  if (auto as = netvc->get_service<ALPNSupport>()) {
+    m_conn_info.alpn_id = as->get_negotiated_protocol_id();
+  }
+
+  if (auto tsrs = netvc->get_service<TLSSessionResumptionSupport>()) {
+    m_conn_info.ssl_reused = tsrs->getSSLSessionCacheHit();
+  }
+
+  if (auto protocol_str{txn->get_protocol_string()}; protocol_str) {
+    m_conn_info.protocol = protocol_str;
+  } else {
+    m_conn_info.protocol = "-";
+  }
+}
+
+inline int
+HttpUserAgent::get_client_connection_id() const
+{
+  return m_client_connection_id;
+}
+
+inline int
+HttpUserAgent::get_client_transaction_id() const
+{
+  return m_txn_info.id;
+}
+
+inline int
+HttpUserAgent::get_client_transaction_priority_weight() const
+{
+  return m_txn_info.priority_weight;
+}
+
+inline int
+HttpUserAgent::get_client_transaction_priority_dependence() const
+{
+  return m_txn_info.priority_dependence;
+}
+
+inline bool
+HttpUserAgent::get_client_tcp_reused() const
+{
+  return m_conn_info.tcp_reused;
+}
+
+inline bool
+HttpUserAgent::get_client_ssl_reused() const
+{
+  return m_conn_info.ssl_reused;
+}
+
+inline bool
+HttpUserAgent::get_client_connection_is_ssl() const
+{
+  return m_conn_info.connection_is_ssl;
+}
+
+inline char const *
+HttpUserAgent::get_client_protocol() const
+{
+  return m_conn_info.protocol;
+}
+
+inline char const *
+HttpUserAgent::get_client_sec_protocol() const
+{
+  return m_conn_info.sec_protocol;
+}
+
+inline char const *
+HttpUserAgent::get_client_cipher_suite() const
+{
+  return m_conn_info.cipher_suite;
+}
+
+inline char const *
+HttpUserAgent::get_client_curve() const
+{
+  return m_conn_info.curve;
+}
+
+inline int
+HttpUserAgent::get_client_alpn_id() const
+{
+  return m_conn_info.alpn_id;
+}
+
+inline void
+HttpUserAgent::save_transaction_info()
+{
+  m_txn_info = {m_txn->get_transaction_id(), 
m_txn->get_transaction_priority_weight(),
+                m_txn->get_transaction_priority_dependence()};
+}
diff --git a/proxy/http/HttpVCTable.cc b/proxy/http/HttpVCTable.cc
new file mode 100644
index 0000000000..09efb4b3b9
--- /dev/null
+++ b/proxy/http/HttpVCTable.cc
@@ -0,0 +1,136 @@
+/** @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 "HttpConfig.h"
+#include "HttpVCTable.h"
+
+#include "I_VConnection.h"
+#include "I_VIO.h"
+#include "tscore/ink_assert.h"
+
+class HttpSM;
+
+HttpVCTable::HttpVCTable(HttpSM *mysm)
+{
+  memset(&vc_table, 0, sizeof(vc_table));
+  sm = mysm;
+}
+
+HttpVCTableEntry *
+HttpVCTable::new_entry()
+{
+  for (int i = 0; i < vc_table_max_entries; i++) {
+    if (vc_table[i].vc == nullptr) {
+      vc_table[i].sm = sm;
+      return vc_table + i;
+    }
+  }
+
+  ink_release_assert(0);
+  return nullptr;
+}
+
+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 nullptr;
+}
+
+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 != nullptr);
+      return vc_table + i;
+    }
+  }
+
+  return nullptr;
+}
+
+// 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)
+{
+  e->vc  = nullptr;
+  e->eos = false;
+  if (e->read_buffer) {
+    free_MIOBuffer(e->read_buffer);
+    e->read_buffer = nullptr;
+  }
+  if (e->write_buffer) {
+    free_MIOBuffer(e->write_buffer);
+    e->write_buffer = nullptr;
+  }
+  // Cannot reach in to checkout the netvc
+  // for remaining I/O operations because the netvc
+  // may have been deleted at this point and the pointer
+  // could be stale.
+  e->read_vio         = nullptr;
+  e->write_vio        = nullptr;
+  e->vc_read_handler  = nullptr;
+  e->vc_write_handler = nullptr;
+  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) {
+    if (e->vc_type == HTTP_SERVER_VC) {
+      HTTP_INCREMENT_DYN_STAT(http_origin_shutdown_cleanup_entry);
+    }
+    e->vc->do_io_close();
+    e->vc = nullptr;
+  }
+  remove_entry(e);
+}
+
+void
+HttpVCTable::cleanup_all()
+{
+  for (int i = 0; i < vc_table_max_entries; i++) {
+    if (vc_table[i].vc != nullptr) {
+      cleanup_entry(vc_table + i);
+    }
+  }
+}
diff --git a/proxy/http/HttpVCTable.h b/proxy/http/HttpVCTable.h
new file mode 100644
index 0000000000..9c257e66cb
--- /dev/null
+++ b/proxy/http/HttpVCTable.h
@@ -0,0 +1,83 @@
+/** @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.
+ */
+
+#pragma once
+
+#include "I_IOBuffer.h"
+#include "I_VConnection.h"
+#include "I_VIO.h"
+
+class HttpSM;
+using HttpSMHandler = int (HttpSM::*)(int, void *);
+
+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
+};
+
+struct HttpVCTableEntry {
+  VConnection *vc;
+  MIOBuffer *read_buffer;
+  MIOBuffer *write_buffer;
+  VIO *read_vio;
+  VIO *write_vio;
+  HttpSMHandler vc_read_handler;
+  HttpSMHandler vc_write_handler;
+  HttpVC_t vc_type;
+  HttpSM *sm;
+  bool eos;
+  bool in_tunnel;
+};
+
+struct HttpVCTable {
+  static const int vc_table_max_entries = 4;
+  explicit HttpVCTable(HttpSM *);
+
+  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];
+  HttpSM *sm = nullptr;
+};
+
+inline bool
+HttpVCTable::is_table_clear() const
+{
+  for (const auto &i : vc_table) {
+    if (i.vc != nullptr) {
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am
index e1d5d6ad65..954d8ef5a3 100644
--- a/proxy/http/Makefile.am
+++ b/proxy/http/Makefile.am
@@ -80,6 +80,9 @@ libhttp_a_SOURCES = \
        HttpTransactHeaders.h \
        HttpTunnel.cc \
        HttpTunnel.h \
+       HttpUserAgent.h \
+       HttpVCTable.cc \
+       HttpVCTable.h \
        ForwardedConfig.cc \
        PreWarmConfig.cc \
        PreWarmManager.cc
@@ -124,6 +127,7 @@ test_PreWarm_LDADD = \
        @YAMLCPP_LIBS@
 
 test_PreWarm_SOURCES = \
+       unit_tests/unit_test_main.cc \
        unit_tests/test_PreWarm.cc
 
 test_HttpTransact_CPPFLAGS = \
diff --git a/proxy/http/remap/UrlRewrite.cc b/proxy/http/remap/UrlRewrite.cc
index 3f3f15bd28..60f751b51b 100644
--- a/proxy/http/remap/UrlRewrite.cc
+++ b/proxy/http/remap/UrlRewrite.cc
@@ -449,7 +449,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, 
url_mapping *map)
         match = false;
         for (int j = 0; j < rp->in_ip_cnt && !match; j++) {
           IpEndpoint incoming_addr;
-          
incoming_addr.assign(s->state_machine->ua_txn->get_netvc()->get_local_addr());
+          
incoming_addr.assign(s->state_machine->get_ua_txn()->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));
@@ -471,7 +471,7 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, 
url_mapping *map)
       }
 
       if (rp->internal) {
-        match = 
s->state_machine->ua_txn->get_netvc()->get_is_internal_request();
+        match = 
s->state_machine->get_ua_txn()->get_netvc()->get_is_internal_request();
         Debug("url_rewrite", "%s an internal request", match ? "matched" : 
"didn't match");
       }
 
diff --git a/proxy/http/unit_tests/CMakeLists.txt 
b/proxy/http/unit_tests/CMakeLists.txt
index b90f46e116..6d0f0958cc 100644
--- a/proxy/http/unit_tests/CMakeLists.txt
+++ b/proxy/http/unit_tests/CMakeLists.txt
@@ -15,17 +15,40 @@
 #
 #######################
 
-add_executable(test_http test_ForwardedConfig.cc test_PreWarm.cc)
+add_executable(test_http
+    main.cc
+    "${PROJECT_SOURCE_DIR}/iocore/cache/test/stub.cc"
+    test_error_page_selection.cc
+    test_ForwardedConfig.cc
+    test_HttpTransact.cc
+    test_HttpUserAgent.cc
+    test_PreWarm.cc
+)
 
 # transitive
-target_include_directories(test_http PRIVATE "${PROJECT_SOURCE_DIR}/mgmt")
+target_include_directories(test_http
+    PRIVATE
+        "${PROJECT_BINARY_DIR}/include/ts"
+        "${PROJECT_SOURCE_DIR}/mgmt"
+)
 
 target_link_libraries(test_http
     PRIVATE
         catch2::catch2
         ts::http
+        ts::hdrs # transitive
+        ts::inkutils # transitive
+        inkcache # transitive
+        inkhostdb # transitive
+        logging # transitive
         ts::inkevent # transitive
         ts::proxy # transitive
+        http_remap # transitive
+        ts::proxy # transitive
+        aio # transitive
+        inkdns # transitive
+        fastlz # transitive
+        ts::inknet
 )
 
 add_test(NAME test_http COMMAND $<TARGET_FILE:test_http>)
diff --git a/proxy/http/unit_tests/test_HttpUserAgent.cc 
b/proxy/http/unit_tests/test_HttpUserAgent.cc
new file mode 100644
index 0000000000..e330deefdf
--- /dev/null
+++ b/proxy/http/unit_tests/test_HttpUserAgent.cc
@@ -0,0 +1,93 @@
+/** @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 "Http1ClientSession.h"
+#include "Http1ClientTransaction.h"
+#include "HttpSessionAccept.h"
+#include "HttpUserAgent.h"
+#include "I_VConnection.h"
+#include "Milestones.h"
+#include "P_SSLNetVConnection.h"
+
+#include <catch.hpp>
+
+#include <cstring>
+
+class Http1ClientTestSession final : public Http1ClientSession
+{
+public:
+  int get_transact_count() const override;
+  void set_transact_count(int count);
+  void set_vc(NetVConnection *new_vc);
+
+private:
+  int m_transact_count{0};
+};
+
+int
+Http1ClientTestSession::get_transact_count() const
+{
+  return m_transact_count;
+}
+
+void
+Http1ClientTestSession::set_transact_count(int count)
+{
+  m_transact_count = count;
+}
+
+void
+Http1ClientTestSession::set_vc(NetVConnection *new_vc)
+{
+  _vc = new_vc;
+};
+
+TEST_CASE("tcp_reused should be set correctly when a session is attached.")
+{
+  HttpUserAgent user_agent;
+  TransactionMilestones milestones;
+
+  Http1ClientTestSession ssn;
+  SSLNetVConnection netvc;
+  ssn.set_vc(&netvc);
+  HttpSessionAccept::Options options;
+  ssn.accept_options = &options;
+  Http1ClientTransaction txn{&ssn};
+
+  SECTION("When a transaction is the first one, "
+          "then tcp_reused should be false.")
+  {
+    ssn.set_transact_count(1);
+    user_agent.set_txn(&txn, milestones);
+    CHECK(user_agent.get_client_tcp_reused() == false);
+  }
+
+  SECTION("When a transaction is the second one, "
+          "then tcp_reused should be true.")
+  {
+    ssn.set_transact_count(2);
+    user_agent.set_txn(&txn, milestones);
+    CHECK(user_agent.get_client_tcp_reused() == true);
+  }
+}
diff --git a/proxy/http/unit_tests/test_PreWarm.cc 
b/proxy/http/unit_tests/test_PreWarm.cc
index 3f22b8909d..aa2cc88e9b 100644
--- a/proxy/http/unit_tests/test_PreWarm.cc
+++ b/proxy/http/unit_tests/test_PreWarm.cc
@@ -23,7 +23,6 @@
 
 #include "PreWarmAlgorithm.h"
 
-#define CATCH_CONFIG_MAIN
 #include "catch.hpp"
 
 TEST_CASE("PreWarm Algorithm", "[prewarm]")
diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc
index cef8ab27a5..f853af57c8 100644
--- a/proxy/logging/LogAccess.cc
+++ b/proxy/logging/LogAccess.cc
@@ -1874,7 +1874,7 @@ LogAccess::marshal_client_req_http_version(char *buf)
 int
 LogAccess::marshal_client_req_protocol_version(char *buf)
 {
-  const char *protocol_str = m_http_sm->client_protocol;
+  const char *protocol_str = m_http_sm->get_user_agent().get_client_protocol();
   int len                  = LogAccess::strlen(protocol_str);
 
   // Set major & minor versions when protocol_str is not "http/2".
@@ -1984,7 +1984,7 @@ int
 LogAccess::marshal_client_req_tcp_reused(char *buf)
 {
   if (buf) {
-    marshal_int(buf, m_http_sm->client_tcp_reused ? 1 : 0);
+    marshal_int(buf, m_http_sm->get_user_agent().get_client_tcp_reused() ? 1 : 
0);
   }
   return INK_MIN_ALIGN;
 }
@@ -1993,7 +1993,7 @@ int
 LogAccess::marshal_client_req_is_ssl(char *buf)
 {
   if (buf) {
-    marshal_int(buf, m_http_sm->client_connection_is_ssl ? 1 : 0);
+    marshal_int(buf, 
m_http_sm->get_user_agent().get_client_connection_is_ssl() ? 1 : 0);
   }
   return INK_MIN_ALIGN;
 }
@@ -2002,7 +2002,7 @@ int
 LogAccess::marshal_client_req_ssl_reused(char *buf)
 {
   if (buf) {
-    marshal_int(buf, m_http_sm->client_ssl_reused ? 1 : 0);
+    marshal_int(buf, m_http_sm->get_user_agent().get_client_ssl_reused() ? 1 : 
0);
   }
   return INK_MIN_ALIGN;
 }
@@ -2125,7 +2125,7 @@ LogAccess::marshal_client_tx_error_code(char *buf)
 int
 LogAccess::marshal_client_security_protocol(char *buf)
 {
-  const char *proto = m_http_sm->client_sec_protocol;
+  const char *proto = m_http_sm->get_user_agent().get_client_sec_protocol();
   int round_len     = LogAccess::strlen(proto);
 
   if (buf) {
@@ -2138,7 +2138,7 @@ LogAccess::marshal_client_security_protocol(char *buf)
 int
 LogAccess::marshal_client_security_cipher_suite(char *buf)
 {
-  const char *cipher = m_http_sm->client_cipher_suite;
+  const char *cipher = m_http_sm->get_user_agent().get_client_cipher_suite();
   int round_len      = LogAccess::strlen(cipher);
 
   if (buf) {
@@ -2151,7 +2151,7 @@ LogAccess::marshal_client_security_cipher_suite(char *buf)
 int
 LogAccess::marshal_client_security_curve(char *buf)
 {
-  const char *curve = m_http_sm->client_curve;
+  const char *curve = m_http_sm->get_user_agent().get_client_curve();
   int round_len     = LogAccess::strlen(curve);
 
   if (buf) {
@@ -2165,7 +2165,7 @@ int
 LogAccess::marshal_client_security_alpn(char *buf)
 {
   const char *alpn = "-";
-  if (const int alpn_id = m_http_sm->client_alpn_id; alpn_id != 
SessionProtocolNameRegistry::INVALID) {
+  if (const int alpn_id = m_http_sm->get_user_agent().get_client_alpn_id(); 
alpn_id != SessionProtocolNameRegistry::INVALID) {
     ts::TextView client_sec_alpn = 
globalSessionProtocolNameRegistry.nameFor(alpn_id);
     alpn                         = client_sec_alpn.data();
   }
diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc
index 3afca1e576..ea052c77c7 100644
--- a/src/traffic_server/InkAPI.cc
+++ b/src/traffic_server/InkAPI.cc
@@ -5079,7 +5079,7 @@ TSHttpTxnSsnGet(TSHttpTxn txnp)
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
 
   HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-  return reinterpret_cast<TSHttpSsn>(sm->ua_txn ? 
(TSHttpSsn)sm->ua_txn->get_proxy_ssn() : nullptr);
+  return reinterpret_cast<TSHttpSsn>(sm->get_ua_txn() ? 
(TSHttpSsn)sm->get_ua_txn()->get_proxy_ssn() : nullptr);
 }
 
 // TODO: Is this still necessary ??
@@ -5887,8 +5887,8 @@ TSHttpTxnOutgoingAddrSet(TSHttpTxn txnp, const struct 
sockaddr *addr)
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
   HttpSM *sm = (HttpSM *)txnp;
 
-  sm->ua_txn->upstream_outbound_options.outbound_port = 
ats_ip_port_host_order(addr);
-  sm->ua_txn->set_outbound_ip(swoc::IPAddr(addr));
+  sm->get_ua_txn()->upstream_outbound_options.outbound_port = 
ats_ip_port_host_order(addr);
+  sm->get_ua_txn()->set_outbound_ip(swoc::IPAddr(addr));
   return TS_SUCCESS;
 }
 
@@ -5948,11 +5948,11 @@ TSHttpTxnOutgoingTransparencySet(TSHttpTxn txnp, int 
flag)
   }
 
   HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-  if (nullptr == sm || nullptr == sm->ua_txn) {
+  if (nullptr == sm || nullptr == sm->get_ua_txn()) {
     return TS_ERROR;
   }
 
-  sm->ua_txn->set_outbound_transparent(flag);
+  sm->get_ua_txn()->set_outbound_transparent(flag);
   return TS_SUCCESS;
 }
 
@@ -5961,11 +5961,11 @@ TSHttpTxnClientPacketMarkSet(TSHttpTxn txnp, int mark)
 {
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
   HttpSM *sm = (HttpSM *)txnp;
-  if (nullptr == sm->ua_txn) {
+  if (nullptr == sm->get_ua_txn()) {
     return TS_ERROR;
   }
 
-  NetVConnection *vc = sm->ua_txn->get_netvc();
+  NetVConnection *vc = sm->get_ua_txn()->get_netvc();
   if (nullptr == vc) {
     return TS_ERROR;
   }
@@ -6001,11 +6001,11 @@ TSHttpTxnClientPacketDscpSet(TSHttpTxn txnp, int dscp)
 {
   sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
   HttpSM *sm = (HttpSM *)txnp;
-  if (nullptr == sm->ua_txn) {
+  if (nullptr == sm->get_ua_txn()) {
     return TS_ERROR;
   }
 
-  NetVConnection *vc = sm->ua_txn->get_netvc();
+  NetVConnection *vc = sm->get_ua_txn()->get_netvc();
   if (nullptr == vc) {
     return TS_ERROR;
   }
@@ -8173,7 +8173,7 @@ TSHttpTxnServerPush(TSHttpTxn txnp, const char *url, int 
url_len)
   }
 
   HttpSM *sm          = reinterpret_cast<HttpSM *>(txnp);
-  Http2Stream *stream = dynamic_cast<Http2Stream *>(sm->ua_txn);
+  Http2Stream *stream = dynamic_cast<Http2Stream *>(sm->get_ua_txn());
   if (stream == nullptr) {
     url_obj.destroy();
     return TS_ERROR;
@@ -8209,7 +8209,7 @@ TSHttpTxnClientStreamIdGet(TSHttpTxn txnp, uint64_t 
*stream_id)
   sdk_assert(stream_id != nullptr);
 
   auto *sm     = reinterpret_cast<HttpSM *>(txnp);
-  auto *stream = dynamic_cast<Http2Stream *>(sm->ua_txn);
+  auto *stream = dynamic_cast<Http2Stream *>(sm->get_ua_txn());
   if (stream == nullptr) {
     return TS_ERROR;
   }
@@ -8226,7 +8226,7 @@ TSHttpTxnClientStreamPriorityGet(TSHttpTxn txnp, 
TSHttpPriority *priority)
   sdk_assert(priority != nullptr);
 
   auto *sm     = reinterpret_cast<HttpSM *>(txnp);
-  auto *stream = dynamic_cast<Http2Stream *>(sm->ua_txn);
+  auto *stream = dynamic_cast<Http2Stream *>(sm->get_ua_txn());
   if (stream == nullptr) {
     return TS_ERROR;
   }
@@ -9122,7 +9122,7 @@ TSHttpTxnCloseAfterResponse(TSHttpTxn txnp, int 
should_close)
   HttpSM *sm = (HttpSM *)txnp;
   if (should_close) {
     sm->t_state.client_info.keep_alive = HTTP_NO_KEEPALIVE;
-    if (sm->ua_txn) {
+    if (sm->get_ua_txn()) {
       sm->set_ua_half_close_flag();
     }
   }


Reply via email to