Merge branch 'nghttpx-trailer'

This commit is contained in:
Tatsuhiro Tsujikawa 2015-03-08 19:32:49 +09:00
commit bff5a28d12
13 changed files with 465 additions and 88 deletions

View File

@ -211,6 +211,41 @@ func TestH1H1HTTP10NoHostRewrite(t *testing.T) {
}
}
// TestH1H1RequestTrailer tests request trailer part is forwarded to
// backend.
func TestH1H1RequestTrailer(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
buf := make([]byte, 4096)
for {
_, err := r.Body.Read(buf)
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("r.Body.Read() = %v", err)
}
}
if got, want := r.Trailer.Get("foo"), "bar"; got != want {
t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1RequestTrailer",
body: []byte("1"),
trailer: []hpack.HeaderField{
pair("foo", "bar"),
},
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("res.status: %v; want %v", got, want)
}
}
// TestH1H2ConnectFailure tests that server handles the situation that
// connection attempt to HTTP/2 backend failed.
func TestH1H2ConnectFailure(t *testing.T) {

View File

@ -520,6 +520,41 @@ func TestH2H1ServerPush(t *testing.T) {
}
}
// TestH2H1RequestTrailer tests request trailer part is forwarded to
// backend.
func TestH2H1RequestTrailer(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
buf := make([]byte, 4096)
for {
_, err := r.Body.Read(buf)
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("r.Body.Read() = %v", err)
}
}
if got, want := r.Trailer.Get("foo"), "bar"; got != want {
t.Errorf("r.Trailer.Get(foo): %v; want %v", got, want)
}
})
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1RequestTrailer",
body: []byte("1"),
trailer: []hpack.HeaderField{
pair("foo", "bar"),
},
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("res.status: %v; want %v", got, want)
}
}
// TestH2H1GracefulShutdown tests graceful shutdown.
func TestH2H1GracefulShutdown(t *testing.T) {
st := newServerTester(nil, t, noopHandler)

View File

@ -255,6 +255,27 @@ type requestParam struct {
path string // path, defaults to /
header []hpack.HeaderField // additional request header fields
body []byte // request body
trailer []hpack.HeaderField // trailer part
}
// wrapper for request body to set trailer part
type chunkedBodyReader struct {
trailer []hpack.HeaderField
trailerWritten bool
body io.Reader
req *http.Request
}
func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) {
// document says that we have to set http.Request.Trailer
// after request was sent and before body returns EOF.
if !cbr.trailerWritten {
cbr.trailerWritten = true
for _, h := range cbr.trailer {
cbr.req.Trailer.Set(h.Name, h.Value)
}
}
return cbr.body.Read(p)
}
func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
@ -264,8 +285,16 @@ func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
}
var body io.Reader
var cbr *chunkedBodyReader
if rp.body != nil {
body = bytes.NewBuffer(rp.body)
if len(rp.trailer) != 0 {
cbr = &chunkedBodyReader{
trailer: rp.trailer,
body: body,
}
body = cbr
}
}
req, err := http.NewRequest(method, st.url, body)
if err != nil {
@ -275,7 +304,15 @@ func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
req.Header.Add(h.Name, h.Value)
}
req.Header.Add("Test-Case", rp.name)
if cbr != nil {
cbr.req = req
// this makes request use chunked encoding
req.ContentLength = -1
req.Trailer = make(http.Header)
for _, h := range cbr.trailer {
req.Trailer.Set(h.Name, "")
}
}
if err := req.Write(st.conn); err != nil {
return nil, err
}
@ -473,7 +510,7 @@ func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
err := st.fr.WriteHeaders(http2.HeadersFrameParam{
StreamID: id,
EndStream: len(rp.body) == 0,
EndStream: len(rp.body) == 0 && len(rp.trailer) == 0,
EndHeaders: true,
BlockFragment: st.headerBlkBuf.Bytes(),
})
@ -483,7 +520,23 @@ func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
if len(rp.body) != 0 {
// TODO we assume rp.body fits in 1 frame
if err := st.fr.WriteData(id, true, rp.body); err != nil {
if err := st.fr.WriteData(id, len(rp.trailer) == 0, rp.body); err != nil {
return nil, err
}
}
if len(rp.trailer) != 0 {
st.headerBlkBuf.Reset()
for _, h := range rp.trailer {
_ = st.enc.WriteField(h)
}
err := st.fr.WriteHeaders(http2.HeadersFrameParam{
StreamID: id,
EndStream: true,
EndHeaders: true,
BlockFragment: st.headerBlkBuf.Bytes(),
})
if err != nil {
return nil, err
}
}

View File

@ -690,12 +690,22 @@ int Http2Handler::submit_file_response(const std::string &status,
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ll("cache-control", "max-age=3600"),
http2::make_nv_ls("date", sessions_->get_cached_date()),
http2::make_nv_ll("", ""));
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
size_t nvlen = 5;
if (last_modified != 0) {
last_modified_str = util::http_date(last_modified);
nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
}
auto &trailer = get_config()->trailer;
std::string trailer_names;
if (!trailer.empty()) {
trailer_names = trailer[0].name;
for (size_t i = 1; i < trailer.size(); ++i) {
trailer_names += ", ";
trailer_names += trailer[i].name;
}
nva[nvlen++] = http2::make_nv_ls("trailer", trailer_names);
}
return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
data_prd);
}
@ -832,7 +842,6 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
auto config = hd->get_config();
if (!config->trailer.empty()) {
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
std::vector<nghttp2_nv> nva;
nva.reserve(config->trailer.size());
for (auto &kv : config->trailer) {
@ -840,7 +849,11 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
}
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
if (rv != 0) {
*data_flags &= ~NGHTTP2_DATA_FLAG_NO_END_STREAM;
if (nghttp2_is_fatal(rv)) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else {
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
}
}

View File

@ -230,7 +230,6 @@ void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_SERVER:
case HD_TRAILER:
case HD_TRANSFER_ENCODING:
case HD_UPGRADE:
case HD_VIA:
@ -256,7 +255,6 @@ void build_http1_headers_from_headers(std::string &hdrs,
case HD_KEEP_ALIVE:
case HD_PROXY_CONNECTION:
case HD_SERVER:
case HD_TRAILER:
case HD_UPGRADE:
case HD_VIA:
case HD_X_FORWARDED_FOR:

View File

@ -376,6 +376,16 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
}
std::string trailer_names;
if (!config.trailer.empty()) {
trailer_names = config.trailer[0].name;
for (size_t i = 1; i < config.trailer.size(); ++i) {
trailer_names += ", ";
trailer_names += config.trailer[i].name;
}
nva.push_back(http2::make_nv_ls("trailer", trailer_names));
}
auto stream_id =
nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
nva.size(), req->data_prd, req);
@ -2128,7 +2138,6 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
if (nread == 0) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
if (!config.trailer.empty()) {
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
std::vector<nghttp2_nv> nva;
nva.reserve(config.trailer.size());
for (auto &kv : config.trailer) {
@ -2136,7 +2145,11 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
}
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
if (rv != 0) {
*data_flags &= ~NGHTTP2_DATA_FLAG_NO_END_STREAM;
if (nghttp2_is_fatal(rv)) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else {
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
}
}
} else {

View File

@ -120,8 +120,9 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
response_minor_(1), upgrade_request_(false), upgraded_(false),
http2_upgrade_seen_(false), chunked_request_(false),
request_connection_close_(false), request_header_key_prev_(false),
request_http2_expect_body_(false), chunked_response_(false),
response_connection_close_(false), response_header_key_prev_(false),
request_trailer_key_prev_(false), request_http2_expect_body_(false),
chunked_response_(false), response_connection_close_(false),
response_header_key_prev_(false), response_trailer_key_prev_(false),
expect_final_response_(false), request_pending_(false) {
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
@ -287,6 +288,45 @@ const std::string &Downstream::get_assembled_request_cookie() const {
return assembled_request_cookie_;
}
namespace {
void add_header(bool &key_prev, size_t &sum, Headers &headers, std::string name,
std::string value) {
key_prev = true;
sum += name.size() + value.size();
headers.emplace_back(std::move(name), std::move(value));
}
} // namespace
namespace {
void append_last_header_key(bool key_prev, size_t &sum, Headers &headers,
const char *data, size_t len) {
assert(key_prev);
sum += len;
auto &item = headers.back();
item.name.append(data, len);
}
} // namespace
namespace {
void append_last_header_value(bool key_prev, size_t &sum, Headers &headers,
const char *data, size_t len) {
assert(!key_prev);
sum += len;
auto &item = headers.back();
item.value.append(data, len);
}
} // namespace
namespace {
void set_last_header_value(bool &key_prev, size_t &sum, Headers &headers,
const char *data, size_t len) {
key_prev = false;
sum += len;
auto &item = headers.back();
item.value.assign(data, len);
}
} // namespace
namespace {
int index_headers(http2::HeaderIndex &hdidx, Headers &headers,
int64_t &content_length) {
@ -333,16 +373,13 @@ Downstream::get_request_header(const std::string &name) const {
}
void Downstream::add_request_header(std::string name, std::string value) {
request_header_key_prev_ = true;
request_headers_sum_ += name.size() + value.size();
request_headers_.emplace_back(std::move(name), std::move(value));
add_header(request_header_key_prev_, request_headers_sum_, request_headers_,
std::move(name), std::move(value));
}
void Downstream::set_last_request_header_value(std::string value) {
request_header_key_prev_ = false;
request_headers_sum_ += value.size();
Headers::value_type &item = request_headers_.back();
item.value = std::move(value);
void Downstream::set_last_request_header_value(const char *data, size_t len) {
set_last_header_value(request_header_key_prev_, request_headers_sum_,
request_headers_, data, len);
}
void Downstream::add_request_header(const uint8_t *name, size_t namelen,
@ -359,18 +396,14 @@ bool Downstream::get_request_header_key_prev() const {
}
void Downstream::append_last_request_header_key(const char *data, size_t len) {
assert(request_header_key_prev_);
request_headers_sum_ += len;
auto &item = request_headers_.back();
item.name.append(data, len);
append_last_header_key(request_header_key_prev_, request_headers_sum_,
request_headers_, data, len);
}
void Downstream::append_last_request_header_value(const char *data,
size_t len) {
assert(!request_header_key_prev_);
request_headers_sum_ += len;
auto &item = request_headers_.back();
item.value.append(data, len);
append_last_header_value(request_header_key_prev_, request_headers_sum_,
request_headers_, data, len);
}
void Downstream::clear_request_headers() {
@ -382,6 +415,45 @@ 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::add_request_trailer(std::string name, std::string value) {
add_header(request_trailer_key_prev_, request_headers_sum_, request_trailers_,
std::move(name), std::move(value));
}
void Downstream::set_last_request_trailer_value(const char *data, size_t len) {
set_last_header_value(request_trailer_key_prev_, request_headers_sum_,
request_trailers_, data, len);
}
bool Downstream::get_request_trailer_key_prev() const {
return request_trailer_key_prev_;
}
void Downstream::append_last_request_trailer_key(const char *data, size_t len) {
append_last_header_key(request_trailer_key_prev_, request_headers_sum_,
request_trailers_, data, len);
}
void Downstream::append_last_request_trailer_value(const char *data,
size_t len) {
append_last_header_value(request_trailer_key_prev_, request_headers_sum_,
request_trailers_, data, len);
}
void Downstream::set_request_method(std::string method) {
request_method_ = std::move(method);
}
@ -592,16 +664,13 @@ void Downstream::rewrite_location_response_header(
}
void Downstream::add_response_header(std::string name, std::string value) {
response_header_key_prev_ = true;
response_headers_sum_ += name.size() + value.size();
response_headers_.emplace_back(std::move(name), std::move(value));
add_header(response_header_key_prev_, response_headers_sum_,
response_headers_, std::move(name), std::move(value));
}
void Downstream::set_last_response_header_value(std::string value) {
response_header_key_prev_ = false;
response_headers_sum_ += value.size();
auto &item = response_headers_.back();
item.value = std::move(value);
void Downstream::set_last_response_header_value(const char *data, size_t len) {
set_last_header_value(response_header_key_prev_, response_headers_sum_,
response_headers_, data, len);
}
void Downstream::add_response_header(std::string name, std::string value,
@ -626,18 +695,14 @@ bool Downstream::get_response_header_key_prev() const {
}
void Downstream::append_last_response_header_key(const char *data, size_t len) {
assert(response_header_key_prev_);
response_headers_sum_ += len;
auto &item = response_headers_.back();
item.name.append(data, len);
append_last_header_key(response_header_key_prev_, response_headers_sum_,
response_headers_, data, len);
}
void Downstream::append_last_response_header_value(const char *data,
size_t len) {
assert(!response_header_key_prev_);
response_headers_sum_ += len;
auto &item = response_headers_.back();
item.value.append(data, len);
append_last_header_value(response_header_key_prev_, response_headers_sum_,
response_headers_, data, len);
}
void Downstream::clear_response_headers() {
@ -649,10 +714,48 @@ size_t Downstream::get_response_headers_sum() const {
return response_headers_sum_;
}
const Headers &Downstream::get_response_trailers() const {
return response_trailers_;
}
void Downstream::add_response_trailer(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token) {
response_headers_sum_ += namelen + valuelen;
http2::add_header(response_trailers_, name, namelen, value, valuelen,
no_index, -1);
}
unsigned int Downstream::get_response_http_status() const {
return response_http_status_;
}
void Downstream::add_response_trailer(std::string name, std::string value) {
add_header(response_trailer_key_prev_, response_headers_sum_,
response_trailers_, std::move(name), std::move(value));
}
void Downstream::set_last_response_trailer_value(const char *data, size_t len) {
set_last_header_value(response_trailer_key_prev_, response_headers_sum_,
response_trailers_, data, len);
}
bool Downstream::get_response_trailer_key_prev() const {
return response_trailer_key_prev_;
}
void Downstream::append_last_response_trailer_key(const char *data,
size_t len) {
append_last_header_key(response_trailer_key_prev_, response_headers_sum_,
response_trailers_, data, len);
}
void Downstream::append_last_response_trailer_value(const char *data,
size_t len) {
append_last_header_value(response_trailer_key_prev_, response_headers_sum_,
response_trailers_, data, len);
}
void Downstream::set_response_http_status(unsigned int status) {
response_http_status_ = status;
}

View File

@ -113,7 +113,7 @@ public:
// no such header is found, returns nullptr.
const Headers::value_type *get_request_header(const std::string &name) const;
void add_request_header(std::string name, std::string value);
void set_last_request_header_value(std::string value);
void set_last_request_header_value(const char *data, size_t len);
void add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
@ -127,6 +127,16 @@ 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 add_request_trailer(std::string name, std::string value);
void set_last_request_trailer_value(const char *data, size_t len);
bool get_request_trailer_key_prev() const;
void append_last_request_trailer_key(const char *data, size_t len);
void append_last_request_trailer_value(const char *data, size_t len);
void set_request_method(std::string method);
const std::string &get_request_method() const;
void set_request_path(std::string path);
@ -201,7 +211,7 @@ public:
// Rewrites the location response header field.
void rewrite_location_response_header(const std::string &upstream_scheme);
void add_response_header(std::string name, std::string value);
void set_last_response_header_value(std::string value);
void set_last_response_header_value(const char *data, size_t len);
void add_response_header(std::string name, std::string value, int16_t token);
void add_response_header(const uint8_t *name, size_t namelen,
@ -216,6 +226,16 @@ public:
size_t get_response_headers_sum() const;
const Headers &get_response_trailers() const;
void add_response_trailer(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token);
void add_response_trailer(std::string name, std::string value);
void set_last_response_trailer_value(const char *data, size_t len);
bool get_response_trailer_key_prev() const;
void append_last_response_trailer_key(const char *data, size_t len);
void append_last_response_trailer_value(const char *data, size_t len);
unsigned int get_response_http_status() const;
void set_response_http_status(unsigned int status);
void set_response_major(int major);
@ -308,6 +328,11 @@ 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_;
Headers response_trailers_;
std::chrono::high_resolution_clock::time_point request_start_time_;
std::string request_method_;
@ -385,11 +410,13 @@ private:
bool chunked_request_;
bool request_connection_close_;
bool request_header_key_prev_;
bool request_trailer_key_prev_;
bool request_http2_expect_body_;
bool chunked_response_;
bool response_connection_close_;
bool response_header_key_prev_;
bool response_trailer_key_prev_;
bool expect_final_response_;
// true if downstream request is pending because backend connection
// has not been established or should be checked before use;

View File

@ -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<StreamData *>(
nghttp2_session_get_stream_user_data(session, stream_id));
if (!sd || !sd->dconn) {
@ -201,6 +202,23 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
!downstream->get_upgraded()))) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
auto &trailers = downstream->get_request_trailers();
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
http2::copy_headers_to_nva(nva, trailers);
if (!nva.empty()) {
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 +293,7 @@ int Http2DownstreamConnection::push_request_headers() {
cookies = downstream_->crumble_request_cookie();
}
// 8 means:
// 9 means:
// 1. :method
// 2. :scheme
// 3. :path
@ -284,8 +302,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<nghttp2_nv>();
nva.reserve(nheader + 8 + cookies.size());
nva.reserve(nheader + 9 + cookies.size());
std::string via_value;
std::string xff_value;
@ -432,6 +451,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) {

View File

@ -668,20 +668,35 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (frame->hd.type != NGHTTP2_HEADERS ||
(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
!downstream->get_expect_final_response())) {
if (frame->hd.type != NGHTTP2_HEADERS) {
return 0;
}
auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
!downstream->get_expect_final_response();
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum();
}
if (trailer) {
// we don't care trailer part exceeds header size limit; just
// discard it.
return 0;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (trailer) {
// just store header fields for trailer part
downstream->add_response_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) {
@ -891,10 +906,6 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
if (rv != 0) {
return 0;
}
} else if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return 0;
}
}

View File

@ -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<Http2Upstream *>(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) {
@ -1056,6 +1068,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
size_t length, uint32_t *data_flags,
nghttp2_data_source *source,
void *user_data) {
int rv;
auto downstream = static_cast<Downstream *>(source->ptr);
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
auto body = downstream->get_response_buf();
@ -1081,7 +1094,23 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
if (!downstream->get_upgraded()) {
auto &trailers = downstream->get_response_trailers();
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
http2::copy_headers_to_nva(nva, trailers);
if (!nva.empty()) {
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 (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
}

View File

@ -403,7 +403,16 @@ int HttpDownstreamConnection::end_upload_data() {
}
auto output = downstream_->get_request_buf();
output->append("0\r\n\r\n");
auto &trailers = downstream_->get_request_trailers();
if (trailers.empty()) {
output->append("0\r\n\r\n");
} else {
output->append("0\r\n");
std::string trailer_part;
http2::build_http1_headers_from_headers(trailer_part, trailers);
output->append(trailer_part.c_str(), trailer_part.size());
output->append("\r\n");
}
signal_write();
@ -555,14 +564,19 @@ int htp_hdrs_completecb(http_parser *htp) {
namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_state() != Downstream::INITIAL) {
// ignore trailers
return 0;
}
if (downstream->get_response_header_key_prev()) {
downstream->append_last_response_header_key(data, len);
if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) {
downstream->append_last_response_header_key(data, len);
} else {
downstream->add_response_header(std::string(data, len), "");
}
} else {
downstream->add_response_header(std::string(data, len), "");
// trailer part
if (downstream->get_response_trailer_key_prev()) {
downstream->append_last_response_trailer_key(data, len);
} else {
downstream->add_response_trailer(std::string(data, len), "");
}
}
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
@ -578,14 +592,18 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_state() != Downstream::INITIAL) {
// ignore trailers
return 0;
}
if (downstream->get_response_header_key_prev()) {
downstream->set_last_response_header_value(std::string(data, len));
if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) {
downstream->set_last_response_header_value(data, len);
} else {
downstream->append_last_response_header_value(data, len);
}
} else {
downstream->append_last_response_header_value(data, len);
if (downstream->get_response_trailer_key_prev()) {
downstream->set_last_response_trailer_value(data, len);
} else {
downstream->append_last_response_trailer_value(data, len);
}
}
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {

View File

@ -83,14 +83,19 @@ namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
if (downstream->get_request_state() != Downstream::INITIAL) {
// ignore trailers
return 0;
}
if (downstream->get_request_header_key_prev()) {
downstream->append_last_request_header_key(data, len);
if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) {
downstream->append_last_request_header_key(data, len);
} else {
downstream->add_request_header(std::string(data, len), "");
}
} else {
downstream->add_request_header(std::string(data, len), "");
// trailer part
if (downstream->get_request_trailer_key_prev()) {
downstream->append_last_request_trailer_key(data, len);
} else {
downstream->add_request_trailer(std::string(data, len), "");
}
}
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
@ -107,14 +112,18 @@ namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto upstream = static_cast<HttpsUpstream *>(htp->data);
auto downstream = upstream->get_downstream();
if (downstream->get_request_state() != Downstream::INITIAL) {
// ignore trailers
return 0;
}
if (downstream->get_request_header_key_prev()) {
downstream->set_last_request_header_value(std::string(data, len));
if (downstream->get_request_state() == Downstream::INITIAL) {
if (downstream->get_request_header_key_prev()) {
downstream->set_last_request_header_value(data, len);
} else {
downstream->append_last_request_header_value(data, len);
}
} else {
downstream->append_last_request_header_value(data, len);
if (downstream->get_request_trailer_key_prev()) {
downstream->set_last_request_trailer_value(data, len);
} else {
downstream->append_last_request_trailer_value(data, len);
}
}
if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
if (LOG_ENABLED(INFO)) {
@ -780,7 +789,16 @@ int HttpsUpstream::on_downstream_body(Downstream *downstream,
int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
if (downstream->get_chunked_response()) {
auto output = downstream->get_response_buf();
output->append("0\r\n\r\n");
auto &trailers = downstream->get_response_trailers();
if (trailers.empty()) {
output->append("0\r\n\r\n");
} else {
output->append("0\r\n");
std::string trailer_part;
http2::build_http1_headers_from_headers(trailer_part, trailers);
output->append(trailer_part.c_str(), trailer_part.size());
output->append("\r\n");
}
}
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response completed";