From 8e3406ad2080ba1cedcf0bf4f2b7defcdf28efe2 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 3 Jan 2015 00:12:26 +0900 Subject: [PATCH] nghttpd: Use faster request header handling --- genheaderfunc.py | 88 +++++++++++++++++++++ src/HttpServer.cc | 65 +++++++-------- src/HttpServer.h | 1 + src/http2.cc | 198 ++++++++++++++++++++++++++++++++++++++++++++++ src/http2.h | 39 +++++++++ 5 files changed, 353 insertions(+), 38 deletions(-) create mode 100755 genheaderfunc.py diff --git a/genheaderfunc.py b/genheaderfunc.py new file mode 100755 index 00000000..f029ed17 --- /dev/null +++ b/genheaderfunc.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +HEADERS = [ + ':authority', + ':method', + ':path', + ':scheme', + # disallowed h1 headers + 'connection', + 'expect', + 'host', + 'if-modified-since', + 'keep-alive', + 'proxy-connection', + 'te', + 'transfer-encoding', + 'upgrade' +] + +def to_enum_hd(k): + res = 'HD_' + for c in k.upper(): + if c == ':': + continue + if c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + ent[c].append(k) + + return res + +def gen_enum(): + print '''\ +enum {''' + for k in sorted(HEADERS): + print '''\ + {},'''.format(to_enum_hd(k)) + print '''\ + HD_MAXIDX, +};''' + +def gen_index_header(): + print '''\ +void index_header(int *hdidx, const uint8_t *s, size_t len, size_t idx) { + switch (len) {''' + b = build_header(HEADERS) + for size in sorted(b.keys()): + ents = b[size] + print '''\ + case {}:'''.format(size) + print '''\ + switch (util::lowcase(s[len - 1])) {''' + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print '''\ + case '{}':'''.format(c) + for k in headers: + print '''\ + if (util::strieq("{}", s, {})) {{ + hdidx[{}] = idx; + return; + }}'''.format(k[:-1], size - 1, to_enum_hd(k)) + print '''\ + break;''' + print '''\ + } + break;''' + print '''\ + } +}''' + +if __name__ == '__main__': + gen_enum() + print '' + gen_index_header() diff --git a/src/HttpServer.cc b/src/HttpServer.cc index a4fcc48b..514ce36d 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -89,7 +89,9 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; } namespace { void append_nv(Stream *stream, const std::vector &nva) { + size_t idx = 0; for (auto &nv : nva) { + http2::index_header(stream->hdidx, nv.name, nv.namelen, idx++); http2::add_header(stream->headers, nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); } @@ -270,6 +272,8 @@ Stream::Stream(Http2Handler *handler, int32_t stream_id) wtimer.data = this; headers.reserve(10); + + http2::init_hdidx(hdidx); } Stream::~Stream() { @@ -734,13 +738,12 @@ int Http2Handler::submit_non_final_response(const std::string &status, int Http2Handler::submit_push_promise(Stream *stream, const std::string &push_path) { - auto itr = - std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header(":authority", "")); + auto authority = + http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); - if (itr == std::end(stream->headers) || (*itr).name != ":authority") { - itr = std::lower_bound(std::begin(stream->headers), - std::end(stream->headers), Header("host", "")); + if (!authority) { + authority = + http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); } auto nva = std::vector{ @@ -748,7 +751,7 @@ int Http2Handler::submit_push_promise(Stream *stream, http2::make_nv_ls(":path", push_path), get_config()->no_tls ? http2::make_nv_ll(":scheme", "http") : http2::make_nv_ll(":scheme", "https"), - http2::make_nv_ls(":authority", (*itr).value)}; + http2::make_nv_ls(":authority", authority->value)}; auto promised_stream_id = nghttp2_submit_push_promise( session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(), @@ -896,10 +899,13 @@ namespace { void prepare_redirect_response(Stream *stream, Http2Handler *hd, const std::string &path, const std::string &status) { - auto scheme = http2::get_unique_header(stream->headers, ":scheme"); - auto authority = http2::get_unique_header(stream->headers, ":authority"); + auto scheme = + http2::get_header(stream->hdidx, http2::HD_SCHEME, stream->headers); + auto authority = + http2::get_header(stream->hdidx, http2::HD_AUTHORITY, stream->headers); if (!authority) { - authority = http2::get_unique_header(stream->headers, "host"); + authority = + http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); } auto redirect_url = scheme->value; @@ -918,17 +924,15 @@ void prepare_response(Stream *stream, Http2Handler *hd, bool allow_push = true) { int rv; auto reqpath = - (*std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header(":path", ""))).value; + http2::get_header(stream->hdidx, http2::HD_PATH, stream->headers)->value; auto ims = - std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header("if-modified-since", "")); + get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers); time_t last_mod = 0; bool last_mod_found = false; - if (ims != std::end(stream->headers) && (*ims).name == "if-modified-since") { + if (ims) { last_mod_found = true; - last_mod = util::parse_http_date((*ims).value); + last_mod = util::parse_http_date(ims->value); } auto query_pos = reqpath.find("?"); std::string url; @@ -1011,10 +1015,6 @@ void prepare_response(Stream *stream, Http2Handler *hd, } } // namespace -namespace { -const char *REQUIRED_HEADERS[] = {":method", ":path", ":scheme", nullptr}; -} // namespace - namespace { int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, @@ -1041,7 +1041,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, if (namelen > 0 && name[0] == ':') { if ((!stream->headers.empty() && stream->headers.back().name.c_str()[0] != ':') || - !http2::check_http2_request_pseudo_header(name, namelen)) { + !http2::check_http2_request_pseudo_header(stream->hdidx, name, + namelen)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -1049,6 +1050,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } } + http2::index_header(stream->hdidx, name, namelen, stream->headers.size()); + http2::add_header(stream->headers, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); return 0; @@ -1110,27 +1113,13 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - http2::normalize_headers(stream->headers); - if (!http2::check_http2_request_headers(stream->headers)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - for (size_t i = 0; REQUIRED_HEADERS[i]; ++i) { - if (!http2::get_unique_header(stream->headers, REQUIRED_HEADERS[i])) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - } - // intermediary translating from HTTP/1 request to HTTP/2 may - // not produce :authority header field. In this case, it should - // provide host HTTP/1.1 header field. - if (!http2::get_unique_header(stream->headers, ":authority") && - !http2::get_unique_header(stream->headers, "host")) { + if (!http2::check_http2_request_headers(stream->hdidx)) { hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); return 0; } - auto expect100 = http2::get_header(stream->headers, "expect"); + auto expect100 = + http2::get_header(stream->hdidx, http2::HD_EXPECT, stream->headers); if (expect100 && util::strieq("100-continue", expect100->value.c_str())) { hd->submit_non_final_response("100", frame->hd.stream_id); diff --git a/src/HttpServer.h b/src/HttpServer.h index dc63e3f7..87d32964 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -83,6 +83,7 @@ struct Stream { int64_t body_left; int32_t stream_id; int file; + int hdidx[http2::HD_MAXIDX]; Stream(Http2Handler *handler, int32_t stream_id); ~Stream(); }; diff --git a/src/http2.cc b/src/http2.cc index 5d4374ec..95ebc49f 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -572,6 +572,204 @@ int parse_http_status_code(const std::string &src) { return status; } +void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); } + +// This function was generated by genheaderfunc.py. Inspired by h2o +// header lookup. https://github.com/h2o/h2o +void index_header(int *hdidx, const uint8_t *s, size_t len, size_t idx) { + switch (len) { + case 2: + switch (util::lowcase(s[len - 1])) { + case 'e': + if (util::strieq("t", s, 1)) { + hdidx[HD_TE] = idx; + return; + } + break; + } + break; + case 4: + switch (util::lowcase(s[len - 1])) { + case 't': + if (util::strieq("hos", s, 3)) { + hdidx[HD_HOST] = idx; + return; + } + break; + } + break; + case 5: + switch (util::lowcase(s[len - 1])) { + case 'h': + if (util::strieq(":pat", s, 4)) { + hdidx[HD_PATH] = idx; + return; + } + break; + } + break; + case 6: + switch (util::lowcase(s[len - 1])) { + case 't': + if (util::strieq("expec", s, 5)) { + hdidx[HD_EXPECT] = idx; + return; + } + break; + } + break; + case 7: + switch (util::lowcase(s[len - 1])) { + case 'd': + if (util::strieq(":metho", s, 6)) { + hdidx[HD_METHOD] = idx; + return; + } + break; + case 'e': + if (util::strieq(":schem", s, 6)) { + hdidx[HD_SCHEME] = idx; + return; + } + if (util::strieq("upgrad", s, 6)) { + hdidx[HD_UPGRADE] = idx; + return; + } + break; + } + break; + case 10: + switch (util::lowcase(s[len - 1])) { + case 'e': + if (util::strieq("keep-aliv", s, 9)) { + hdidx[HD_KEEP_ALIVE] = idx; + return; + } + break; + case 'n': + if (util::strieq("connectio", s, 9)) { + hdidx[HD_CONNECTION] = idx; + return; + } + break; + case 'y': + if (util::strieq(":authorit", s, 9)) { + hdidx[HD_AUTHORITY] = idx; + return; + } + break; + } + break; + case 16: + switch (util::lowcase(s[len - 1])) { + case 'n': + if (util::strieq("proxy-connectio", s, 15)) { + hdidx[HD_PROXY_CONNECTION] = idx; + return; + } + break; + } + break; + case 17: + switch (util::lowcase(s[len - 1])) { + case 'e': + if (util::strieq("if-modified-sinc", s, 16)) { + hdidx[HD_IF_MODIFIED_SINCE] = idx; + return; + } + break; + case 'g': + if (util::strieq("transfer-encodin", s, 16)) { + hdidx[HD_TRANSFER_ENCODING] = idx; + return; + } + break; + } + break; + } +} + +bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *s, + size_t len) { + switch (len) { + case 5: + switch (util::lowcase(s[len - 1])) { + case 'h': + if (util::strieq(":pat", s, 4)) { + if (hdidx[HD_PATH] != -1) { + return false; + } + return true; + } + break; + } + break; + case 7: + switch (util::lowcase(s[len - 1])) { + case 'd': + if (util::strieq(":metho", s, 6)) { + if (hdidx[HD_METHOD] != -1) { + return false; + } + return true; + } + break; + case 'e': + if (util::strieq(":schem", s, 6)) { + if (hdidx[HD_SCHEME] != -1) { + return false; + } + return true; + } + break; + } + break; + case 10: + switch (util::lowcase(s[len - 1])) { + case 'y': + if (util::strieq(":authorit", s, 9)) { + if (hdidx[HD_AUTHORITY] != -1) { + return false; + } + return true; + } + break; + } + break; + } + return false; +} + +bool check_http2_headers(int *hdidx) { + if (hdidx[HD_CONNECTION] != -1 || hdidx[HD_KEEP_ALIVE] != -1 || + hdidx[HD_PROXY_CONNECTION] != -1 || hdidx[HD_TE] != -1 || + hdidx[HD_TRANSFER_ENCODING] != -1 || hdidx[HD_UPGRADE] != -1) { + return false; + } + return true; +} + +bool check_http2_request_headers(int *hdidx) { + if (!check_http2_headers(hdidx)) { + return false; + } + if (hdidx[HD_METHOD] == -1 || hdidx[HD_PATH] == -1 || + hdidx[HD_SCHEME] == -1 || + (hdidx[HD_AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) { + return false; + } + return true; +} + +const Headers::value_type *get_header(int *hdidx, int hdkey, + const Headers &nva) { + auto i = hdidx[hdkey]; + if (i == -1) { + return nullptr; + } + return &nva[i]; +} + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2.h b/src/http2.h index 0efcfc5b..3020ae48 100644 --- a/src/http2.h +++ b/src/http2.h @@ -218,6 +218,45 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, // Returns parsed HTTP status code. Returns -1 on failure. int parse_http_status_code(const std::string &src); +enum { + HD_AUTHORITY, + HD_METHOD, + HD_PATH, + HD_SCHEME, + HD_CONNECTION, + HD_EXPECT, + HD_HOST, + HD_IF_MODIFIED_SINCE, + HD_KEEP_ALIVE, + HD_PROXY_CONNECTION, + HD_TE, + HD_TRANSFER_ENCODING, + HD_UPGRADE, + HD_MAXIDX, +}; + +// Initializes |hdidx|, header index. The |hdidx| must point to the +// array containing at least HD_MAXIDX elements. +void init_hdidx(int *hdidx); +// Indexes header |name| of length |namelen| using index |idx|. +void index_header(int *hdidx, const uint8_t *name, size_t namelen, size_t idx); + +// Checks pseudo header |name| of length |namelen| are unique and +// allowed for HTTP/2 request. +bool check_http2_request_pseudo_header(int *hdidx, const uint8_t *name, + size_t namelen); + +// Checks |hdidx| does not contain disallowed headers. +bool check_http2_headers(int *hdidx); +// Checks |hdidx| does not contain disallowed headers and contains +// mandatory headers. This funtions internally calls +// check_http2_headers(). +bool check_http2_request_headers(int *hdidx); + +// Returns header denoted by |hdkey| using index |hdidx|. +const Headers::value_type *get_header(int *hdidx, int hdkey, + const Headers &nva); + } // namespace http2 } // namespace nghttp2