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 \