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 2020/01/24 04:31:33 UTC

[trafficserver] branch master updated: Perf: Optimize sending HTTP/2 frame

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

masaori 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 48bcbe6  Perf: Optimize sending HTTP/2 frame
48bcbe6 is described below

commit 48bcbe692ae7172479e8d71a91d1c779577186c9
Author: Masaori Koshiba <ma...@apache.org>
AuthorDate: Mon Jan 20 13:57:53 2020 +0900

    Perf: Optimize sending HTTP/2 frame
    
    Prior to this change, HTTP/2 was almost 30% slower than HTTP/1.1 (over TLS) on downloading a huge file (over 1GB).
    
    Improvements:
    - Avoid unnecessary IOBufferBlock allocation for all type of frame
    - Avoid unnecessary copy on sending DATA frame
    - Adjust IOBufferBlock size of Http2ClientSession::write_buffer
    
    Cleanups:
    - Decouple receiving & sending HTTP/2 Frame
    - Remove unnecessary SCOPED_MUTEX_LOCK
---
 proxy/http2/HTTP2.cc                |  18 ---
 proxy/http2/HTTP2.h                 |   7 +-
 proxy/http2/Http2ClientSession.cc   |  27 ++--
 proxy/http2/Http2ClientSession.h    |  92 +------------
 proxy/http2/Http2ConnectionState.cc | 175 ++++++++-----------------
 proxy/http2/Http2Frame.cc           | 253 ++++++++++++++++++++++++++++++++++++
 proxy/http2/Http2Frame.h            | 252 +++++++++++++++++++++++++++++++++++
 proxy/http2/Http2Stream.cc          |   4 +-
 proxy/http2/Makefile.am             |   2 +
 9 files changed, 585 insertions(+), 245 deletions(-)

diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index 0a97482..e23c44d 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -244,24 +244,6 @@ http2_write_frame_header(const Http2FrameHeader &hdr, IOVec iov)
 }
 
 bool
-http2_write_data(const uint8_t *src, size_t length, const IOVec &iov)
-{
-  byte_pointer ptr(iov.iov_base);
-  write_and_advance(ptr, src, length);
-
-  return true;
-}
-
-bool
-http2_write_headers(const uint8_t *src, size_t length, const IOVec &iov)
-{
-  byte_pointer ptr(iov.iov_base);
-  write_and_advance(ptr, src, length);
-
-  return true;
-}
-
-bool
 http2_write_rst_stream(uint32_t error_code, IOVec iov)
 {
   byte_pointer ptr(iov.iov_base);
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index 6d2fad5..4151887 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -33,6 +33,9 @@ class HTTPHdr;
 
 typedef unsigned Http2StreamId;
 
+constexpr Http2StreamId HTTP2_CONNECTION_CONTROL_STRTEAM = 0;
+constexpr uint8_t HTTP2_FRAME_NO_FLAG                    = 0;
+
 // [RFC 7540] 6.9.2. Initial Flow Control Window Size
 // the flow control window can be come negative so we need to track it with a signed type.
 typedef int32_t Http2WindowSize;
@@ -320,10 +323,6 @@ bool http2_parse_frame_header(IOVec, Http2FrameHeader &);
 
 bool http2_write_frame_header(const Http2FrameHeader &, IOVec);
 
-bool http2_write_data(const uint8_t *, size_t, const IOVec &);
-
-bool http2_write_headers(const uint8_t *, size_t, const IOVec &);
-
 bool http2_write_rst_stream(uint32_t, IOVec);
 
 bool http2_write_settings(const Http2SettingsParameter &, const IOVec &);
diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc
index 10378c7..127b938 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -213,7 +213,8 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB
   this->read_buffer->water_mark = connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE);
   this->_reader                 = reader ? reader : this->read_buffer->alloc_reader();
 
-  this->write_buffer = new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX);
+  // Set write buffer size to max size of TLS record (16KB)
+  this->write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_16K);
   this->sm_writer    = this->write_buffer->alloc_reader();
 
   this->_handle_if_ssl(new_vc);
@@ -332,6 +333,19 @@ Http2ClientSession::set_half_close_local_flag(bool flag)
   half_close_local = flag;
 }
 
+int64_t
+Http2ClientSession::xmit(const Http2TxFrame &frame)
+{
+  int64_t len = frame.write_to(this->write_buffer);
+
+  if (len > 0) {
+    total_write_len += len;
+    write_reenable();
+  }
+
+  return len;
+}
+
 int
 Http2ClientSession::main_event_handler(int event, void *edata)
 {
@@ -356,16 +370,6 @@ Http2ClientSession::main_event_handler(int event, void *edata)
     break;
   }
 
-  case HTTP2_SESSION_EVENT_XMIT: {
-    Http2Frame *frame = static_cast<Http2Frame *>(edata);
-    total_write_len += frame->size();
-    write_vio->nbytes = total_write_len;
-    frame->xmit(this->write_buffer);
-    write_reenable();
-    retval = 0;
-    break;
-  }
-
   case HTTP2_SESSION_EVENT_REENABLE:
     // VIO will be reenableed in this handler
     retval = (this->*session_handler)(VC_EVENT_READ_READY, static_cast<VIO *>(e->cookie));
@@ -391,6 +395,7 @@ Http2ClientSession::main_event_handler(int event, void *edata)
     retval = 0;
     break;
 
+  case HTTP2_SESSION_EVENT_XMIT:
   default:
     Http2SsnDebug("unexpected event=%d edata=%p", event, edata);
     ink_release_assert(0);
diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h
index c3a7209..c03a053 100644
--- a/proxy/http2/Http2ClientSession.h
+++ b/proxy/http2/Http2ClientSession.h
@@ -27,6 +27,7 @@
 #include "Plugin.h"
 #include "ProxySession.h"
 #include "Http2ConnectionState.h"
+#include "Http2Frame.h"
 #include <string_view>
 #include "tscore/ink_inet.h"
 #include "tscore/History.h"
@@ -77,96 +78,6 @@ struct Http2UpgradeContext {
   Http2ConnectionSettings client_settings;
 };
 
-class Http2Frame
-{
-public:
-  Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : hdr(h), ioreader(r), from_early_data(e) {}
-  Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags, bool e = false)
-    : hdr({0, (uint8_t)type, flags, streamid}), from_early_data(e)
-  {
-  }
-
-  IOBufferReader *
-  reader() const
-  {
-    return ioreader;
-  }
-
-  const Http2FrameHeader &
-  header() const
-  {
-    return this->hdr;
-  }
-
-  // Allocate an IOBufferBlock for payload of this frame.
-  void
-  alloc(int index)
-  {
-    this->ioblock = new_IOBufferBlock();
-    this->ioblock->alloc(index);
-  }
-
-  // Return the writeable buffer space for frame payload
-  IOVec
-  write()
-  {
-    return make_iovec(this->ioblock->end(), this->ioblock->write_avail());
-  }
-
-  // Once the frame has been serialized, update the payload length of frame header.
-  void
-  finalize(size_t nbytes)
-  {
-    if (this->ioblock) {
-      ink_assert((int64_t)nbytes <= this->ioblock->write_avail());
-      this->ioblock->fill(nbytes);
-
-      this->hdr.length = this->ioblock->size();
-    }
-  }
-
-  void
-  xmit(MIOBuffer *iobuffer)
-  {
-    // Write frame header
-    uint8_t buf[HTTP2_FRAME_HEADER_LEN];
-    http2_write_frame_header(hdr, make_iovec(buf));
-    iobuffer->write(buf, sizeof(buf));
-
-    // Write frame payload
-    // It could be empty (e.g. SETTINGS frame with ACK flag)
-    if (ioblock && ioblock->read_avail() > 0) {
-      iobuffer->append_block(this->ioblock.get());
-    }
-  }
-
-  int64_t
-  size()
-  {
-    if (ioblock) {
-      return HTTP2_FRAME_HEADER_LEN + ioblock->size();
-    } else {
-      return HTTP2_FRAME_HEADER_LEN;
-    }
-  }
-
-  bool
-  is_from_early_data() const
-  {
-    return this->from_early_data;
-  }
-
-  // noncopyable
-  Http2Frame(Http2Frame &) = delete;
-  Http2Frame &operator=(const Http2Frame &) = delete;
-
-private:
-  Http2FrameHeader hdr;       // frame header
-  Ptr<IOBufferBlock> ioblock; // frame payload
-  IOBufferReader *ioreader = nullptr;
-  bool from_early_data     = false;
-};
-
 class Http2ClientSession : public ProxySession
 {
 public:
@@ -194,6 +105,7 @@ public:
 
   // more methods
   void write_reenable();
+  int64_t xmit(const Http2TxFrame &frame);
 
   ////////////////////
   // Accessors
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index fed9cd2..57a40a6 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -25,6 +25,7 @@
 #include "Http2ConnectionState.h"
 #include "Http2ClientSession.h"
 #include "Http2Stream.h"
+#include "Http2Frame.h"
 #include "Http2DebugNames.h"
 #include "HttpDebugNames.h"
 #include <sstream>
@@ -49,12 +50,12 @@ static const int buffer_size_index[HTTP2_FRAME_TYPE_MAX] = {
   BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_DATA
   BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_HEADERS
   -1,                    // HTTP2_FRAME_TYPE_PRIORITY
-  BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_RST_STREAM
-  BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_SETTINGS
+  -1,                    // HTTP2_FRAME_TYPE_RST_STREAM
+  -1,                    // HTTP2_FRAME_TYPE_SETTINGS
   BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_PUSH_PROMISE
-  BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_PING
-  BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_GOAWAY
-  BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_WINDOW_UPDATE
+  -1,                    // HTTP2_FRAME_TYPE_PING
+  -1,                    // HTTP2_FRAME_TYPE_GOAWAY
+  -1,                    // HTTP2_FRAME_TYPE_WINDOW_UPDATE
   BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_CONTINUATION
 };
 
@@ -639,8 +640,8 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame)
 
   // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST
   // immediately emit a SETTINGS frame with the ACK flag set.
