You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2019/04/09 07:16:20 UTC

[trafficserver] branch quic-latest updated: Add HTTP/3 support to traffic_quic cmd

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

masaori pushed a commit to branch quic-latest
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/quic-latest by this push:
     new 41c06df  Add HTTP/3 support to traffic_quic cmd
41c06df is described below

commit 41c06df698844c80d2dd146b8ce5fb7cc5853c91
Author: Masaori Koshiba <ma...@gmail.com>
AuthorDate: Fri Apr 5 10:12:57 2019 +0900

    Add HTTP/3 support to traffic_quic cmd
---
 iocore/net/I_NetVConnection.h         |   6 +
 iocore/net/QUICNetVConnection.cc      |   3 +-
 iocore/net/quic/QUICConfig.cc         |  13 +-
 iocore/net/quic/QUICTLS.h             |   2 +-
 iocore/net/quic/QUICTLS_openssl.cc    |   5 +-
 proxy/http2/HTTP2.cc                  |   8 ++
 proxy/http3/Http3App.cc               |  59 ++++++---
 proxy/http3/Http3App.h                |  16 ++-
 proxy/http3/Http3ClientTransaction.cc |  60 ++++++---
 proxy/http3/Http3ClientTransaction.h  |   3 +-
 proxy/http3/Http3HeaderFramer.cc      |  52 ++------
 proxy/http3/Http3HeaderVIOAdaptor.cc  |   7 +-
 src/traffic_quic/Makefile.inc         |  66 +++++-----
 src/traffic_quic/quic_client.cc       | 237 ++++++++++++++++++++++++++--------
 src/traffic_quic/quic_client.h        |  47 ++++++-
 src/traffic_quic/traffic_quic.cc      | 105 +++++++++++++--
 16 files changed, 489 insertions(+), 200 deletions(-)

diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h
index 786c7af..1dedc0d 100644
--- a/iocore/net/I_NetVConnection.h
+++ b/iocore/net/I_NetVConnection.h
@@ -179,6 +179,10 @@ struct NetVCOptions {
 
   EventType etype;
 
+  /** ALPN protocol-lists. The format is OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
+      https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
+   */
+  std::string_view alpn_protos;
   /** Server name to use for SNI data on an outbound connection.
    */
   ats_scoped_str sni_servername;
@@ -224,6 +228,7 @@ struct NetVCOptions {
 
   NetVCOptions() { reset(); }
   ~NetVCOptions() {}
+
   /** Set the SNI server name.
       A local copy is made of @a name.
   */
@@ -240,6 +245,7 @@ struct NetVCOptions {
     }
     return *this;
   }
+
   self &
   set_ssl_servername(const char *name)
   {
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index 1e25171..bcc581d 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -2236,7 +2236,8 @@ QUICNetVConnection::_setup_handshake_protocol(SSL_CTX *ctx)
 {
   // Initialize handshake protocol specific stuff
   // For QUICv1 TLS is the only option
-  QUICTLS *tls = new QUICTLS(this->_pp_key_info, ctx, this->direction(), this->_quic_config->session_file());
+  QUICTLS *tls =
+    new QUICTLS(this->_pp_key_info, ctx, this->direction(), this->options, this->_quic_config->session_file());
   SSL_set_ex_data(tls->ssl_handle(), QUIC::ssl_quic_qc_index, static_cast<QUICConnection *>(this));
   return tls;
 }
diff --git a/iocore/net/quic/QUICConfig.cc b/iocore/net/quic/QUICConfig.cc
index 139df1a..4314caa 100644
--- a/iocore/net/quic/QUICConfig.cc
+++ b/iocore/net/quic/QUICConfig.cc
@@ -35,11 +35,6 @@
 
 #define QUICConfDebug(fmt, ...) Debug("quic_conf", fmt, ##__VA_ARGS__)
 
-// OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
-// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
-// Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ?
-using namespace std::literals;
-static constexpr std::string_view QUIC_ALPN_PROTO_LIST("\5hq-18"sv);
 
 int QUICConfig::_config_id                   = 0;
 int QUICConfigParams::_connection_table_size = 65521;
@@ -74,16 +69,14 @@ quic_new_ssl_ctx()
   return ssl_ctx;
 }
 
+/**
+   ALPN and SNI should be set to SSL object with NETVC_OPTIONS
+ **/
 static SSL_CTX *
 quic_init_client_ssl_ctx(const QUICConfigParams *params)
 {
   SSL_CTX *ssl_ctx = quic_new_ssl_ctx();
 
-  if (SSL_CTX_set_alpn_protos(ssl_ctx, reinterpret_cast<const unsigned char *>(QUIC_ALPN_PROTO_LIST.data()),
-                              QUIC_ALPN_PROTO_LIST.size()) != 0) {
-    Error("SSL_CTX_set_alpn_protos failed");
-  }
-
   if (params->client_supported_groups() != nullptr) {
     if (SSL_CTX_set1_groups_list(ssl_ctx, params->client_supported_groups()) != 1) {
       Error("SSL_CTX_set1_groups_list failed");
diff --git a/iocore/net/quic/QUICTLS.h b/iocore/net/quic/QUICTLS.h
index 5e8d13d..5a2ac56 100644
--- a/iocore/net/quic/QUICTLS.h
+++ b/iocore/net/quic/QUICTLS.h
@@ -40,7 +40,7 @@ class QUICTLS : public QUICHandshakeProtocol
 {
 public:
   QUICTLS(QUICPacketProtectionKeyInfo &pp_key_info, SSL_CTX *ssl_ctx, NetVConnectionContext_t nvc_ctx,
-          const char *session_file = nullptr);
+          const NetVCOptions &netvc_options, const char *session_file = nullptr);
   ~QUICTLS();
 
   // TODO: integrate with _early_data_processed
diff --git a/iocore/net/quic/QUICTLS_openssl.cc b/iocore/net/quic/QUICTLS_openssl.cc
index d9951fe..d6d9842 100644
--- a/iocore/net/quic/QUICTLS_openssl.cc
+++ b/iocore/net/quic/QUICTLS_openssl.cc
@@ -329,13 +329,16 @@ QUICTLS::update_key_materials_on_key_cb(int name, const uint8_t *secret, size_t
 }
 
 QUICTLS::QUICTLS(QUICPacketProtectionKeyInfo &pp_key_info, SSL_CTX *ssl_ctx, NetVConnectionContext_t nvc_ctx,
-                 const char *session_file)
+                 const NetVCOptions &netvc_options, const char *session_file)
   : QUICHandshakeProtocol(pp_key_info), _session_file(session_file), _ssl(SSL_new(ssl_ctx)), _netvc_context(nvc_ctx)
 {
   ink_assert(this->_netvc_context != NET_VCONNECTION_UNSET);
 
   if (this->_netvc_context == NET_VCONNECTION_OUT) {
     SSL_set_connect_state(this->_ssl);
+
+    SSL_set_alpn_protos(this->_ssl, reinterpret_cast<const unsigned char *>(netvc_options.alpn_protos.data()), netvc_options.alpn_protos.size());
+    SSL_set_tlsext_host_name(this->_ssl, netvc_options.sni_servername.get());
   } else {
     SSL_set_accept_state(this->_ssl);
   }
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index e1a0a4e..b5c1e81 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -477,6 +477,11 @@ http2_convert_header_from_2_to_1_1(HTTPHdr *headers)
     headers->field_delete(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY);
     headers->field_delete(HTTP2_VALUE_PATH, HTTP2_LEN_PATH);
   } else {
+    // Set HTTP Version 1.1
+    int32_t version = HTTP_VERSION(1, 1);
+    http_hdr_version_set(headers->m_http, version);
+
+    // Set status from :status
     int status_len;
     const char *status;
 
@@ -521,6 +526,9 @@ http2_generate_h2_header_from_1_1(HTTPHdr *headers, HTTPHdr *h2_headers)
     int value_len;
 
     // Add ':authority' header field
+    // TODO: remove host/Host header
+    // [RFC 7540] 8.1.2.3. Clients that generate HTTP/2 requests directly SHOULD use the ":authority" pseudo-header field instead of
+    // the Host header field.
     field = h2_headers->field_create(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY);
     value = headers->host_get(&value_len);
     if (headers->is_port_in_header()) {
diff --git a/proxy/http3/Http3App.cc b/proxy/http3/Http3App.cc
index 8b53cbd..1174e39 100644
--- a/proxy/http3/Http3App.cc
+++ b/proxy/http3/Http3App.cc
@@ -37,13 +37,13 @@ static constexpr char debug_tag_v[] = "v_http3";
 
 Http3App::Http3App(QUICNetVConnection *client_vc, IpAllow::ACL session_acl) : QUICApplication(client_vc)
 {
-  this->_client_session      = new Http3ClientSession(client_vc);
-  this->_client_session->acl = std::move(session_acl);
-  this->_client_session->new_connection(client_vc, nullptr, nullptr);
+  this->_ssn      = new Http3ClientSession(client_vc);
+  this->_ssn->acl = std::move(session_acl);
+  this->_ssn->new_connection(client_vc, nullptr, nullptr);
 
   this->_qc->stream_manager()->set_default_application(this);
 
-  this->_settings_handler = new Http3SettingsHandler(this->_client_session);
+  this->_settings_handler = new Http3SettingsHandler(this->_ssn);
   this->_control_stream_dispatcher.add_handler(this->_settings_handler);
 
   this->_settings_framer = new Http3SettingsFramer(client_vc->get_context());
@@ -54,7 +54,7 @@ Http3App::Http3App(QUICNetVConnection *client_vc, IpAllow::ACL session_acl) : QU
 
 Http3App::~Http3App()
 {
-  delete this->_client_session;
+  delete this->_ssn;
   delete this->_settings_handler;
   delete this->_settings_framer;
 }
@@ -71,15 +71,16 @@ Http3App::start()
     this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_local_control_stream);
   }
 
-  error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_ENCODER);
-  if (error == nullptr) {
-    this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id));
-  }
+  // TODO: Open uni streams for QPACK when dynamic table is used
+  // error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_ENCODER);
+  // if (error == nullptr) {
+  //   this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id));
+  // }
 
-  error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_DECODER);
-  if (error == nullptr) {
-    this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id));
-  }
+  // error = this->create_uni_stream(stream_id, Http3StreamType::QPACK_DECODER);
+  // if (error == nullptr) {
+  //   this->_handle_uni_stream_on_write_ready(VC_EVENT_WRITE_READY, this->_find_stream_io(stream_id));
+  // }
 }
 
 int
