Skip to content

Commit

Permalink
nghttpx: Tokenize request method
Browse files Browse the repository at this point in the history
We share the same method value with http-parser.  This commit also
returns 501 for unknown request method on HTTP/2 and SPDY frontend.
  • Loading branch information
tatsuhiro-t committed Jun 9, 2015
1 parent f9c60d5 commit 41dd5f6
Show file tree
Hide file tree
Showing 15 changed files with 420 additions and 107 deletions.
69 changes: 3 additions & 66 deletions genheaderfunc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python

from gentokenlookup import gentokenlookup

HEADERS = [
':authority',
':method',
Expand Down Expand Up @@ -34,70 +36,5 @@
'upgrade'
]

def to_enum_hd(k):
res = 'HD_'
for c in k.upper():
if c == ':' or 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 '''\
int lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {'''
b = build_header(HEADERS)
for size in sorted(b.keys()):
ents = b[size]
print '''\
case {}:'''.format(size)
print '''\
switch (name[{}]) {{'''.format(size - 1)
for c in sorted(ents.keys()):
headers = sorted(ents[c])
print '''\
case '{}':'''.format(c)
for k in headers:
print '''\
if (util::streq_l("{}", name, {})) {{
return {};
}}'''.format(k[:-1], size - 1, to_enum_hd(k))
print '''\
break;'''
print '''\
}
break;'''
print '''\
}
return -1;
}'''

if __name__ == '__main__':
gen_enum()
print ''
gen_index_header()
gentokenlookup(HEADERS, 'HD')
53 changes: 53 additions & 0 deletions genmethodfunc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python
from __future__ import unicode_literals
from io import StringIO

from gentokenlookup import gentokenlookup

# copied from http-parser/http_parser.h, and stripped trailing spaces
# and backslashes.
SRC = '''
XX(0, DELETE, DELETE)
XX(1, GET, GET)
XX(2, HEAD, HEAD)
XX(3, POST, POST)
XX(4, PUT, PUT)
/* pathological */
XX(5, CONNECT, CONNECT)
XX(6, OPTIONS, OPTIONS)
XX(7, TRACE, TRACE)
/* webdav */
XX(8, COPY, COPY)
XX(9, LOCK, LOCK)
XX(10, MKCOL, MKCOL)
XX(11, MOVE, MOVE)
XX(12, PROPFIND, PROPFIND)
XX(13, PROPPATCH, PROPPATCH)
XX(14, SEARCH, SEARCH)
XX(15, UNLOCK, UNLOCK)
/* subversion */
XX(16, REPORT, REPORT)
XX(17, MKACTIVITY, MKACTIVITY)
XX(18, CHECKOUT, CHECKOUT)
XX(19, MERGE, MERGE)
/* upnp */
XX(20, MSEARCH, M-SEARCH)
XX(21, NOTIFY, NOTIFY)
XX(22, SUBSCRIBE, SUBSCRIBE)
XX(23, UNSUBSCRIBE, UNSUBSCRIBE)
/* RFC-5789 */
XX(24, PATCH, PATCH)
XX(25, PURGE, PURGE)
/* CalDAV */
XX(26, MKCALENDAR, MKCALENDAR)
'''

if __name__ == '__main__':
methods = []
for line in StringIO(SRC):
line = line.strip()
if not line.startswith('XX'):
continue
_, m, _ = line.split(',', 2)
methods.append(m.strip())
gentokenlookup(methods, 'HTTP')
69 changes: 69 additions & 0 deletions gentokenlookup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python

def to_enum_hd(k, prefix):
res = prefix + '_'
for c in k.upper():
if c == ':' or 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(tokens, prefix):
print '''\
enum {'''
for k in sorted(tokens):
print '''\
{},'''.format(to_enum_hd(k, prefix))
print '''\
{}_MAXIDX,
}};'''.format(prefix)

def gen_index_header(tokens, prefix):
print '''\
int lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {'''
b = build_header(tokens)
for size in sorted(b.keys()):
ents = b[size]
print '''\
case {}:'''.format(size)
print '''\
switch (name[{}]) {{'''.format(size - 1)
for c in sorted(ents.keys()):
headers = sorted(ents[c])
print '''\
case '{}':'''.format(c)
for k in headers:
print '''\
if (util::streq_l("{}", name, {})) {{
return {};
}}'''.format(k[:-1], size - 1, to_enum_hd(k, prefix))
print '''\
break;'''
print '''\
}
break;'''
print '''\
}
return -1;
}'''

def gentokenlookup(tokens, prefix):
gen_enum(tokens, prefix)
print ''
gen_index_header(tokens, prefix)
20 changes: 20 additions & 0 deletions integration-tests/nghttpx_http2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,26 @@ func TestH2H1ConnectFailure(t *testing.T) {
}
}

// TestH2H1InvalidMethod tests that server rejects invalid method with
// 501.
func TestH2H1InvalidMethod(t *testing.T) {
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()

res, err := st.http2(requestParam{
name: "TestH2H1InvalidMethod",
method: "get",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 501; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}

// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2
// request is assembled into 1 when forwarding to HTTP/1 backend link.
func TestH2H1AssembleCookies(t *testing.T) {
Expand Down
20 changes: 20 additions & 0 deletions integration-tests/nghttpx_spdy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,26 @@ func TestS3H1HeaderFields(t *testing.T) {
}
}

// TestS3H1InvalidMethod tests that server rejects invalid method with
// 501.
func TestS3H1InvalidMethod(t *testing.T) {
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
t.Errorf("server should not forward this request")
})
defer st.Close()

res, err := st.spdy(requestParam{
name: "TestS3H1InvalidMethod",
method: "get",
})
if err != nil {
t.Fatalf("Error st.spdy() = %v", err)
}
if got, want := res.status, 501; got != want {
t.Errorf("status: %v; want %v", got, want)
}
}

// TestS3H2ConnectFailure tests that server handles the situation that
// connection attempt to HTTP/2 backend failed.
func TestS3H2ConnectFailure(t *testing.T) {
Expand Down
Loading

0 comments on commit 41dd5f6

Please sign in to comment.