nghttpd: Use faster request header handling

This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-03 00:12:26 +09:00
parent aaf0dc825d
commit 8e3406ad20
5 changed files with 353 additions and 38 deletions

88
genheaderfunc.py Executable file
View File

@ -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()

View File

@ -89,7 +89,9 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
namespace {
void append_nv(Stream *stream, const std::vector<nghttp2_nv> &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<nghttp2_nv>{
@ -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);

View File

@ -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();
};

View File

@ -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

View File

@ -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