nghttpd: Use faster request header handling
This commit is contained in:
parent
aaf0dc825d
commit
8e3406ad20
|
@ -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()
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
198
src/http2.cc
198
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
|
||||
|
|
39
src/http2.h
39
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
|
||||
|
|
Loading…
Reference in New Issue