@@ -113,6 +114,12 @@ Http3App::main_event_handler(int event, Event *data)
     }
     break;
   case VC_EVENT_EOS:
+    if (stream_io->is_bidirectional()) {
+      this->_handle_bidi_stream_on_eos(event, stream_io);
+    } else {
+      this->_handle_uni_stream_on_eos(event, stream_io);
+    }
+    break;
   case VC_EVENT_ERROR:
   case VC_EVENT_INACTIVITY_TIMEOUT:
   case VC_EVENT_ACTIVE_TIMEOUT:
@@ -196,10 +203,10 @@ Http3App::_handle_bidi_stream_on_read_ready(int event, QUICStreamIO *stream_io)
   uint8_t dummy;
   if (stream_io->peek(&dummy, 1)) {
     QUICStreamId stream_id      = stream_io->stream_id();
-    Http3ClientTransaction *txn = static_cast<Http3ClientTransaction *>(this->_client_session->get_transaction(stream_id));
+    Http3ClientTransaction *txn = static_cast<Http3ClientTransaction *>(this->_ssn->get_transaction(stream_id));
 
     if (txn == nullptr) {
-      txn = new Http3ClientTransaction(this->_client_session, stream_io);
+      txn = new Http3ClientTransaction(this->_ssn, stream_io);
       SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread());
 
       txn->new_transaction();
@@ -237,20 +244,32 @@ Http3App::_handle_uni_stream_on_write_ready(int /* event */, QUICStreamIO *strea
 }
 
 void
+Http3App::_handle_bidi_stream_on_eos(int /* event */, QUICStreamIO *stream_io)
+{
+  // TODO: handle eos
+}
+
+void
+Http3App::_handle_uni_stream_on_eos(int /* event */, QUICStreamIO *stream_io)
+{
+  // TODO: handle eos
+}
+
+void
 Http3App::_set_qpack_stream(Http3StreamType type, QUICStreamIO *stream_io)
 {
   // Change app to QPACK from Http3
   if (type == Http3StreamType::QPACK_ENCODER) {
     if (this->_qc->direction() == NET_VCONNECTION_IN) {
-      this->_client_session->remote_qpack()->set_encoder_stream(stream_io);
+      this->_ssn->remote_qpack()->set_encoder_stream(stream_io);
     } else {
-      this->_client_session->local_qpack()->set_encoder_stream(stream_io);
+      this->_ssn->local_qpack()->set_encoder_stream(stream_io);
     }
   } else if (type == Http3StreamType::QPACK_DECODER) {
     if (this->_qc->direction() == NET_VCONNECTION_IN) {
-      this->_client_session->local_qpack()->set_decoder_stream(stream_io);
+      this->_ssn->local_qpack()->set_decoder_stream(stream_io);
     } else {
-      this->_client_session->remote_qpack()->set_decoder_stream(stream_io);
+      this->_ssn->remote_qpack()->set_decoder_stream(stream_io);
     }
   } else {
     ink_abort("unkown stream type");
@@ -261,7 +280,7 @@ void
 Http3App::_handle_bidi_stream_on_write_ready(int event, QUICStreamIO *stream_io)
 {
   QUICStreamId stream_id      = stream_io->stream_id();
-  Http3ClientTransaction *txn = static_cast<Http3ClientTransaction *>(this->_client_session->get_transaction(stream_id));
+  Http3ClientTransaction *txn = static_cast<Http3ClientTransaction *>(this->_ssn->get_transaction(stream_id));
   if (txn != nullptr) {
     SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread());
     txn->handleEvent(event);
diff --git a/proxy/http3/Http3App.h b/proxy/http3/Http3App.h
index 87d2f26..e4a31c0 100644
--- a/proxy/http3/Http3App.h
+++ b/proxy/http3/Http3App.h
@@ -44,23 +44,29 @@ class Http3App : public QUICApplication
 {
 public:
   Http3App(QUICNetVConnection *client_vc, IpAllow::ACL session_acl);
-  ~Http3App();
+  virtual ~Http3App();
 
-  void start();
-  int main_event_handler(int event, Event *data);
+  virtual void start();
+  virtual int main_event_handler(int event, Event *data);
 
   // TODO: Return StreamIO. It looks bother that coller have to look up StreamIO by stream id.
   // Why not create_bidi_stream ?
   QUICConnectionErrorUPtr create_uni_stream(QUICStreamId &new_stream_id, Http3StreamType type);
 
+protected:
+  // TODO: create Http3Session
+  Http3ClientSession *_ssn = nullptr;
+
 private:
   void _handle_uni_stream_on_read_ready(int event, QUICStreamIO *stream_io);
-  void _handle_bidi_stream_on_read_ready(int event, QUICStreamIO *stream_io);
   void _handle_uni_stream_on_write_ready(int event, QUICStreamIO *stream_io);
+  void _handle_uni_stream_on_eos(int event, QUICStreamIO *stream_io);
+  void _handle_bidi_stream_on_read_ready(int event, QUICStreamIO *stream_io);
   void _handle_bidi_stream_on_write_ready(int event, QUICStreamIO *stream_io);
+  void _handle_bidi_stream_on_eos(int event, QUICStreamIO *stream_io);
+
   void _set_qpack_stream(Http3StreamType type, QUICStreamIO *stream_io);
 
-  Http3ClientSession *_client_session   = nullptr;
   Http3FrameHandler *_settings_handler  = nullptr;
   Http3FrameGenerator *_settings_framer = nullptr;
 
diff --git a/proxy/http3/Http3ClientTransaction.cc b/proxy/http3/Http3ClientTransaction.cc
index 228621b..42467c0 100644
--- a/proxy/http3/Http3ClientTransaction.cc
+++ b/proxy/http3/Http3ClientTransaction.cc
@@ -64,12 +64,20 @@ HQClientTransaction::HQClientTransaction(HQClientSession *session, QUICStreamIO
   this->set_parent(session);
 
   this->sm_reader = this->_read_vio_buf.alloc_reader();
-  this->_request_header.create(HTTP_TYPE_REQUEST);
+
+  HTTPType http_type = HTTP_TYPE_UNKNOWN;
+  if (this->direction() == NET_VCONNECTION_OUT) {
+    http_type = HTTP_TYPE_RESPONSE;
+  } else {
+    http_type = HTTP_TYPE_REQUEST;
+  }
+
+  this->_header.create(http_type);
 }
 
 HQClientTransaction::~HQClientTransaction()
 {
-  this->_request_header.destroy();
+  this->_header.destroy();
 }
 
 void
@@ -236,6 +244,12 @@ HQClientTransaction::decrement_client_transactions_stat()
   // TODO
 }
 
+NetVConnectionContext_t
+HQClientTransaction::direction() const
+{
+  return this->parent->get_netvc()->get_context();
+}
+
 /**
  * @brief Replace existing event only if the new event is different than the inprogress event
  */
@@ -311,7 +325,7 @@ Http3ClientTransaction::Http3ClientTransaction(Http3ClientSession *session, QUIC
   this->_frame_collector.add_generator(this->_data_framer);
   // this->_frame_collector.add_generator(this->_push_controller);
 
-  this->_header_handler = new Http3HeaderVIOAdaptor(&this->_request_header, session->remote_qpack(), this, stream_io->stream_id());
+  this->_header_handler = new Http3HeaderVIOAdaptor(&this->_header, session->remote_qpack(), this, stream_io->stream_id());
   this->_data_handler   = new Http3StreamDataVIOAdaptor(&this->_read_vio);
 
   this->_frame_dispatcher.add_handler(this->_header_handler);
@@ -332,8 +346,6 @@ int
 Http3ClientTransaction::state_stream_open(int event, void *edata)
 {
   // TODO: should check recursive call?
-  Http3TransVDebug("%s (%d)", get_vc_event_name(event), event);
-
   if (this->_thread != this_ethread()) {
     // Send on to the owning thread
     if (this->_cross_thread_event == nullptr) {
@@ -352,6 +364,7 @@ Http3ClientTransaction::state_stream_open(int event, void *edata)
   switch (event) {
   case VC_EVENT_READ_READY:
   case VC_EVENT_READ_COMPLETE: {
+    Http3TransVDebug("%s (%d)", get_vc_event_name(event), event);
     int64_t len = this->_process_read_vio();
     // if no progress, don't need to signal
     if (len > 0) {
@@ -363,7 +376,9 @@ Http3ClientTransaction::state_stream_open(int event, void *edata)
   }
   case VC_EVENT_WRITE_READY:
   case VC_EVENT_WRITE_COMPLETE: {
+    Http3TransVDebug("%s (%d)", get_vc_event_name(event), event);
     int64_t len = this->_process_write_vio();
+    // if no progress, don't need to signal
     if (len > 0) {
       this->_signal_write_event();
     }
@@ -379,6 +394,7 @@ Http3ClientTransaction::state_stream_open(int event, void *edata)
     break;
   }
   case QPACK_EVENT_DECODE_COMPLETE: {
+    Http3TransVDebug("%s (%d)", "QPACK_EVENT_DECODE_COMPLETE", event);
     int res = this->_on_qpack_decode_complete();
     if (res) {
       // If READ_READY event is scheduled, should it be canceled?
@@ -387,6 +403,7 @@ Http3ClientTransaction::state_stream_open(int event, void *edata)
     break;
   }
   case QPACK_EVENT_DECODE_FAILED: {
+    Http3TransVDebug("%s (%d)", "QPACK_EVENT_DECODE_FAILED", event);
     // FIXME: handle error
     break;
   }
@@ -506,20 +523,22 @@ Http3ClientTransaction::_convert_header_from_3_to_1_1(HTTPHdr *hdrs)
 {
   // TODO: do HTTP/3 specific convert, if there
 
-  // Dirty hack to bypass checks
-  MIMEField *field;
-  if ((field = hdrs->field_find(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME)) == nullptr) {
-    char value_s[]          = "https";
-    MIMEField *scheme_field = hdrs->field_create(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME);
-    scheme_field->value_set(hdrs->m_heap, hdrs->m_mime, value_s, sizeof(value_s) - 1);
-    hdrs->field_attach(scheme_field);
-  }
+  if (http_hdr_type_get(hdrs->m_http) == HTTP_TYPE_REQUEST) {
+    // Dirty hack to bypass checks
+    MIMEField *field;
+    if ((field = hdrs->field_find(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME)) == nullptr) {
+      char value_s[]          = "https";
+      MIMEField *scheme_field = hdrs->field_create(HTTP3_VALUE_SCHEME, HTTP3_LEN_SCHEME);
+      scheme_field->value_set(hdrs->m_heap, hdrs->m_mime, value_s, sizeof(value_s) - 1);
+      hdrs->field_attach(scheme_field);
+    }
 
-  if ((field = hdrs->field_find(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY)) == nullptr) {
-    char value_a[]             = "localhost";
-    MIMEField *authority_field = hdrs->field_create(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY);
-    authority_field->value_set(hdrs->m_heap, hdrs->m_mime, value_a, sizeof(value_a) - 1);
-    hdrs->field_attach(authority_field);
+    if ((field = hdrs->field_find(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY)) == nullptr) {
+      char value_a[]             = "localhost";
+      MIMEField *authority_field = hdrs->field_create(HTTP3_VALUE_AUTHORITY, HTTP3_LEN_AUTHORITY);
+      authority_field->value_set(hdrs->m_heap, hdrs->m_mime, value_a, sizeof(value_a) - 1);
+      hdrs->field_attach(authority_field);
+    }
   }
 
   return http2_convert_header_from_2_to_1_1(hdrs);
@@ -528,8 +547,9 @@ Http3ClientTransaction::_convert_header_from_3_to_1_1(HTTPHdr *hdrs)
 int
 Http3ClientTransaction::_on_qpack_decode_complete()
 {
-  ParseResult res = this->_convert_header_from_3_to_1_1(&this->_request_header);
+  ParseResult res = this->_convert_header_from_3_to_1_1(&this->_header);
   if (res == PARSE_RESULT_ERROR) {
+    Http3TransDebug("PARSE_RESULT_ERROR");
     return -1;
   }
 
@@ -554,7 +574,7 @@ Http3ClientTransaction::_on_qpack_decode_complete()
       writer->add_block();
       block = writer->get_current_block();
     }
-    done = this->_request_header.print(block->start(), block->write_avail(), &bufindex, &tmp);
+    done = this->_header.print(block->end(), block->write_avail(), &bufindex, &tmp);
     dumpoffset += bufindex;
     writer->fill(bufindex);
     if (!done) {
diff --git a/proxy/http3/Http3ClientTransaction.h b/proxy/http3/Http3ClientTransaction.h
index 2d16663..a6fe36b 100644
--- a/proxy/http3/Http3ClientTransaction.h
+++ b/proxy/http3/Http3ClientTransaction.h
@@ -66,6 +66,7 @@ public:
   // HQClientTransaction
   virtual int state_stream_open(int, void *)             = 0;
   virtual int state_stream_closed(int event, void *data) = 0;
+  NetVConnectionContext_t direction() const;
 
 protected:
   virtual int64_t _process_read_vio()  = 0;
@@ -85,7 +86,7 @@ protected:
   Event *_read_event  = nullptr;
   Event *_write_event = nullptr;
 
-  HTTPHdr _request_header;
+  HTTPHdr _header; ///< HTTP header buffer for decoding
 };
 
 class Http3ClientTransaction : public HQClientTransaction
diff --git a/proxy/http3/Http3HeaderFramer.cc b/proxy/http3/Http3HeaderFramer.cc
index 6bda091..1d493f5 100644
--- a/proxy/http3/Http3HeaderFramer.cc
+++ b/proxy/http3/Http3HeaderFramer.cc
@@ -70,60 +70,31 @@ Http3HeaderFramer::is_done() const
   return this->_sent_all_data;
 }
 
-const char *HTTP3_VALUE_STATUS           = ":status";
-const unsigned HTTP3_LEN_STATUS          = countof(":status") - 1;
-static size_t HTTP3_LEN_STATUS_VALUE_STR = 3;
-
-// Copy code from http2_generate_h2_header_from_1_1(h1_hdrs, h3_hdrs);
 void
 Http3HeaderFramer::_convert_header_from_1_1_to_3(HTTPHdr *h3_hdrs, HTTPHdr *h1_hdrs)
 {
-  // Add ':status' header field
-  char status_str[HTTP3_LEN_STATUS_VALUE_STR + 1];
-  snprintf(status_str, sizeof(status_str), "%d", h1_hdrs->status_get());
-  MIMEField *status_field = h3_hdrs->field_create(HTTP3_VALUE_STATUS, HTTP3_LEN_STATUS);
-  status_field->value_set(h3_hdrs->m_heap, h3_hdrs->m_mime, status_str, HTTP3_LEN_STATUS_VALUE_STR);
-  h3_hdrs->field_attach(status_field);
-
-  // Copy headers
-  // Intermediaries SHOULD remove connection-specific header fields.
-  MIMEFieldIter field_iter;
-  for (MIMEField *field = h1_hdrs->iter_get_first(&field_iter); field != nullptr; field = h1_hdrs->iter_get_next(&field_iter)) {
-    const char *name;
-    int name_len;
-    const char *value;
-    int value_len;
-    name = field->name_get(&name_len);
-    if ((name_len == MIME_LEN_CONNECTION && strncasecmp(name, MIME_FIELD_CONNECTION, name_len) == 0) ||
-        (name_len == MIME_LEN_KEEP_ALIVE && strncasecmp(name, MIME_FIELD_KEEP_ALIVE, name_len) == 0) ||
-        (name_len == MIME_LEN_PROXY_CONNECTION && strncasecmp(name, MIME_FIELD_PROXY_CONNECTION, name_len) == 0) ||
-        (name_len == MIME_LEN_TRANSFER_ENCODING && strncasecmp(name, MIME_FIELD_TRANSFER_ENCODING, name_len) == 0) ||
-        (name_len == MIME_LEN_UPGRADE && strncasecmp(name, MIME_FIELD_UPGRADE, name_len) == 0)) {
-      continue;
-    }
-    MIMEField *newfield;
-    name     = field->name_get(&name_len);
-    newfield = h3_hdrs->field_create(name, name_len);
-    value    = field->value_get(&value_len);
-    newfield->value_set(h3_hdrs->m_heap, h3_hdrs->m_mime, value, value_len);
-    h3_hdrs->field_attach(newfield);
-  }
+  http2_generate_h2_header_from_1_1(h1_hdrs, h3_hdrs);
 }
 
 void
 Http3HeaderFramer::_generate_header_block()
 {
   // Prase response header and generate header block
-  int bytes_used = 0;
-  // TODO Use HTTP_TYPE_REQUEST if this is for requests
-  this->_header.create(HTTP_TYPE_RESPONSE);
-  int parse_result = this->_header.parse_resp(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false);
+  int bytes_used           = 0;
+  ParseResult parse_result = PARSE_RESULT_ERROR;
+
+  if (this->_transaction->direction() == NET_VCONNECTION_OUT) {
+    this->_header.create(HTTP_TYPE_REQUEST);
+    parse_result = this->_header.parse_req(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false);
+  } else {
+    this->_header.create(HTTP_TYPE_RESPONSE);
+    parse_result = this->_header.parse_resp(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false);
+  }
   this->_source_vio->ndone += this->_header.length_get();
 
   switch (parse_result) {
   case PARSE_RESULT_DONE: {
     HTTPHdr h3_hdr;
-    h3_hdr.create(HTTP_TYPE_RESPONSE);
     this->_convert_header_from_1_1_to_3(&h3_hdr, &this->_header);
 
     this->_header_block        = new_MIOBuffer();
@@ -135,6 +106,7 @@ Http3HeaderFramer::_generate_header_block()
   case PARSE_RESULT_CONT:
     break;
   default:
+    Debug("http3_trans", "Ignore ivalid headers");
     break;
   }
 }
diff --git a/proxy/http3/Http3HeaderVIOAdaptor.cc b/proxy/http3/Http3HeaderVIOAdaptor.cc
index c2e4414..6a55ae7 100644
--- a/proxy/http3/Http3HeaderVIOAdaptor.cc
+++ b/proxy/http3/Http3HeaderVIOAdaptor.cc
@@ -49,11 +49,12 @@ Http3HeaderVIOAdaptor::handle_frame(std::shared_ptr<const Http3Frame> frame)
   if (res == 0) {
     // When decoding is not blocked, continuation should be called directly?
   } else if (res == 1) {
-    // Decoding is blocked. Callback will be fired.
+    // Decoding is blocked.
+    Debug("http3", "Decoding is blocked. DecodeRequest is scheduled");
   } else if (res < 0) {
-    // error
+    Debug("http3", "Error on decoding header (%d)", res);
   } else {
-    // should not be here
+    ink_abort("should not be here");
   }
 
   return Http3ErrorUPtr(new Http3NoError());
diff --git a/src/traffic_quic/Makefile.inc b/src/traffic_quic/Makefile.inc
index b9291db..18b466f 100644
--- a/src/traffic_quic/Makefile.inc
+++ b/src/traffic_quic/Makefile.inc
@@ -19,39 +19,45 @@ bin_PROGRAMS += traffic_quic/traffic_quic
 
 traffic_quic_traffic_quic_CPPFLAGS = \
 	$(AM_CPPFLAGS) \
-  $(iocore_include_dirs) \
-  -I$(abs_top_srcdir)/lib \
-  -I$(abs_top_srcdir)/lib/records \
-  -I$(abs_top_srcdir)/mgmt \
-  -I$(abs_top_srcdir)/mgmt/utils \
-  -I$(abs_top_srcdir)/proxy \
-  -I$(abs_top_srcdir)/proxy/hdrs \
-  -I$(abs_top_srcdir)/proxy/http \
-  -I$(abs_top_srcdir)/proxy/logging \
-  -I$(abs_top_srcdir)/proxy/shared \
-  $(TS_INCLUDES) \
-  @OPENSSL_INCLUDES@
+	$(iocore_include_dirs) \
+	-I$(abs_top_srcdir)/lib \
+	-I$(abs_top_srcdir)/lib/records \
+	-I$(abs_top_srcdir)/mgmt \
+	-I$(abs_top_srcdir)/mgmt/utils \
+	-I$(abs_top_srcdir)/proxy \
+	-I$(abs_top_srcdir)/proxy/hdrs \
+	-I$(abs_top_srcdir)/proxy/http \
+	-I$(abs_top_srcdir)/proxy/http/remap \
+	-I$(abs_top_srcdir)/proxy/http3 \
+	-I$(abs_top_srcdir)/proxy/logging \
+	-I$(abs_top_srcdir)/proxy/shared \
+	$(TS_INCLUDES) \
+	@OPENSSL_INCLUDES@
 
 traffic_quic_traffic_quic_LDFLAGS = \
-  $(AM_LDFLAGS) \
-  @OPENSSL_LDFLAGS@
+	$(AM_LDFLAGS) \
+	@OPENSSL_LDFLAGS@
 
 traffic_quic_traffic_quic_SOURCES = \
-  traffic_quic/quic_client.cc \
-  traffic_quic/traffic_quic.cc
+	traffic_quic/quic_client.cc \
+	traffic_quic/traffic_quic.cc
 
 traffic_quic_traffic_quic_LDADD = \
-  $(top_builddir)/iocore/net/libinknet.a \
-  $(top_builddir)/iocore/aio/libinkaio.a \
-  $(top_builddir)/iocore/net/quic/libquic.a \
-  $(top_builddir)/iocore/eventsystem/libinkevent.a \
-  $(top_builddir)/mgmt/libmgmt_p.la \
-  $(top_builddir)/lib/records/librecords_p.a \
-  $(top_builddir)/src/tscore/libtscore.la \
-  $(top_builddir)/src/tscpp/util/libtscpputil.la \
-  $(top_builddir)/lib/tsconfig/libtsconfig.la \
-  $(top_builddir)/proxy/ParentSelectionStrategy.o \
-  @HWLOC_LIBS@ \
-  @YAMLCPP_LIBS@ \
-  @OPENSSL_LIBS@ \
-  @LIBPCRE@
+	$(top_builddir)/iocore/net/libinknet.a \
+	$(top_builddir)/iocore/aio/libinkaio.a \
+	$(top_builddir)/iocore/net/quic/libquic.a \
+	$(top_builddir)/iocore/eventsystem/libinkevent.a \
+	$(top_builddir)/mgmt/libmgmt_p.la \
+	$(top_builddir)/lib/records/librecords_p.a \
+	$(top_builddir)/src/tscore/libtscore.la \
+	$(top_builddir)/src/tscpp/util/libtscpputil.la \
+	$(top_builddir)/lib/tsconfig/libtsconfig.la \
+	$(top_builddir)/proxy/ParentSelectionStrategy.o \
+	$(top_builddir)/proxy/libproxy.a \
+	$(top_builddir)/proxy/hdrs/libhdrs.a \
+	$(top_builddir)/proxy/http2/libhttp2.a \
+	$(top_builddir)/proxy/http3/libhttp3.a \
+	@HWLOC_LIBS@ \
+	@YAMLCPP_LIBS@ \
+	@OPENSSL_LIBS@ \
+	@LIBPCRE@
diff --git a/src/traffic_quic/quic_client.cc b/src/traffic_quic/quic_client.cc
index d2eb7e0..47560fc 100644
--- a/src/traffic_quic/quic_client.cc
+++ b/src/traffic_quic/quic_client.cc
@@ -25,6 +25,17 @@
 
 #include <iostream>
 #include <fstream>
+#include <string_view>
+
+#include "Http3ClientSession.h"
+#include "Http3ClientTransaction.h"
+
+// OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
+// https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
+// Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ?
+using namespace std::literals;
+static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-18"sv);
+static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-18"sv);
 
 QUICClient::QUICClient(const QUICClientConfig *config) : Continuation(new_ProxyMutex()), _config(config)
 {
@@ -55,6 +66,13 @@ QUICClient::start(int, void *)
     return EVENT_DONE;
   }
 
+  std::string_view alpn_protos;
+  if (this->_config->http3) {
+    alpn_protos = H3_ALPN_PROTO_LIST;
+  } else {
+    alpn_protos = HQ_ALPN_PROTO_LIST;
+  }
+
   for (struct addrinfo *info = this->_remote_addr_info; info != nullptr; info = info->ai_next) {
     NetVCOptions opt;
     opt.ip_proto            = NetVCOptions::USE_UDP;
@@ -62,6 +80,8 @@ QUICClient::start(int, void *)
     opt.etype               = ET_NET;
     opt.socket_recv_bufsize = 1048576;
     opt.socket_send_bufsize = 1048576;
+    opt.alpn_protos         = alpn_protos;
+    opt.set_sni_servername(this->_config->addr, strnlen(this->_config->addr, 1023));
 
     SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
 
@@ -83,8 +103,19 @@ QUICClient::state_http_server_open(int event, void *data)
     Debug("quic_client", "start proxy server ssn/txn");
 
     QUICNetVConnection *conn = static_cast<QUICNetVConnection *>(data);
-    QUICClientApp *app       = new QUICClientApp(conn, this->_config);
-    app->start(this->_config->path);
+
+    if (this->_config->http0_9) {
+      Http09ClientApp *app = new Http09ClientApp(conn, this->_config);
+      app->start();
+    } else if (this->_config->http3) {
+      // TODO: see what server session is doing with IpAllow::ACL
+      IpAllow::ACL session_acl;
+      Http3ClientApp *app = new Http3ClientApp(conn, std::move(session_acl), this->_config);
+      SCOPED_MUTEX_LOCK(lock, app->mutex, this_ethread());
+      app->start();
+    } else {
+      ink_abort("invalid config");
+    }
 
     break;
   }
@@ -104,20 +135,20 @@ QUICClient::state_http_server_open(int event, void *data)
 }
 
 //
-// QUICClientApp
+// Http09ClientApp
 //
-#define QUICClientAppDebug(fmt, ...) Debug("quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
-#define QUICClientAppVDebug(fmt, ...) Debug("v_quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
+#define Http09ClientAppDebug(fmt, ...) Debug("quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
+#define Http09ClientAppVDebug(fmt, ...) Debug("v_quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
 
-QUICClientApp::QUICClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config) : QUICApplication(qvc), _config(config)
+Http09ClientApp::Http09ClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config) : QUICApplication(qvc), _config(config)
 {
   this->_qc->stream_manager()->set_default_application(this);
 
-  SET_HANDLER(&QUICClientApp::main_event_handler);
+  SET_HANDLER(&Http09ClientApp::main_event_handler);
 }
 
 void
-QUICClientApp::start(const char *path)
+Http09ClientApp::start()
 {
   if (this->_config->output[0] != 0x0) {
     this->_filename = this->_config->output;
@@ -128,15 +159,11 @@ QUICClientApp::start(const char *path)
     std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc);
   }
 
-  if (this->_config->http3) {
-    this->_start_http_3_session(path);
-  } else {
-    this->_start_http_09_session(path);
-  }
+  this->_do_http_request();
 }
 
 void
-QUICClientApp::_start_http_09_session(const char *path)
+Http09ClientApp::_do_http_request()
 {
   QUICStreamId stream_id;
   QUICConnectionErrorUPtr error = this->_qc->stream_manager()->create_bidi_stream(stream_id);
@@ -148,44 +175,9 @@ QUICClientApp::_start_http_09_session(const char *path)
 
   // TODO: move to transaction
   char request[1024] = {0};
-  int request_len    = snprintf(request, sizeof(request), "GET %s\r\n", path);
-
-  QUICClientAppDebug("\n%s", request);
-
-  QUICStreamIO *stream_io = this->_find_stream_io(stream_id);
-
-  stream_io->write(reinterpret_cast<uint8_t *>(request), request_len);
-  stream_io->write_done();
-  stream_io->write_reenable();
-}
-
-void
-QUICClientApp::_start_http_3_session(const char *path)
-{
-  QUICConnectionErrorUPtr error;
-
-  QUICStreamId settings_stream_id;
-  error = this->_qc->stream_manager()->create_uni_stream(settings_stream_id);
-
-  if (error != nullptr) {
-    Error("%s", error->msg);
-    ink_abort("Could not create uni stream : %s", error->msg);
-  }
-  // TODO: send settings frame
-
-  QUICStreamId stream_id;
-  error = this->_qc->stream_manager()->create_bidi_stream(stream_id);
-
-  if (error != nullptr) {
-    Error("%s", error->msg);
-    ink_abort("Could not create bidi stream : %s", error->msg);
-  }
-
-  // TODO: move to transaction
-  char request[1024] = {0};
-  int request_len    = snprintf(request, sizeof(request), "GET %s\r\n", path);
+  int request_len    = snprintf(request, sizeof(request), "GET %s\r\n", this->_config->path);
 
-  QUICClientAppDebug("\n%s", request);
+  Http09ClientAppDebug("\n%s", request);
 
   QUICStreamIO *stream_io = this->_find_stream_io(stream_id);
 
@@ -195,15 +187,15 @@ QUICClientApp::_start_http_3_session(const char *path)
 }
 
 int
-QUICClientApp::main_event_handler(int event, Event *data)
+Http09ClientApp::main_event_handler(int event, Event *data)
 {
-  QUICClientAppVDebug("%s (%d)", get_vc_event_name(event), event);
+  Http09ClientAppVDebug("%s (%d)", get_vc_event_name(event), event);
 
   VIO *vio                = reinterpret_cast<VIO *>(data);
   QUICStreamIO *stream_io = this->_find_stream_io(vio);
 
   if (stream_io == nullptr) {
-    QUICClientAppDebug("Unknown Stream");
+    Http09ClientAppDebug("Unknown Stream");
     return -1;
   }
 
@@ -253,3 +245,140 @@ QUICClientApp::main_event_handler(int event, Event *data)
 
   return EVENT_CONT;
 }
+
+//
+// Http3ClientApp
+//
+Http3ClientApp::Http3ClientApp(QUICNetVConnection *qvc, IpAllow::ACL session_acl, const QUICClientConfig *config)
+  : super(qvc, std::move(session_acl)), _config(config)
+{
+}
+
+Http3ClientApp::~Http3ClientApp()
+{
+  free_MIOBuffer(this->_req_buf);
+  this->_req_buf = nullptr;
+
+  free_MIOBuffer(this->_resp_buf);
+  this->_resp_buf = nullptr;
+
+  delete this->_resp_handler;
+}
+
+void
+Http3ClientApp::start()
+{
+  this->_req_buf                  = new_MIOBuffer();
+  this->_resp_buf                 = new_MIOBuffer();
+  IOBufferReader *resp_buf_reader = _resp_buf->alloc_reader();
+
+  this->_resp_handler = new RespHandler(this->_config, resp_buf_reader);
+
+  super::start();
+  this->_do_http_request();
+}
+
+void
+Http3ClientApp::_do_http_request()
+{
+  QUICConnectionErrorUPtr error;
+  QUICStreamId stream_id;
+  error = this->_qc->stream_manager()->create_bidi_stream(stream_id);
+  if (error != nullptr) {
+    Error("%s", error->msg);
+    ink_abort("Could not create bidi stream : %s", error->msg);
+  }
+
+  QUICStreamIO *stream_io = this->_find_stream_io(stream_id);
+
+  // TODO: create Http3ServerTransaction
+  Http3ClientTransaction *txn = new Http3ClientTransaction(this->_ssn, stream_io);
+  SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread());
+
+  // TODO: fix below issue with H2 origin conn stuff
+  // Do not call ProxyClientTransaction::new_transaction(), but need to setup txn - e.g. do_io_write / do_io_read
+  VIO *read_vio = txn->do_io_read(this->_resp_handler, INT64_MAX, this->_resp_buf);
+  this->_resp_handler->set_read_vio(read_vio);
+
+  // Write HTTP Request to write_vio
+  char request[1024] = {0};
+  std::string format;
+  if (this->_config->path[0] == '/') {
+    format = "GET https://%s%s HTTP/1.1\r\n\r\n";
+  } else {
+    format = "GET https://%s/%s HTTP/1.1\r\n\r\n";
+  }
+
+  int request_len = snprintf(request, sizeof(request), format.c_str(), this->_config->addr, this->_config->path);
+
+  Http09ClientAppDebug("\n%s", request);
+
+  // TODO: check write avail size
+  int64_t nbytes            = this->_req_buf->write(request, request_len);
+  IOBufferReader *buf_start = this->_req_buf->alloc_reader();
+  txn->do_io_write(this, nbytes, buf_start);
+}
+
+//
+// Response Handler
+//
+RespHandler::RespHandler(const QUICClientConfig *config, IOBufferReader *reader)
+  : Continuation(new_ProxyMutex()), _config(config), _reader(reader)
+{
+  if (this->_config->output[0] != 0x0) {
+    this->_filename = this->_config->output;
+  }
+
+  if (this->_filename) {
+    // Destroy contents if file already exists
+    std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc);
+  }
+
+  SET_HANDLER(&RespHandler::main_event_handler);
+}
+
+void
+RespHandler::set_read_vio(VIO *vio)
+{
+  this->_read_vio = vio;
+}
+
+int
+RespHandler::main_event_handler(int event, Event *data)
+{
+  Debug("v_http3", "%s", get_vc_event_name(event));
+  switch (event) {
+  case VC_EVENT_READ_READY:
+  case VC_EVENT_READ_COMPLETE: {
+    std::streambuf *default_stream = nullptr;
+    std::ofstream f_stream;
+
+    if (this->_filename) {
+      default_stream = std::cout.rdbuf();
+      f_stream       = std::ofstream(this->_filename, std::ios::binary | std::ios::app);
+      std::cout.rdbuf(f_stream.rdbuf());
+    }
+
+    uint8_t buf[8192] = {0};
+    int64_t nread;
+    while ((nread = this->_reader->read(buf, sizeof(buf))) > 0) {
+      std::cout.write(reinterpret_cast<char *>(buf), nread);
+      this->_read_vio->ndone += nread;
+    }
+    std::cout.flush();
+
+    if (this->_filename) {
+      f_stream.close();
+      std::cout.rdbuf(default_stream);
+    }
+
+    break;
+  }
+  case VC_EVENT_WRITE_READY:
+  case VC_EVENT_WRITE_COMPLETE:
+  default:
+    break;
+  }
+
+  return EVENT_CONT;
+}
diff --git a/src/traffic_quic/quic_client.h b/src/traffic_quic/quic_client.h
index fe66740..5a2a950 100644
--- a/src/traffic_quic/quic_client.h
+++ b/src/traffic_quic/quic_client.h
@@ -29,19 +29,35 @@
 #include "P_QUICNetProcessor.h"
 
 #include "QUICApplication.h"
+#include "Http3App.h"
 
 // TODO: add quic version option
+// TODO: add host header option (also should be used for SNI)
 struct QUICClientConfig {
   char addr[1024]       = "127.0.0.1";
   char output[1024]     = {0};
   char port[16]         = "4433";
   char path[1018]       = "/";
-  char debug_tags[1024] = "quic|vv_quic_crypto";
+  char debug_tags[1024] = "quic|vv_quic_crypto|http3|qpack";
   int close             = false;
   int http0_9           = true;
   int http3             = false;
 };
 
+class RespHandler : public Continuation
+{
+public:
+  RespHandler(const QUICClientConfig *config, IOBufferReader *reader);
+  int main_event_handler(int event, Event *data);
+  void set_read_vio(VIO *vio);
+
+private:
+  const QUICClientConfig *_config = nullptr;
+  const char *_filename           = nullptr;
+  IOBufferReader *_reader         = nullptr;
+  VIO *_read_vio                  = nullptr;
+};
+
 class QUICClient : public Continuation
 {
 public:
@@ -56,18 +72,37 @@ private:
   struct addrinfo *_remote_addr_info = nullptr;
 };
 
-class QUICClientApp : public QUICApplication
+class Http09ClientApp : public QUICApplication
 {
 public:
-  QUICClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config);
+  Http09ClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config);
 
-  void start(const char *path);
+  void start();
   int main_event_handler(int event, Event *data);
 
 private:
-  void _start_http_09_session(const char *path);
-  void _start_http_3_session(const char *path);
+  void _do_http_request();
 
   const QUICClientConfig *_config = nullptr;
   const char *_filename           = nullptr;
 };
+
+class Http3ClientApp : public Http3App
+{
+public:
+  using super = Http3App;
+
+  Http3ClientApp(QUICNetVConnection *qvc, IpAllow::ACL session_acl, const QUICClientConfig *config);
+  ~Http3ClientApp();
+
+  void start() override;
+
+private:
+  void _do_http_request();
+
+  RespHandler *_resp_handler      = nullptr;
+  const QUICClientConfig *_config = nullptr;
+
+  MIOBuffer *_req_buf  = nullptr;
+  MIOBuffer *_resp_buf = nullptr;
+};
diff --git a/src/traffic_quic/traffic_quic.cc b/src/traffic_quic/traffic_quic.cc
index 551bb50..9ac59b5 100644
--- a/src/traffic_quic/traffic_quic.cc
+++ b/src/traffic_quic/traffic_quic.cc
@@ -27,6 +27,11 @@
 #include "tscore/I_Version.h"
 
 #include "RecordsConfig.h"
+#include "URL.h"
+#include "MIME.h"
+#include "HTTP.h"
+#include "HuffmanCodec.h"
+#include "Http3Config.h"
 
 #include "diags.h"
 #include "quic_client.h"
@@ -95,6 +100,14 @@ main(int argc, const char **argv)
   udpNet.start(1, stacksize);
   quic_NetProcessor.start(-1, stacksize);
 
+  // Same to init_http_header(); in traffic_server.cc
+  url_init();
+  mime_init();
+  http_init();
+  hpack_huffman_init();
+
+  Http3Config::startup();
+
   QUICClient client(&config);
   eventProcessor.schedule_in(&client, 1, ET_NET);
 
@@ -168,6 +181,7 @@ Log::trace_out(sockaddr const *, unsigned short, char const *, ...)
 }
 
 #include "InkAPIInternal.h"
+
 int
 APIHook::invoke(int, void *)
 {
@@ -190,6 +204,24 @@ APIHooks::get() const
 }
 
 void
+APIHooks::clear()
+{
+  ink_abort("do not call stub");
+}
+
+void
+APIHooks::append(INKContInternal *)
+{
+  ink_abort("do not call stub");
+}
+
+void
+APIHooks::prepend(INKContInternal *)
+{
+  ink_abort("do not call stub");
+}
+
+void
 ConfigUpdateCbTable::invoke(const char * /* name ATS_UNUSED */)
 {
   ink_release_assert(false);
@@ -227,19 +259,76 @@ HttpRequestData::get_client_ip()
 SslAPIHooks *ssl_hooks = nullptr;
 StatPagesManager statPagesManager;
 
-#include "ProcessManager.h"
-inkcoreapi ProcessManager *pmgmt = nullptr;
+#include "HttpDebugNames.h"
+const char *
+HttpDebugNames::get_api_hook_name(TSHttpHookID t)
+{
+  return "dummy";
+}
+
+#include "HttpSM.h"
+HttpSM::HttpSM() : Continuation(nullptr), vc_table(this) {}
+
+void
+HttpSM::cleanup()
+{
+  ink_abort("do not call stub");
+}
+
+void
+HttpSM::destroy()
+{
+  ink_abort("do not call stub");
+}
+
+void
+HttpSM::set_next_state()
+{
+  ink_abort("do not call stub");
+}
+
+void
+HttpSM::handle_api_return()
+{
+  ink_abort("do not call stub");
+}
 
 int
-BaseManager::registerMgmtCallback(int, const MgmtCallback &)
+HttpSM::kill_this_async_hook(int /* event ATS_UNUSED */, void * /* data ATS_UNUSED */)
 {
-  ink_assert(false);
-  return 0;
+  return EVENT_DONE;
 }
 
 void
-ProcessManager::signalManager(int, char const *, int)
+HttpSM::attach_client_session(ProxyClientTransaction *, IOBufferReader *)
+{
+  ink_abort("do not call stub");
+}
+
+void
+HttpSM::init()
+{
+  ink_abort("do not call stub");
+}
+
+ClassAllocator<HttpSM> httpSMAllocator("httpSMAllocator");
+HttpAPIHooks *http_global_hooks;
+
+HttpVCTable::HttpVCTable(HttpSM *) {}
+
+PostDataBuffers::~PostDataBuffers() {}
+
+#include "HttpTunnel.h"
+HttpTunnel::HttpTunnel() : Continuation(nullptr) {}
+HttpTunnelConsumer::HttpTunnelConsumer() {}
+HttpTunnelProducer::HttpTunnelProducer() {}
+ChunkedHandler::ChunkedHandler() {}
+
+#include "HttpCacheSM.h"
+HttpCacheSM::HttpCacheSM() {}
+
+HttpCacheAction::HttpCacheAction() : sm(nullptr) {}
+void
+HttpCacheAction::cancel(Continuation *c)
 {
-  ink_assert(false);
-  return;
 }