-  Http2Frame ackFrame(HTTP2_FRAME_TYPE_SETTINGS, 0, HTTP2_FLAGS_SETTINGS_ACK);
-  cstate.ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ackFrame);
+  Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK);
+  cstate.ua_session->xmit(ack_frame);
 
   return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
 }
@@ -1481,27 +1482,29 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len
   const size_t write_available_size = std::min(buf_len, static_cast<size_t>(window_size));
   payload_length                    = 0;
 
-  uint8_t flags = 0x00;
-  uint8_t payload_buffer[buf_len];
-  IOBufferReader *_sm = stream->response_get_data_reader();
+  uint8_t flags               = 0x00;
+  IOBufferReader *resp_reader = stream->response_get_data_reader();
 
   SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
 
-  if (!_sm) {
+  if (!resp_reader) {
     Http2StreamDebug(this->ua_session, stream->get_id(), "couldn't get data reader");
     return Http2SendDataFrameResult::ERROR;
   }
 
   // Select appropriate payload length
-  if (_sm->is_read_avail_more_than(0)) {
+  if (resp_reader->is_read_avail_more_than(0)) {
     // We only need to check for window size when there is a payload
     if (window_size <= 0) {
       Http2StreamDebug(this->ua_session, stream->get_id(), "No window");
       return Http2SendDataFrameResult::NO_WINDOW;
     }
-    // Copy into the payload buffer. Seems like we should be able to skip this copy step
-    payload_length = write_available_size;
-    payload_length = _sm->read(payload_buffer, static_cast<int64_t>(write_available_size));
+
+    if (resp_reader->is_read_avail_more_than(write_available_size)) {
+      payload_length = write_available_size;
+    } else {
+      payload_length = resp_reader->read_avail();
+    }
   } else {
     payload_length = 0;
   }
@@ -1514,7 +1517,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len
     return Http2SendDataFrameResult::NO_PAYLOAD;
   }
 
-  if (stream->is_write_vio_done() && !_sm->is_read_avail_more_than(0)) {
+  if (stream->is_write_vio_done() && !resp_reader->is_read_avail_more_than(0)) {
     flags |= HTTP2_FLAGS_DATA_END_STREAM;
   }
 
@@ -1526,22 +1529,16 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len
   Http2StreamDebug(ua_session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd",
                    _client_rwnd, stream->client_rwnd(), payload_length);
 
-  Http2Frame data(HTTP2_FRAME_TYPE_DATA, stream->get_id(), flags);
-  data.alloc(buffer_size_index[HTTP2_FRAME_TYPE_DATA]);
-  http2_write_data(payload_buffer, payload_length, data.write());
-  data.finalize(payload_length);
+  Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length);
+  this->ua_session->xmit(data);
 
   stream->update_sent_count(payload_length);
 
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &data);
-
   if (flags & HTTP2_FLAGS_DATA_END_STREAM) {
     Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM");
     stream->send_end_stream = true;
     // Setting to the same state shouldn't be erroneous
-    stream->change_state(data.header().type, data.header().flags);
+    stream->change_state(HTTP2_FRAME_TYPE_DATA, flags);
 
     return Http2SendDataFrameResult::DONE;
   }
@@ -1586,10 +1583,8 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
   uint32_t buf_len            = 0;
   uint32_t header_blocks_size = 0;
   int payload_length          = 0;
-  uint64_t sent               = 0;
   uint8_t flags               = 0x00;
-
-  HTTPHdr *resp_header = &stream->response_header;
+  HTTPHdr *resp_header        = &stream->response_header;
 
   Http2StreamDebug(ua_session, stream->get_id(), "Send HEADERS frame");
 
@@ -1626,10 +1621,6 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
   } else {
     payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]);
   }
-  Http2Frame headers(HTTP2_FRAME_TYPE_HEADERS, stream->get_id(), flags);
-  headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]);
-  http2_write_headers(buf, payload_length, headers.write());
-  headers.finalize(payload_length);
 
   // Change stream state
   if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, flags)) {
@@ -1644,10 +1635,9 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
     return;
   }
 
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers);
-  sent += payload_length;
+  Http2HeadersFrame headers(stream->get_id(), flags, buf, payload_length);
+  this->ua_session->xmit(headers);
+  uint64_t sent = payload_length;
 
   // Send CONTINUATION frames
   flags = 0;
@@ -1658,14 +1648,10 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
     if (sent + payload_length == header_blocks_size) {
       flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS;
     }
-    Http2Frame continuation_frame(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags);
-    continuation_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]);
-    http2_write_headers(buf + sent, payload_length, continuation_frame.write());
-    continuation_frame.finalize(payload_length);
-    stream->change_state(continuation_frame.header().type, continuation_frame.header().flags);
-    // xmit event
-    SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-    this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &continuation_frame);
+    stream->change_state(HTTP2_FRAME_TYPE_CONTINUATION, flags);
+
+    Http2ContinuationFrame continuation_frame(stream->get_id(), flags, buf + sent, payload_length);
+    this->ua_session->xmit(continuation_frame);
     sent += payload_length;
   }
 
@@ -1681,7 +1667,6 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
   uint32_t buf_len            = 0;
   uint32_t header_blocks_size = 0;
   int payload_length          = 0;
-  uint64_t sent               = 0;
   uint8_t flags               = 0x00;
 
   if (client_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) {
@@ -1735,16 +1720,13 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
     payload_length =
       BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]) - sizeof(push_promise.promised_streamid);
   }
-  Http2Frame push_promise_frame(HTTP2_FRAME_TYPE_PUSH_PROMISE, stream->get_id(), flags);
-  push_promise_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]);
+
   Http2StreamId id               = this->get_latest_stream_id_out() + 2;
   push_promise.promised_streamid = id;
-  http2_write_push_promise(push_promise, buf, payload_length, push_promise_frame.write());
-  push_promise_frame.finalize(sizeof(push_promise.promised_streamid) + payload_length);
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &push_promise_frame);
-  sent += payload_length;
+
+  Http2PushPromiseFrame push_promise_frame(stream->get_id(), flags, push_promise, buf, payload_length);
+  this->ua_session->xmit(push_promise_frame);
+  uint64_t sent = payload_length;
 
   // Send CONTINUATION frames
   flags = 0;
@@ -1755,13 +1737,9 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
     if (sent + payload_length == header_blocks_size) {
       flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS;
     }
-    Http2Frame continuation_frame(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags);
-    continuation_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]);
-    http2_write_headers(buf + sent, payload_length, continuation_frame.write());
-    continuation_frame.finalize(payload_length);
-    // xmit event
-    SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-    this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &continuation_frame);
+
+    Http2ContinuationFrame continuation(stream->get_id(), flags, buf + sent, payload_length);
+    this->ua_session->xmit(continuation);
     sent += payload_length;
   }
   ats_free(buf);
@@ -1806,12 +1784,6 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec)
     ++stream_error_count;
   }
 
-  Http2Frame rst_stream(HTTP2_FRAME_TYPE_RST_STREAM, id, 0);
-
-  rst_stream.alloc(buffer_size_index[HTTP2_FRAME_TYPE_RST_STREAM]);
-  http2_write_rst_stream(static_cast<uint32_t>(ec), rst_stream.write());
-  rst_stream.finalize(HTTP2_RST_STREAM_LEN);
-
   // change state to closed
   Http2Stream *stream = find_stream(id);
   if (stream != nullptr) {
@@ -1827,9 +1799,8 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec)
     }
   }
 
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &rst_stream);
+  Http2RstStreamFrame rst_stream(id, static_cast<uint32_t>(ec));
+  this->ua_session->xmit(rst_stream);
 }
 
 void
