diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index ec5a5fb2330..a6250963535 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"; @@ -495,87 +505,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; + } - 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); + // :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(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; + } - } else if (http_hdr_type_get(headers->m_http) == HTTP_TYPE_REQUEST) { - MIMEField *field; - const char *value; - int value_len; + // :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(ats_malloc(value_len + 1)); + path[0] = '/'; + memcpy(path + 1, value, 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(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(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 @@ -589,6 +658,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) { @@ -820,6 +890,27 @@ Http2::init() static_cast(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(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 7bcc75f1ddc..966164508a5 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 f8f57bffb8f..ab1b5c6a255 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 #include @@ -1589,25 +1592,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(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; } @@ -1616,8 +1616,8 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) if (header_blocks_size <= static_cast(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; @@ -1635,7 +1635,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; } @@ -1660,14 +1659,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; @@ -1680,37 +1677,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(ats_malloc(buf_len)); + buf_len = hdr.length_get() * 2; // Make it double just in case + buf = static_cast(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; } @@ -1752,7 +1747,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; } @@ -1771,11 +1765,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 7863cb2a8a7..a5a49b6b3ad 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -54,9 +54,12 @@ Http2Stream::init(Http2StreamId sid, ssize_t initial_rwnd) this->_client_rwnd = initial_rwnd; 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); } @@ -595,6 +598,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 0952eef8852..9fbc6b47b9a 100644 --- a/proxy/http2/Makefile.am +++ b/proxy/http2/Makefile.am @@ -82,6 +82,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 00000000000..e67f50ee47e --- /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 0931acc36eb..405ba41fac2 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 += this->_header.length_get(); 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(); 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 55ce58627a7..2e70338586e 100644 --- a/proxy/http3/Http3HeaderFramer.h +++ b/proxy/http3/Http3HeaderFramer.h @@ -55,6 +55,6 @@ class Http3HeaderFramer : public Http3FrameGenerator 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(); };