nghttpd: Add --echo-upload option to send back request body

This commit is contained in:
Tatsuhiro Tsujikawa 2015-05-13 00:38:28 +09:00
parent 260131966d
commit 5da49989f8
3 changed files with 95 additions and 7 deletions

View File

@ -91,7 +91,8 @@ Config::Config()
session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1), session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1),
max_concurrent_streams(100), header_table_size(-1), port(0), max_concurrent_streams(100), header_table_size(-1), port(0),
verbose(false), daemon(false), verify_client(false), no_tls(false), verbose(false), daemon(false), verify_client(false), no_tls(false),
error_gzip(false), early_response(false), hexdump(false) { error_gzip(false), early_response(false), hexdump(false),
echo_upload(false) {
nghttp2_option_new(&session_option); nghttp2_option_new(&session_option);
nghttp2_option_set_recv_client_preface(session_option, 1); nghttp2_option_set_recv_client_preface(session_option, 1);
} }
@ -282,7 +283,7 @@ private:
Stream::Stream(Http2Handler *handler, int32_t stream_id) Stream::Stream(Http2Handler *handler, int32_t stream_id)
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0), : handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
stream_id(stream_id) { stream_id(stream_id), echo_upload(false) {
auto config = handler->get_config(); auto config = handler->get_config();
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout); ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout); ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
@ -918,6 +919,49 @@ void prepare_status_response(Stream *stream, Http2Handler *hd, int status) {
} }
} // namespace } // namespace
namespace {
void prepare_echo_response(Stream *stream, Http2Handler *hd) {
auto length = lseek(stream->file_ent->fd, 0, SEEK_END);
if (length == -1) {
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
return;
}
stream->body_length = length;
if (lseek(stream->file_ent->fd, 0, SEEK_SET) == -1) {
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
return;
}
nghttp2_data_provider data_prd;
data_prd.source.fd = stream->file_ent->fd;
data_prd.read_callback = file_read_callback;
Headers headers;
headers.emplace_back("nghttpd-response", "echo");
headers.emplace_back("content-length", util::utos(length));
hd->submit_response("200", stream->stream_id, headers, &data_prd);
}
} // namespace
namespace {
bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) {
auto sessions = hd->get_sessions();
char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
auto fd = mkstemp(tempfn);
if (fd == -1) {
return false;
}
unlink(tempfn);
// Ordinary request never start with "echo:". The length is 0 for
// now. We will update it when we get whole request body.
stream->file_ent = sessions->cache_fd(std::string("echo:") + tempfn,
FileEntry(tempfn, 0, 0, fd));
stream->echo_upload = true;
return true;
}
} // namespace
namespace { namespace {
void prepare_redirect_response(Stream *stream, Http2Handler *hd, void prepare_redirect_response(Stream *stream, Http2Handler *hd,
const std::string &path, int status) { const std::string &path, int status) {
@ -972,8 +1016,14 @@ void prepare_response(Stream *stream, Http2Handler *hd,
url = reqpath; url = reqpath;
} }
url = util::percentDecode(url.begin(), url.end()); auto sessions = hd->get_sessions();
url = util::percentDecode(std::begin(url), std::end(url));
if (!util::check_path(url)) { if (!util::check_path(url)) {
if (stream->file_ent) {
sessions->release_fd(stream->file_ent->path);
stream->file_ent = nullptr;
}
prepare_status_response(stream, hd, 404); prepare_status_response(stream, hd, 404);
return; return;
} }
@ -987,12 +1037,18 @@ void prepare_response(Stream *stream, Http2Handler *hd,
} }
} }
} }
std::string path = hd->get_config()->htdocs + url; std::string path = hd->get_config()->htdocs + url;
if (path[path.size() - 1] == '/') { if (path[path.size() - 1] == '/') {
path += DEFAULT_HTML; path += DEFAULT_HTML;
} }
auto sessions = hd->get_sessions(); if (stream->echo_upload) {
assert(stream->file_ent);
prepare_echo_response(stream, hd);
return;
}
auto file_ent = sessions->get_cached_fd(path); auto file_ent = sessions->get_cached_fd(path);
if (file_ent == nullptr) { if (file_ent == nullptr) {
@ -1122,7 +1178,7 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
remove_stream_read_timeout(stream); remove_stream_read_timeout(stream);
if (!hd->get_config()->early_response) { if (stream->echo_upload || !hd->get_config()->early_response) {
prepare_response(stream, hd); prepare_response(stream, hd);
} }
} else { } else {
@ -1146,14 +1202,22 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
hd->submit_non_final_response("100", frame->hd.stream_id); hd->submit_non_final_response("100", frame->hd.stream_id);
} }
if (hd->get_config()->early_response) { auto &method = http2::get_header(stream->hdidx, http2::HD__METHOD,
stream->headers)->value;
if (hd->get_config()->echo_upload &&
(method == "POST" || method == "PUT")) {
if (!prepare_upload_temp_store(stream, hd)) {
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
return 0;
}
} else if (hd->get_config()->early_response) {
prepare_response(stream, hd); prepare_response(stream, hd);
} }
} }
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
remove_stream_read_timeout(stream); remove_stream_read_timeout(stream);
if (!hd->get_config()->early_response) { if (stream->echo_upload || !hd->get_config()->early_response) {
prepare_response(stream, hd); prepare_response(stream, hd);
} }
} else { } else {
@ -1299,6 +1363,21 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
return 0; return 0;
} }
if (stream->echo_upload) {
assert(stream->file_ent);
while (len) {
ssize_t n;
while ((n = write(stream->file_ent->fd, data, len)) == -1 &&
errno == EINTR)
;
if (n == -1) {
hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
return 0;
}
len -= n;
data += n;
}
}
// TODO Handle POST // TODO Handle POST
add_stream_read_timeout(stream); add_stream_read_timeout(stream);