@@ -1839,11 +1810,8 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set
 
   Http2StreamDebug(ua_session, stream_id, "Send SETTINGS frame");
 
-  Http2Frame settings(HTTP2_FRAME_TYPE_SETTINGS, stream_id, 0);
-  settings.alloc(buffer_size_index[HTTP2_FRAME_TYPE_SETTINGS]);
-
-  IOVec iov                = settings.write();
-  uint32_t settings_length = 0;
+  Http2SettingsParameter params[HTTP2_SETTINGS_MAX];
+  size_t params_size = 0;
 
   for (int i = HTTP2_SETTINGS_HEADER_TABLE_SIZE; i < HTTP2_SETTINGS_MAX; ++i) {
     Http2SettingsIdentifier id = static_cast<Http2SettingsIdentifier>(i);
@@ -1851,32 +1819,17 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set
 
     // Send only difference
     if (settings_value != server_settings.get(id)) {
-      const Http2SettingsParameter param = {static_cast<uint16_t>(id), settings_value};
+      Http2StreamDebug(ua_session, stream_id, "  %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value);
 
-      // Write settings to send buffer
-      if (!http2_write_settings(param, iov)) {
-        this->send_goaway_frame(this->latest_streamid_in, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR);
-        this->ua_session->set_half_close_local_flag(true);
-        if (fini_event == nullptr) {
-          fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI);
-        }
-
-        return;
-      }
-      iov.iov_base = reinterpret_cast<uint8_t *>(iov.iov_base) + HTTP2_SETTINGS_PARAMETER_LEN;
-      iov.iov_len -= HTTP2_SETTINGS_PARAMETER_LEN;
-      settings_length += HTTP2_SETTINGS_PARAMETER_LEN;
+      params[params_size++] = {static_cast<uint16_t>(id), settings_value};
 
       // Update current settings
       server_settings.set(id, new_settings.get(id));
-
-      Http2StreamDebug(ua_session, stream_id, "  %s : %u", Http2DebugNames::get_settings_param_name(param.id), param.value);
     }
   }
 
-  settings.finalize(settings_length);
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &settings);
+  Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size);
+  this->ua_session->xmit(settings);
 }
 
 void
@@ -1884,15 +1837,8 @@ Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint
 {
   Http2StreamDebug(ua_session, id, "Send PING frame");
 
-  Http2Frame ping(HTTP2_FRAME_TYPE_PING, id, flag);
-
-  ping.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PING]);
-  http2_write_ping(opaque_data, ping.write());
-  ping.finalize(HTTP2_PING_LEN);
-
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ping);
+  Http2PingFrame ping(id, flag, opaque_data);
+  this->ua_session->xmit(ping);
 }
 
 // As for gracefull shutdown, TS should process outstanding stream as long as possible.
@@ -1908,21 +1854,14 @@ Http2ConnectionState::send_goaway_frame(Http2StreamId id, Http2ErrorCode ec)
     HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CONNECTION_ERRORS_COUNT, this_ethread());
   }
 
-  Http2Frame frame(HTTP2_FRAME_TYPE_GOAWAY, 0, 0);
-  Http2Goaway goaway;
+  this->tx_error_code = {ProxyErrorClass::SSN, static_cast<uint32_t>(ec)};
 
+  Http2Goaway goaway;
   goaway.last_streamid = id;
   goaway.error_code    = ec;
 
-  frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_GOAWAY]);
-  http2_write_goaway(goaway, frame.write());
-  frame.finalize(HTTP2_GOAWAY_LEN);
-
-  this->tx_error_code = {ProxyErrorClass::SSN, static_cast<uint32_t>(ec)};
-
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &frame);
+  Http2GoawayFrame frame(goaway);
+  this->ua_session->xmit(frame);
 }
 
 void
@@ -1931,14 +1870,8 @@ Http2ConnectionState::send_window_update_frame(Http2StreamId id, uint32_t size)
   Http2StreamDebug(ua_session, id, "Send WINDOW_UPDATE frame");
 
   // Create WINDOW_UPDATE frame
-  Http2Frame window_update(HTTP2_FRAME_TYPE_WINDOW_UPDATE, id, 0x0);
-  window_update.alloc(buffer_size_index[HTTP2_FRAME_TYPE_WINDOW_UPDATE]);
-  http2_write_window_update(static_cast<uint32_t>(size), window_update.write());
-  window_update.finalize(sizeof(uint32_t));
-
-  // xmit event
-  SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread());
-  this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &window_update);
+  Http2WindowUpdateFrame window_update(id, size);
+  this->ua_session->xmit(window_update);
 }
 
 void
diff --git a/proxy/http2/Http2Frame.cc b/proxy/http2/Http2Frame.cc
new file mode 100644
index 0000000..a731f09
--- /dev/null
+++ b/proxy/http2/Http2Frame.cc
@@ -0,0 +1,253 @@
+/** @file
+
+  Http2Frame
+
+  @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 "Http2Frame.h"
+
+//
+// Http2Frame
+//
+IOBufferReader *
+Http2Frame::reader() const
+{
+  return this->_ioreader;
+}
+
+const Http2FrameHeader &
+Http2Frame::header() const
+{
+  return this->_hdr;
+}
+
+bool
+Http2Frame::is_from_early_data() const
+{
+  return this->_from_early_data;
+}
+
+//
+// DATA Frame
+//
+int64_t
+Http2DataFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  if (this->_reader && this->_payload_len > 0) {
+    int64_t written = 0;
+    // Fill current IOBufferBlock as much as possible to reduce SSL_write() calls
+    while (written < this->_payload_len) {
+      int64_t read_len = std::min(this->_payload_len - written, this->_reader->block_read_avail());
+      written += iobuffer->write(this->_reader->start(), read_len);
+      this->_reader->consume(read_len);
+    }
+    len += written;
+  }
+
+  return len;
+}
+
+//
+// HEADERS Frame
+//
+int64_t
+Http2HeadersFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Validation
+  if (this->_hdr_block_len > Http2::max_frame_size) {
+    return -1;
+  }
+
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  if (this->_hdr_block && this->_hdr_block_len > 0) {
+    len += iobuffer->write(this->_hdr_block, this->_hdr_block_len);
+  }
+
+  return len;
+}
+
+//
+// PRIORITY Frame
+//
+int64_t
+Http2PriorityFrame::write_to(MIOBuffer *iobuffer) const
+{
+  ink_abort("not supported yet");
+
+  return 0;
+}
+
+//
+// RST_STREM Frame
+//
+int64_t
+Http2RstStreamFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  uint8_t payload[HTTP2_RST_STREAM_LEN];
+  http2_write_rst_stream(this->_error_code, make_iovec(payload));
+  len += iobuffer->write(payload, sizeof(payload));
+
+  return len;
+}
+
+//
+// SETTINGS Frame
+//
+int64_t
+Http2SettingsFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  for (uint32_t i = 0; i < this->_psize; ++i) {
+    Http2SettingsParameter *p = this->_params + i;
+
+    uint8_t p_buf[HTTP2_SETTINGS_PARAMETER_LEN];
+    http2_write_settings(*p, make_iovec(p_buf));
+    len += iobuffer->write(p_buf, sizeof(p_buf));
+  }
+
+  return len;
+}
+
+//
+// PUSH_PROMISE Frame
+//
+int64_t
+Http2PushPromiseFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Validation
+  if (this->_hdr_block_len > Http2::max_frame_size) {
+    return -1;
+  }
+
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  uint8_t p_buf[HTTP2_MAX_FRAME_SIZE];
+  http2_write_push_promise(this->_params, this->_hdr_block, this->_hdr_block_len, make_iovec(p_buf));
+  len += iobuffer->write(p_buf, sizeof(Http2StreamId) + this->_hdr_block_len);
+
+  return len;
+}
+
+//
+// PING Frame
+//
+int64_t
+Http2PingFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  uint8_t payload[HTTP2_PING_LEN] = {0};
+  http2_write_ping(this->_opaque_data, make_iovec(payload));
+  len += iobuffer->write(payload, sizeof(payload));
+
+  return len;
+}
+
+//
+// GOAWAY Frame
+//
+int64_t
+Http2GoawayFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  uint8_t payload[HTTP2_GOAWAY_LEN];
+  http2_write_goaway(this->_params, make_iovec(payload));
+  len += iobuffer->write(payload, sizeof(payload));
+
+  return len;
+}
+
+//
+// WINDOW_UPDATE Frame
+//
+int64_t
+Http2WindowUpdateFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  uint8_t payload[HTTP2_WINDOW_UPDATE_LEN];
+  http2_write_window_update(this->_window, make_iovec(payload));
+  len += iobuffer->write(payload, sizeof(payload));
+
+  return len;
+}
+
+//
+// CONTINUATION Frame
+//
+int64_t
+Http2ContinuationFrame::write_to(MIOBuffer *iobuffer) const
+{
+  // Validation
+  if (this->_hdr_block_len > Http2::max_frame_size) {
+    return -1;
+  }
+
+  // Write frame header
+  uint8_t buf[HTTP2_FRAME_HEADER_LEN];
+  http2_write_frame_header(this->_hdr, make_iovec(buf));
+  int64_t len = iobuffer->write(buf, sizeof(buf));
+
+  // Write frame payload
+  if (this->_hdr_block && this->_hdr_block_len > 0) {
+    len += iobuffer->write(this->_hdr_block, this->_hdr_block_len);
+  }
+
+  return len;
+}
diff --git a/proxy/http2/Http2Frame.h b/proxy/http2/Http2Frame.h
new file mode 100644
index 0000000..1715dd5
--- /dev/null
+++ b/proxy/http2/Http2Frame.h
@@ -0,0 +1,252 @@
+/** @file
+
+  Http2Frame
+
+  @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 "P_Net.h"
+
+#include "HTTP2.h"
+
+/**
+   Incoming HTTP/2 Frame
+ */
+class Http2Frame
+{
+public:
+  Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : _hdr(h), _ioreader(r), _from_early_data(e) {}
+
+  // Accessor
+  IOBufferReader *reader() const;
+  const Http2FrameHeader &header() const;
+  bool is_from_early_data() const;
+
+private:
+  Http2FrameHeader _hdr;
+  IOBufferReader *_ioreader = nullptr;
+  bool _from_early_data     = false;
+};
+
+/**
+   Outgoing HTTP/2 Frame
+ */
+class Http2TxFrame
+{
+public:
+  Http2TxFrame(const Http2FrameHeader &h) : _hdr(h) {}
+
+  // Don't allocate on heap
+  void *operator new(std::size_t)   = delete;
+  void *operator new[](std::size_t) = delete;
+
+  virtual int64_t write_to(MIOBuffer *iobuffer) const = 0;
+
+protected:
+  Http2FrameHeader _hdr;
+};
+
+/**
+   DATA Frame
+ */
+class Http2DataFrame : public Http2TxFrame
+{
+public:
+  Http2DataFrame(Http2StreamId stream_id, uint8_t flags, IOBufferReader *r, uint32_t l)
+    : Http2TxFrame({l, HTTP2_FRAME_TYPE_DATA, flags, stream_id}), _reader(r), _payload_len(l)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  IOBufferReader *_reader = nullptr;
+  uint32_t _payload_len   = 0;
+};
+
+/**
+   HEADERS Frame
+
+   TODO: support priority info & padding using Http2HeadersParameter
+ */
+class Http2HeadersFrame : public Http2TxFrame
+{
+public:
+  Http2HeadersFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l)
+    : Http2TxFrame({l, HTTP2_FRAME_TYPE_HEADERS, flags, stream_id}), _hdr_block(h), _hdr_block_len(l)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  uint8_t *_hdr_block     = nullptr;
+  uint32_t _hdr_block_len = 0;
+};
+
+/**
+   PRIORITY Frame
+
+   TODO: implement xmit function
+ */
+class Http2PriorityFrame : public Http2TxFrame
+{
+public:
+  Http2PriorityFrame(Http2StreamId stream_id, uint8_t flags, Http2Priority p)
+    : Http2TxFrame({HTTP2_PRIORITY_LEN, HTTP2_FRAME_TYPE_PRIORITY, flags, stream_id}), _params(p)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  Http2Priority _params;
+};
+
+/**
+   RST_STREAM Frame
+ */
+class Http2RstStreamFrame : public Http2TxFrame
+{
+public:
+  Http2RstStreamFrame(Http2StreamId stream_id, uint32_t e)
+    : Http2TxFrame({HTTP2_RST_STREAM_LEN, HTTP2_FRAME_TYPE_RST_STREAM, HTTP2_FRAME_NO_FLAG, stream_id}), _error_code(e)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  uint32_t _error_code;
+};
+
+/**
+   SETTINGS Frame
+ */
+class Http2SettingsFrame : public Http2TxFrame
+{
+public:
+  Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags) : Http2TxFrame({0, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}) {}
+  Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags, Http2SettingsParameter *p, uint32_t s)
+    : Http2TxFrame({static_cast<uint32_t>(HTTP2_SETTINGS_PARAMETER_LEN) * s, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}),
+      _params(p),
+      _psize(s)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  Http2SettingsParameter *_params = nullptr;
+  uint32_t _psize                 = 0;
+};
+
+/**
+   PUSH_PROMISE Frame
+
+   TODO: support padding
+ */
+class Http2PushPromiseFrame : public Http2TxFrame
+{
+public:
+  Http2PushPromiseFrame(Http2StreamId stream_id, uint8_t flags, Http2PushPromise p, uint8_t *h, uint32_t l)
+    : Http2TxFrame({l, HTTP2_FRAME_TYPE_PUSH_PROMISE, flags, stream_id}), _params(p), _hdr_block(h), _hdr_block_len(l)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  Http2PushPromise _params;
+  uint8_t *_hdr_block     = nullptr;
+  uint32_t _hdr_block_len = 0;
+};
+
+/**
+   PING Frame
+ */
+class Http2PingFrame : public Http2TxFrame
+{
+public:
+  Http2PingFrame(Http2StreamId stream_id, uint8_t flags, const uint8_t *data)
+    : Http2TxFrame({HTTP2_PING_LEN, HTTP2_FRAME_TYPE_PING, flags, stream_id}), _opaque_data(data)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  const uint8_t *_opaque_data;
+};
+
+/**
+   GOAWAY Frame
+
+   TODO: support Additional Debug Data
+ */
+class Http2GoawayFrame : public Http2TxFrame
+{
+public:
+  Http2GoawayFrame(Http2Goaway p)
+    : Http2TxFrame({HTTP2_GOAWAY_LEN, HTTP2_FRAME_TYPE_GOAWAY, HTTP2_FRAME_NO_FLAG, HTTP2_CONNECTION_CONTROL_STRTEAM}), _params(p)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  Http2Goaway _params;
+};
+
+/**
+   WINDOW_UPDATE Frame
+ */
+class Http2WindowUpdateFrame : public Http2TxFrame
+{
+public:
+  Http2WindowUpdateFrame(Http2StreamId stream_id, uint32_t w)
+    : Http2TxFrame({HTTP2_WINDOW_UPDATE_LEN, HTTP2_FRAME_TYPE_WINDOW_UPDATE, HTTP2_FRAME_NO_FLAG, stream_id}), _window(w)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  uint32_t _window = 0;
+};
+
+/**
+   CONTINUATION Frame
+ */
+class Http2ContinuationFrame : public Http2TxFrame
+{
+public:
+  Http2ContinuationFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l)
+    : Http2TxFrame({l, HTTP2_FRAME_TYPE_CONTINUATION, flags, stream_id}), _hdr_block(h), _hdr_block_len(l)
+  {
+  }
+
+  int64_t write_to(MIOBuffer *iobuffer) const override;
+
+private:
+  uint8_t *_hdr_block     = nullptr;
+  uint32_t _hdr_block_len = 0;
+};
diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc
index ecbec35..77de1d1 100644
--- a/proxy/http2/Http2Stream.cc
+++ b/proxy/http2/Http2Stream.cc
@@ -384,7 +384,9 @@ Http2Stream::do_io_close(int /* flags */)
     if (_proxy_ssn && this->is_client_state_writeable()) {
       // Make sure any trailing end of stream frames are sent
       // Wee will be removed at send_data_frames or closing connection phase
-      static_cast<Http2ClientSession *>(_proxy_ssn)->connection_state.send_data_frames(this);
+      Http2ClientSession *h2_proxy_ssn = static_cast<Http2ClientSession *>(this->_proxy_ssn);
+      SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread());
+      h2_proxy_ssn->connection_state.send_data_frames(this);
     }
 
     clear_timers();
diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am
index 3b33d36..6ca54fe 100644
--- a/proxy/http2/Makefile.am
+++ b/proxy/http2/Makefile.am
@@ -38,6 +38,8 @@ libhttp2_a_SOURCES = \
 	HPACK.h \
 	HTTP2.cc \
 	HTTP2.h \
+	Http2Frame.cc \
+	Http2Frame.h \
 	Http2ClientSession.cc \
 	Http2ClientSession.h \
 	Http2ConnectionState.cc \