You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/09/03 19:08:59 UTC
[trafficserver] 02/05: Optimize HTTPHdr conversion of HTTP/1.1 to
HTTP/2
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 53ddfaf8e0e3e0efb0151b28f4fe218bae7d4963
Author: Masaori Koshiba <ma...@apache.org>
AuthorDate: Tue Mar 24 08:45:01 2020 +0900
Optimize HTTPHdr conversion of HTTP/1.1 to HTTP/2
Avoid extra HdrHeap allocation and copy all headers.
(cherry picked from commit 3b0716d9f61ad276beab2d6d7a74d804b5b52cd5)
---
proxy/http2/HTTP2.cc | 231 ++++++++++++++++++++++++-----------
proxy/http2/HTTP2.h | 16 ++-
proxy/http2/Http2ConnectionState.cc | 61 ++++-----
proxy/http2/Http2Stream.cc | 6 +-
proxy/http2/Makefile.am | 1 +
proxy/http2/unit_tests/test_HTTP2.cc | 169 +++++++++++++++++++++++++
proxy/http3/Http3HeaderFramer.cc | 11 +-
proxy/http3/Http3HeaderFramer.h | 2 +-
8 files changed, 385 insertions(+), 112 deletions(-)
diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc
index 6616f03..bb4b4df 100644
--- a/proxy/http2/HTTP2.cc
+++ b/proxy/http2/HTTP2.cc
@@ -45,6 +45,16 @@ const unsigned HTTP2_LEN_STATUS = countof(":status") - 1;
static size_t HTTP2_LEN_STATUS_VALUE_STR = 3;
static const uint32_t HTTP2_MAX_TABLE_SIZE_LIMIT = 64 * 1024;
+namespace
+{
+struct Http2HeaderName {
+ const char *name = nullptr;
+ int name_len = 0;
+};
+
+Http2HeaderName http2_connection_specific_headers[5] = {};
+} // namespace
+
// Statistics
RecRawStatBlock *http2_rsb;
static const char *const HTTP2_STAT_CURRENT_CLIENT_CONNECTION_NAME = "proxy.process.http2.current_client_connections";
@@ -501,87 +511,146 @@ http2_convert_header_from_2_to_1_1(HTTPHdr *headers)
return PARSE_RESULT_DONE;
}
+/**
+ Initialize HTTPHdr for HTTP/2
+
+ Reserve HTTP/2 Pseudo-Header Fields in front of HTTPHdr. Value of these header fields will be set by
+ `http2_convert_header_from_1_1_to_2()`. When a HTTPHdr for HTTP/2 headers is created, this should be called immediately.
+ Because all pseudo-header fields MUST appear in the header block before regular header fields.
+ */
void
-http2_generate_h2_header_from_1_1(HTTPHdr *headers, HTTPHdr *h2_headers)
+http2_init_pseudo_headers(HTTPHdr &hdr)
{
- h2_headers->create(http_hdr_type_get(headers->m_http));
+ switch (http_hdr_type_get(hdr.m_http)) {
+ case HTTP_TYPE_REQUEST: {
+ MIMEField *method = hdr.field_create(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD);
+ hdr.field_attach(method);
+
+ MIMEField *scheme = hdr.field_create(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME);
+ hdr.field_attach(scheme);
+
+ MIMEField *authority = hdr.field_create(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY);
+ hdr.field_attach(authority);
+
+ MIMEField *path = hdr.field_create(HTTP2_VALUE_PATH, HTTP2_LEN_PATH);
+ hdr.field_attach(path);
+
+ break;
+ }
+ case HTTP_TYPE_RESPONSE: {
+ MIMEField *status = hdr.field_create(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS);
+ hdr.field_attach(status);
+
+ break;
+ }
+ default:
+ ink_abort("HTTP_TYPE_UNKNOWN");
+ }
+}
+
+/**
+ Convert HTTP/1.1 HTTPHdr to HTTP/2
+
+ Assuming HTTP/2 Pseudo-Header Fields are reserved by `http2_init_pseudo_headers()`.
+ */
+ParseResult
+http2_convert_header_from_1_1_to_2(HTTPHdr *headers)
+{
+ switch (http_hdr_type_get(headers->m_http)) {
+ case HTTP_TYPE_REQUEST: {
+ // :method
+ if (MIMEField *field = headers->field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD); field != nullptr) {
+ int value_len;
+ const char *value = headers->method_get(&value_len);
+
+ field->value_set(headers->m_heap, headers->m_mime, value, value_len);
+ } else {
+ ink_abort("initialize HTTP/2 pseudo-headers");
+ return PARSE_RESULT_ERROR;
+ }
+
+ // :scheme
+ if (MIMEField *field = headers->field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME); field != nullptr) {
+ int value_len;
+ const char *value = headers->scheme_get(&value_len);
+
+ if (value != nullptr) {
+ field->value_set(headers->m_heap, headers->m_mime, value, value_len);
+ } else {
+ field->value_set(headers->m_heap, headers->m_mime, URL_SCHEME_HTTPS, URL_LEN_HTTPS);
+ }
+ } else {
+ ink_abort("initialize HTTP/2 pseudo-headers");
+ return PARSE_RESULT_ERROR;
+ }
+
+ // :authority
+ if (MIMEField *field = headers->field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY); field != nullptr) {
+ int value_len;
+ const char *value = headers->host_get(&value_len);
+
+ if (headers->is_port_in_header()) {
+ int port = headers->port_get();
+ char *host_and_port = static_cast<char *>(ats_malloc(value_len + 8));
+ value_len = snprintf(host_and_port, value_len + 8, "%.*s:%d", value_len, value, port);
+
+ field->value_set(headers->m_heap, headers->m_mime, host_and_port, value_len);
+ ats_free(host_and_port);
+ } else {
+ field->value_set(headers->m_heap, headers->m_mime, value, value_len);
+ }
+ } else {
+ ink_abort("initialize HTTP/2 pseudo-headers");
+ return PARSE_RESULT_ERROR;
+ }
- if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_RESPONSE) {
- // Add ':status' header field
- char status_str[HTTP2_LEN_STATUS_VALUE_STR + 1];
- snprintf(status_str, sizeof(status_str), "%d", headers->status_get());
- MIMEField *status_field = h2_headers->field_create(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS);
- status_field->value_set(h2_headers->m_heap, h2_headers->m_mime, status_str, HTTP2_LEN_STATUS_VALUE_STR);
- h2_headers->field_attach(status_field);
+ // :path
+ if (MIMEField *field = headers->field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH); field != nullptr) {
+ int value_len;
+ const char *value = headers->path_get(&value_len);
+ char *path = static_cast<char *>(ats_malloc(value_len + 1));
+ path[0] = '/';
+ memcpy(path + 1, value, value_len);
- } else if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_REQUEST) {
- MIMEField *field;
- const char *value;
- int value_len;
+ field->value_set(headers->m_heap, headers->m_mime, path, value_len + 1);
+ ats_free(path);
+ } else {
+ ink_abort("initialize HTTP/2 pseudo-headers");
+ return PARSE_RESULT_ERROR;
+ }
- // 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()) {
- int port = headers->port_get();
- char *host_and_port = static_cast<char *>(ats_malloc(value_len + 8));
- value_len = snprintf(host_and_port, value_len + 8, "%.*s:%d", value_len, value, port);
- field->value_set(h2_headers->m_heap, h2_headers->m_mime, host_and_port, value_len);
- ats_free(host_and_port);
+ // [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.
+
+ break;
+ }
+ case HTTP_TYPE_RESPONSE: {
+ // :status
+ if (MIMEField *field = headers->field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS); field != nullptr) {
+ // ink_small_itoa() requires 5+ buffer length
+ char status_str[HTTP2_LEN_STATUS_VALUE_STR + 3];
+ mime_format_int(status_str, headers->status_get(), sizeof(status_str));
+
+ field->value_set(headers->m_heap, headers->m_mime, status_str, HTTP2_LEN_STATUS_VALUE_STR);
} else {
- field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len);
+ ink_abort("initialize HTTP/2 pseudo-headers");
+ return PARSE_RESULT_ERROR;
}
- h2_headers->field_attach(field);
-
- // Add ':method' header field
- field = h2_headers->field_create(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD);
- value = headers->method_get(&value_len);
- field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len);
- h2_headers->field_attach(field);
-
- // Add ':path' header field
- field = h2_headers->field_create(HTTP2_VALUE_PATH, HTTP2_LEN_PATH);
- value = headers->path_get(&value_len);
- char *path = static_cast<char *>(ats_malloc(value_len + 1));
- path[0] = '/';
- memcpy(path + 1, value, value_len);
- field->value_set(h2_headers->m_heap, h2_headers->m_mime, path, value_len + 1);
- ats_free(path);
- h2_headers->field_attach(field);
-
- // Add ':scheme' header field
- field = h2_headers->field_create(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME);
- value = headers->scheme_get(&value_len);
- field->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len);
- h2_headers->field_attach(field);
- }
-
- // Copy headers
+ break;
+ }
+ default:
+ ink_abort("HTTP_TYPE_UNKNOWN");
+ }
+
// Intermediaries SHOULD remove connection-specific header fields.
- MIMEFieldIter field_iter;
- for (MIMEField *field = headers->iter_get_first(&field_iter); field != nullptr; field = headers->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;
+ for (auto &h : http2_connection_specific_headers) {
+ if (MIMEField *field = headers->field_find(h.name, h.name_len); field != nullptr) {
+ headers->field_delete(field);
}
- MIMEField *newfield;
- name = field->name_get(&name_len);
- newfield = h2_headers->field_create(name, name_len);
- value = field->value_get(&value_len);
- newfield->value_set(h2_headers->m_heap, h2_headers->m_mime, value, value_len);
- h2_headers->field_attach(newfield);
}
+
+ return PARSE_RESULT_DONE;
}
Http2ErrorCode
@@ -595,6 +664,7 @@ http2_encode_header_blocks(HTTPHdr *in, uint8_t *out, uint32_t out_len, uint32_t
if (maximum_table_size == hpack_get_maximum_table_size(handle)) {
maximum_table_size = -1;
}
+
// TODO: It would be better to split Cookie header value
int64_t result = hpack_encode_header_block(handle, out, out_len, in, maximum_table_size);
if (result < 0) {
@@ -826,6 +896,27 @@ Http2::init()
static_cast<int>(HTTP2_STAT_MAX_PRIORITY_FRAMES_PER_MINUTE_EXCEEDED), RecRawStatSyncSum);
RecRegisterRawStat(http2_rsb, RECT_PROCESS, HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE_NAME, RECD_INT, RECP_PERSISTENT,
static_cast<int>(HTTP2_STAT_INSUFFICIENT_AVG_WINDOW_UPDATE), RecRawStatSyncSum);
+
+ http2_init();
+}
+
+/**
+ mime_init() needs to be called
+ */
+void
+http2_init()
+{
+ ink_assert(MIME_FIELD_CONNECTION != nullptr);
+ ink_assert(MIME_FIELD_KEEP_ALIVE != nullptr);
+ ink_assert(MIME_FIELD_PROXY_CONNECTION != nullptr);
+ ink_assert(MIME_FIELD_TRANSFER_ENCODING != nullptr);
+ ink_assert(MIME_FIELD_UPGRADE != nullptr);
+
+ http2_connection_specific_headers[0] = {MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION};
+ http2_connection_specific_headers[1] = {MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE};
+ http2_connection_specific_headers[2] = {MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION};
+ http2_connection_specific_headers[3] = {MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING};
+ http2_connection_specific_headers[4] = {MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE};
}
#if TS_HAS_TESTS
diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h
index 7bcc75f..9661645 100644
--- a/proxy/http2/HTTP2.h
+++ b/proxy/http2/HTTP2.h
@@ -43,6 +43,18 @@ typedef int32_t Http2WindowSize;
extern const char *const HTTP2_CONNECTION_PREFACE;
const size_t HTTP2_CONNECTION_PREFACE_LEN = 24;
+extern const char *HTTP2_VALUE_SCHEME;
+extern const char *HTTP2_VALUE_METHOD;
+extern const char *HTTP2_VALUE_AUTHORITY;
+extern const char *HTTP2_VALUE_PATH;
+extern const char *HTTP2_VALUE_STATUS;
+
+extern const unsigned HTTP2_LEN_SCHEME;
+extern const unsigned HTTP2_LEN_METHOD;
+extern const unsigned HTTP2_LEN_AUTHORITY;
+extern const unsigned HTTP2_LEN_PATH;
+extern const unsigned HTTP2_LEN_STATUS;
+
const size_t HTTP2_FRAME_HEADER_LEN = 9;
const size_t HTTP2_DATA_PADLEN_LEN = 1;
const size_t HTTP2_HEADERS_PADLEN_LEN = 1;
@@ -355,7 +367,9 @@ Http2ErrorCode http2_decode_header_blocks(HTTPHdr *, const uint8_t *, const uint
Http2ErrorCode http2_encode_header_blocks(HTTPHdr *, uint8_t *, uint32_t, uint32_t *, HpackHandle &, int32_t);
ParseResult http2_convert_header_from_2_to_1_1(HTTPHdr *);
-void http2_generate_h2_header_from_1_1(HTTPHdr *headers, HTTPHdr *h2_headers);
+ParseResult http2_convert_header_from_1_1_to_2(HTTPHdr *);
+void http2_init_pseudo_headers(HTTPHdr &);
+void http2_init();
// Not sure where else to put this, but figure this is as good of a start as
// anything else.
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index e295318..23beaff 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -28,6 +28,9 @@
#include "Http2Frame.h"
#include "Http2DebugNames.h"
#include "HttpDebugNames.h"
+
+#include "tscpp/util/PostScript.h"
+
#include <sstream>
#include <numeric>
@@ -1633,25 +1636,22 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
uint32_t header_blocks_size = 0;
int payload_length = 0;
uint8_t flags = 0x00;
- HTTPHdr *resp_header = &stream->response_header;
Http2StreamDebug(ua_session, stream->get_id(), "Send HEADERS frame");
- HTTPHdr h2_hdr;
- http2_generate_h2_header_from_1_1(resp_header, &h2_hdr);
+ HTTPHdr *resp_hdr = &stream->response_header;
+ http2_convert_header_from_1_1_to_2(resp_hdr);
- buf_len = resp_header->length_get() * 2; // Make it double just in case
+ buf_len = resp_hdr->length_get() * 2; // Make it double just in case
buf = static_cast<uint8_t *>(ats_malloc(buf_len));
if (buf == nullptr) {
- h2_hdr.destroy();
return;
}
stream->mark_milestone(Http2StreamMilestone::START_ENCODE_HEADERS);
- Http2ErrorCode result = http2_encode_header_blocks(&h2_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
+ Http2ErrorCode result = http2_encode_header_blocks(resp_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
- h2_hdr.destroy();
ats_free(buf);
return;
}
@@ -1660,8 +1660,8 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
if (header_blocks_size <= static_cast<uint32_t>(BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]))) {
payload_length = header_blocks_size;
flags |= HTTP2_FLAGS_HEADERS_END_HEADERS;
- if ((h2_hdr.presence(MIME_PRESENCE_CONTENT_LENGTH) && h2_hdr.get_content_length() == 0) ||
- (!resp_header->expect_final_response() && stream->is_write_vio_done())) {
+ if ((resp_hdr->presence(MIME_PRESENCE_CONTENT_LENGTH) && resp_hdr->get_content_length() == 0) ||
+ (!resp_hdr->expect_final_response() && stream->is_write_vio_done())) {
Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM");
flags |= HTTP2_FLAGS_HEADERS_END_STREAM;
stream->send_end_stream = true;
@@ -1679,7 +1679,6 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI);
}
- h2_hdr.destroy();
ats_free(buf);
return;
}
@@ -1704,14 +1703,12 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream)
sent += payload_length;
}
- h2_hdr.destroy();
ats_free(buf);
}
bool
Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, const MIMEField *accept_encoding)
{
- HTTPHdr h1_hdr, h2_hdr;
uint8_t *buf = nullptr;
uint32_t buf_len = 0;
uint32_t header_blocks_size = 0;
@@ -1724,37 +1721,35 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
Http2StreamDebug(ua_session, stream->get_id(), "Send PUSH_PROMISE frame");
- h1_hdr.create(HTTP_TYPE_REQUEST);
- h1_hdr.url_set(&url);
- h1_hdr.method_set("GET", 3);
+ HTTPHdr hdr;
+ ts::PostScript hdr_defer([&]() -> void { hdr.destroy(); });
+ hdr.create(HTTP_TYPE_REQUEST);
+ http2_init_pseudo_headers(hdr);
+ hdr.url_set(&url);
+ hdr.method_set(HTTP_METHOD_GET, HTTP_LEN_GET);
+
if (accept_encoding != nullptr) {
- MIMEField *f;
- const char *name;
int name_len;
- const char *value;
- int value_len;
+ const char *name = accept_encoding->name_get(&name_len);
+ MIMEField *f = hdr.field_create(name, name_len);
- name = accept_encoding->name_get(&name_len);
- f = h1_hdr.field_create(name, name_len);
- value = accept_encoding->value_get(&value_len);
- f->value_set(h1_hdr.m_heap, h1_hdr.m_mime, value, value_len);
+ int value_len;
+ const char *value = accept_encoding->value_get(&value_len);
+ f->value_set(hdr.m_heap, hdr.m_mime, value, value_len);
- h1_hdr.field_attach(f);
+ hdr.field_attach(f);
}
- http2_generate_h2_header_from_1_1(&h1_hdr, &h2_hdr);
+ http2_convert_header_from_1_1_to_2(&hdr);
- buf_len = h1_hdr.length_get() * 2; // Make it double just in case
- h1_hdr.destroy();
- buf = static_cast<uint8_t *>(ats_malloc(buf_len));
+ buf_len = hdr.length_get() * 2; // Make it double just in case
+ buf = static_cast<uint8_t *>(ats_malloc(buf_len));
if (buf == nullptr) {
- h2_hdr.destroy();
return false;
}
- Http2ErrorCode result = http2_encode_header_blocks(&h2_hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
+ Http2ErrorCode result = http2_encode_header_blocks(&hdr, buf, buf_len, &header_blocks_size, *(this->remote_hpack_handle),
client_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
if (result != Http2ErrorCode::HTTP2_ERROR_NO_ERROR) {
- h2_hdr.destroy();
ats_free(buf);
return false;
}
@@ -1796,7 +1791,6 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
Http2Error error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE);
stream = this->create_stream(id, error);
if (!stream) {
- h2_hdr.destroy();
return false;
}
@@ -1815,11 +1809,10 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con
}
}
stream->change_state(HTTP2_FRAME_TYPE_PUSH_PROMISE, HTTP2_FLAGS_PUSH_PROMISE_END_HEADERS);
- stream->set_request_headers(h2_hdr);
+ stream->set_request_headers(hdr);
stream->new_transaction();
stream->send_request(*this);
- h2_hdr.destroy();
return true;
}
diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc
index ddf887c..eef4460 100644
--- a/proxy/http2/Http2Stream.cc
+++ b/proxy/http2/Http2Stream.cc
@@ -54,9 +54,12 @@ Http2Stream::init(Http2StreamId sid, ssize_t initial_rwnd)
this->_server_rwnd = Http2::initial_window_size;
this->_reader = this->_request_buffer.alloc_reader();
- // FIXME: Are you sure? every "stream" needs request_header?
+
_req_header.create(HTTP_TYPE_REQUEST);
response_header.create(HTTP_TYPE_RESPONSE);
+ // TODO: init _req_header instead of response_header if this Http2Stream is outgoing
+ http2_init_pseudo_headers(response_header);
+
http_parser_init(&http_parser);
}
@@ -624,6 +627,7 @@ Http2Stream::update_write_request(IOBufferReader *buf_reader, int64_t write_len,
this->response_header_done = false;
response_header.destroy();
response_header.create(HTTP_TYPE_RESPONSE);
+ http2_init_pseudo_headers(response_header);
http_parser_clear(&http_parser);
http_parser_init(&http_parser);
}
diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am
index 7f77e90..100909b 100644
--- a/proxy/http2/Makefile.am
+++ b/proxy/http2/Makefile.am
@@ -84,6 +84,7 @@ test_libhttp2_CPPFLAGS = $(AM_CPPFLAGS)\
-I$(abs_top_srcdir)/tests/include
test_libhttp2_SOURCES = \
+ unit_tests/test_HTTP2.cc \
unit_tests/test_Http2Frame.cc \
unit_tests/main.cc
diff --git a/proxy/http2/unit_tests/test_HTTP2.cc b/proxy/http2/unit_tests/test_HTTP2.cc
new file mode 100644
index 0000000..e67f50e
--- /dev/null
+++ b/proxy/http2/unit_tests/test_HTTP2.cc
@@ -0,0 +1,169 @@
+/** @file
+
+ Unit tests for HTTP2
+
+ @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 "catch.hpp"
+
+#include "HTTP2.h"
+
+#include "tscpp/util/PostScript.h"
+
+TEST_CASE("Convert HTTPHdr", "[HTTP2]")
+{
+ url_init();
+ mime_init();
+ http_init();
+ http2_init();
+
+ HTTPParser parser;
+ ts::PostScript parser_defer([&]() -> void { http_parser_clear(&parser); });
+ http_parser_init(&parser);
+
+ SECTION("request")
+ {
+ const char request[] = "GET /index.html HTTP/1.1\r\n"
+ "Host: trafficserver.apache.org\r\n"
+ "User-Agent: foobar\r\n"
+ "\r\n";
+
+ HTTPHdr hdr_1;
+ ts::PostScript hdr_1_defer([&]() -> void { hdr_1.destroy(); });
+ hdr_1.create(HTTP_TYPE_REQUEST);
+ http2_init_pseudo_headers(hdr_1);
+
+ // parse
+ const char *start = request;
+ const char *end = request + sizeof(request) - 1;
+ hdr_1.parse_req(&parser, &start, end, true);
+
+ // convert to HTTP/2
+ http2_convert_header_from_1_1_to_2(&hdr_1);
+
+ // check pseudo headers
+ // :method
+ {
+ MIMEField *f = hdr_1.field_find(HTTP2_VALUE_METHOD, HTTP2_LEN_METHOD);
+ REQUIRE(f != nullptr);
+ std::string_view v = f->value_get();
+ CHECK(v.compare("GET") == 0);
+ }
+
+ // :scheme
+ {
+ MIMEField *f = hdr_1.field_find(HTTP2_VALUE_SCHEME, HTTP2_LEN_SCHEME);
+ REQUIRE(f != nullptr);
+ std::string_view v = f->value_get();
+ CHECK(v.compare("https") == 0);
+ }
+
+ // :authority
+ {
+ MIMEField *f = hdr_1.field_find(HTTP2_VALUE_AUTHORITY, HTTP2_LEN_AUTHORITY);
+ REQUIRE(f != nullptr);
+ std::string_view v = f->value_get();
+ CHECK(v.compare("trafficserver.apache.org") == 0);
+ }
+
+ // :path
+ {
+ MIMEField *f = hdr_1.field_find(HTTP2_VALUE_PATH, HTTP2_LEN_PATH);
+ REQUIRE(f != nullptr);
+ std::string_view v = f->value_get();
+ CHECK(v.compare("/index.html") == 0);
+ }
+
+ // convert to HTTP/1.1
+ HTTPHdr hdr_2;
+ ts::PostScript hdr_2_defer([&]() -> void { hdr_2.destroy(); });
+ hdr_2.create(HTTP_TYPE_REQUEST);
+ hdr_2.copy(&hdr_1);
+
+ http2_convert_header_from_2_to_1_1(&hdr_2);
+
+ // dump
+ char buf[128] = {0};
+ int bufindex = 0;
+ int dumpoffset = 0;
+
+ hdr_2.print(buf, sizeof(buf), &bufindex, &dumpoffset);
+
+ // check
+ CHECK_THAT(buf, Catch::StartsWith("GET https://trafficserver.apache.org/index.html HTTP/1.1\r\n"
+ "Host: trafficserver.apache.org\r\n"
+ "User-Agent: foobar\r\n"
+ "\r\n"));
+ }
+
+ SECTION("response")
+ {
+ const char response[] = "HTTP/1.1 200 OK\r\n"
+ "Connection: close\r\n"
+ "\r\n";
+
+ HTTPHdr hdr_1;
+ ts::PostScript hdr_1_defer([&]() -> void { hdr_1.destroy(); });
+ hdr_1.create(HTTP_TYPE_RESPONSE);
+ http2_init_pseudo_headers(hdr_1);
+
+ // parse
+ const char *start = response;
+ const char *end = response + sizeof(response) - 1;
+ hdr_1.parse_resp(&parser, &start, end, true);
+
+ // convert to HTTP/2
+ http2_convert_header_from_1_1_to_2(&hdr_1);
+
+ // check pseudo headers
+ // :status
+ {
+ MIMEField *f = hdr_1.field_find(HTTP2_VALUE_STATUS, HTTP2_LEN_STATUS);
+ REQUIRE(f != nullptr);
+ std::string_view v = f->value_get();
+ CHECK(v.compare("200") == 0);
+ }
+
+ // no connection header
+ {
+ MIMEField *f = hdr_1.field_find(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION);
+ CHECK(f == nullptr);
+ }
+
+ // convert to HTTP/1.1
+ HTTPHdr hdr_2;
+ ts::PostScript hdr_2_defer([&]() -> void { hdr_2.destroy(); });
+ hdr_2.create(HTTP_TYPE_REQUEST);
+ hdr_2.copy(&hdr_1);
+
+ http2_convert_header_from_2_to_1_1(&hdr_2);
+
+ // dump
+ char buf[128] = {0};
+ int bufindex = 0;
+ int dumpoffset = 0;
+
+ hdr_2.print(buf, sizeof(buf), &bufindex, &dumpoffset);
+
+ // check
+ REQUIRE(bufindex > 0);
+ CHECK_THAT(buf, Catch::StartsWith("HTTP/1.1 200 OK\r\n\r\n"));
+ }
+}
diff --git a/proxy/http3/Http3HeaderFramer.cc b/proxy/http3/Http3HeaderFramer.cc
index b3ac791..b69f298 100644
--- a/proxy/http3/Http3HeaderFramer.cc
+++ b/proxy/http3/Http3HeaderFramer.cc
@@ -71,9 +71,9 @@ Http3HeaderFramer::is_done() const
}
void
-Http3HeaderFramer::_convert_header_from_1_1_to_3(HTTPHdr *h3_hdrs, HTTPHdr *h1_hdrs)
+Http3HeaderFramer::_convert_header_from_1_1_to_3(HTTPHdr *hdrs)
{
- http2_generate_h2_header_from_1_1(h1_hdrs, h3_hdrs);
+ http2_convert_header_from_1_1_to_2(hdrs);
}
void
@@ -85,22 +85,23 @@ Http3HeaderFramer::_generate_header_block()
if (this->_transaction->direction() == NET_VCONNECTION_OUT) {
this->_header.create(HTTP_TYPE_REQUEST);
+ http2_init_pseudo_headers(this->_header);
parse_result = this->_header.parse_req(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false);
} else {
this->_header.create(HTTP_TYPE_RESPONSE);
+ http2_init_pseudo_headers(this->_header);
parse_result = this->_header.parse_resp(&this->_http_parser, this->_source_vio->get_reader(), &bytes_used, false);
}
this->_source_vio->ndone += bytes_used;
switch (parse_result) {
case PARSE_RESULT_DONE: {
- HTTPHdr h3_hdr;
- this->_convert_header_from_1_1_to_3(&h3_hdr, &this->_header);
+ this->_convert_header_from_1_1_to_3(&this->_header);
this->_header_block = new_MIOBuffer(BUFFER_SIZE_INDEX_32K);
this->_header_block_reader = this->_header_block->alloc_reader();
- this->_qpack->encode(this->_stream_id, h3_hdr, this->_header_block, this->_header_block_len);
+ this->_qpack->encode(this->_stream_id, this->_header, this->_header_block, this->_header_block_len);
break;
}
case PARSE_RESULT_CONT:
diff --git a/proxy/http3/Http3HeaderFramer.h b/proxy/http3/Http3HeaderFramer.h
index 55ce586..2e70338 100644
--- a/proxy/http3/Http3HeaderFramer.h
+++ b/proxy/http3/Http3HeaderFramer.h
@@ -55,6 +55,6 @@ private:
HTTPParser _http_parser;
HTTPHdr _header;
- void _convert_header_from_1_1_to_3(HTTPHdr *h3_hdrs, HTTPHdr *h1_hdrs);
+ void _convert_header_from_1_1_to_3(HTTPHdr *hdrs);
void _generate_header_block();
};