View File

@ -73,6 +73,7 @@ struct Config {
bool error_gzip; bool error_gzip;
bool early_response; bool early_response;
bool hexdump; bool hexdump;
bool echo_upload;
Config(); Config();
~Config(); ~Config();
}; };
@ -101,6 +102,7 @@ struct Stream {
int64_t body_offset; int64_t body_offset;
int32_t stream_id; int32_t stream_id;
http2::HeaderIndex hdidx; http2::HeaderIndex hdidx;
bool echo_upload;
Stream(Http2Handler *handler, int32_t stream_id); Stream(Http2Handler *handler, int32_t stream_id);
~Stream(); ~Stream();
}; };

View File

@ -153,6 +153,8 @@ Options:
--hexdump Display the incoming traffic in hexadecimal (Canonical --hexdump Display the incoming traffic in hexadecimal (Canonical
hex+ASCII display). If SSL/TLS is used, decrypted data hex+ASCII display). If SSL/TLS is used, decrypted data
are used. are used.
--echo-upload
Send back uploaded content if method is POST or PUT.
--version Display version information and exit. --version Display version information and exit.
-h, --help Display this help and exit. -h, --help Display this help and exit.
@ -188,6 +190,7 @@ int main(int argc, char **argv) {
{"early-response", no_argument, &flag, 5}, {"early-response", no_argument, &flag, 5},
{"trailer", required_argument, &flag, 6}, {"trailer", required_argument, &flag, 6},
{"hexdump", no_argument, &flag, 7}, {"hexdump", no_argument, &flag, 7},
{"echo-upload", no_argument, &flag, 8},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options, int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
@ -310,6 +313,10 @@ int main(int argc, char **argv) {
// hexdump option // hexdump option
config.hexdump = true; config.hexdump = true;
break; break;
case 8:
// echo-upload option
config.echo_upload = true;
break;
} }
break; break;
default: default: