diff --git a/src/HttpServer.cc b/src/HttpServer.cc index ff13b6c4..3044567f 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -809,6 +809,7 @@ void Http2Handler::terminate_session(uint32_t error_code) { ssize_t file_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 hd = static_cast(user_data); auto stream = hd->get_stream(stream_id); @@ -829,6 +830,20 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id, if (nread == 0 || stream->body_left <= 0) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; + auto config = hd->get_config(); + if (!config->trailer.empty()) { + *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM; + std::vector nva; + nva.reserve(config->trailer.size()); + for (auto &kv : config->trailer) { + 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) { + *data_flags &= ~NGHTTP2_DATA_FLAG_NO_END_STREAM; + } + } + if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) { remove_stream_read_timeout(stream); remove_stream_write_timeout(stream); diff --git a/src/HttpServer.h b/src/HttpServer.h index 3723afd1..5b8de7ef 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -50,6 +50,7 @@ namespace nghttp2 { struct Config { std::map> push; + Headers trailer; std::string htdocs; std::string host; std::string private_key_file; diff --git a/src/nghttpd.cc b/src/nghttpd.cc index fc1f70a9..275a4827 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -140,6 +140,11 @@ Options: --early-response Start sending response when request HEADERS is received, rather than complete request is received. + --trailer=
+ Add a trailer header to a response.
must not + include pseudo header field (header field name starting + with ':'). The trailer is sent only if a response has + body part. Example: --trailer 'foo: bar'. --version Display version information and exit. -h, --help Display this help and exit. @@ -170,6 +175,7 @@ int main(int argc, char **argv) { {"version", no_argument, &flag, 3}, {"dh-param-file", required_argument, &flag, 4}, {"early-response", no_argument, &flag, 5}, + {"trailer", required_argument, &flag, 6}, {nullptr, 0, nullptr, 0}}; int option_index = 0; int c = getopt_long(argc, argv, "DVb:c:d:ehn:p:va:", long_options, @@ -254,6 +260,30 @@ int main(int argc, char **argv) { // early-response config.early_response = true; break; + case 6: { + // trailer option + auto header = optarg; + auto value = strchr(optarg, ':'); + if (!value) { + std::cerr << "--trailer: invalid header: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + *value = 0; + value++; + while (isspace(*value)) { + value++; + } + if (*value == 0) { + // This could also be a valid case for suppressing a header + // similar to curl + std::cerr << "--trailer: invalid header - value missing: " << optarg + << std::endl; + exit(EXIT_FAILURE); + } + config.trailer.emplace_back(header, value, false); + util::inp_strlower(config.trailer.back().name); + break; + } } break; default: