diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 359787f9..7d4e69ee 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -382,6 +382,20 @@ size_t Downstream::get_request_headers_sum() const { return request_headers_sum_; } +void Downstream::add_request_trailer(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int16_t token) { + // we never index trailer part. Header size limit should be applied + // to all request header fields combined. + request_headers_sum_ += namelen + valuelen; + http2::add_header(request_trailers_, name, namelen, value, valuelen, no_index, + -1); +} + +const Headers &Downstream::get_request_trailers() const { + return request_trailers_; +} + void Downstream::set_request_method(std::string method) { request_method_ = std::move(method); } diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 9a696380..2343dc44 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -127,6 +127,11 @@ public: size_t get_request_headers_sum() const; + const Headers &get_request_trailers() const; + void add_request_trailer(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int16_t token); + void set_request_method(std::string method); const std::string &get_request_method() const; void set_request_path(std::string path); @@ -308,6 +313,10 @@ private: Headers request_headers_; Headers response_headers_; + // trailer part. For HTTP/1.1, trailer part is only included with + // chunked encoding. For HTTP/2, there is no such limit. + Headers request_trailers_; + std::chrono::high_resolution_clock::time_point request_start_time_; std::string request_method_; diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index a3aae8c7..3536a509 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -159,6 +159,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data) { + int rv; auto sd = static_cast( nghttp2_session_get_stream_user_data(session, stream_id)); if (!sd || !sd->dconn) { @@ -201,6 +202,22 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, !downstream->get_upgraded()))) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + if (!downstream->get_request_trailers().empty()) { + std::vector nva; + nva.reserve(downstream->get_request_trailers().size()); + for (auto &kv : downstream->get_request_trailers()) { + nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index)); + } + rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size()); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } else { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } } if (!input_empty) { @@ -275,7 +292,7 @@ int Http2DownstreamConnection::push_request_headers() { cookies = downstream_->crumble_request_cookie(); } - // 8 means: + // 9 means: // 1. :method // 2. :scheme // 3. :path @@ -284,8 +301,9 @@ int Http2DownstreamConnection::push_request_headers() { // 6. via (optional) // 7. x-forwarded-for (optional) // 8. x-forwarded-proto (optional) + // 9. te (optional) auto nva = std::vector(); - nva.reserve(nheader + 8 + cookies.size()); + nva.reserve(nheader + 9 + cookies.size()); std::string via_value; std::string xff_value; @@ -432,6 +450,11 @@ int Http2DownstreamConnection::push_request_headers() { nva.push_back(http2::make_nv_ls("via", via_value)); } + auto te = downstream_->get_request_header(http2::HD_TE); + if (te) { + nva.push_back(http2::make_nv_ls("te", te->value)); + } + if (LOG_ENABLED(INFO)) { std::stringstream ss; for (auto &nv : nva) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index f81593cc..bcaa8236 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -187,8 +187,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, verbose_on_header_callback(session, frame, name, namelen, value, valuelen, flags, user_data); } - if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + if (frame->hd.type != NGHTTP2_HEADERS) { return 0; } auto upstream = static_cast(user_data); @@ -207,12 +206,25 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, << downstream->get_request_headers_sum(); } + // just ignore header fields if this is trailer part. + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + return 0; + } + if (upstream->error_reply(downstream, 431) != 0) { return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } return 0; } + + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + // just store header fields for trailer part + downstream->add_request_trailer(name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, -1); + return 0; + } + auto token = http2::lookup_token(name, namelen); if (token == http2::HD_CONTENT_LENGTH) {