Merge branch 'master' into v1.0.0
This commit is contained in:
commit
5937b4b6f7
|
@ -101,9 +101,9 @@ RUN autoreconf -i && \
|
|||
--disable-threads \
|
||||
LIBSPDYLAY_CFLAGS=-I$PREFIX/usr/local/include \
|
||||
LIBSPDYLAY_LIBS="-L$PREFIX/usr/local/lib -lspdylay" \
|
||||
CPPFLAGS="-I$PREFIX/include" \
|
||||
CPPFLAGS="-fPIE -I$PREFIX/include" \
|
||||
CXXFLAGS="-fno-strict-aliasing" \
|
||||
PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
|
||||
LDFLAGS="-L$PREFIX/lib" && \
|
||||
LDFLAGS="-fPIE -pie -L$PREFIX/lib" && \
|
||||
make && \
|
||||
arm-linux-androideabi-strip src/nghttpx src/nghttpd src/nghttp
|
||||
|
|
|
@ -42,6 +42,6 @@ PATH=$TOOLCHAIN/bin:$PATH
|
|||
--enable-werror \
|
||||
CC=clang \
|
||||
CXX=clang++ \
|
||||
CPPFLAGS="-I$PREFIX/include" \
|
||||
CPPFLAGS="-fPIE -I$PREFIX/include" \
|
||||
PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
|
||||
LDFLAGS="-L$PREFIX/lib"
|
||||
LDFLAGS="-fPIE -pie -L$PREFIX/lib"
|
||||
|
|
|
@ -25,13 +25,13 @@ dnl Do not change user variables!
|
|||
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
|
||||
|
||||
AC_PREREQ(2.61)
|
||||
AC_INIT([nghttp2], [0.7.11-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
AC_INIT([nghttp2], [0.7.13-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
LT_PREREQ([2.2.6])
|
||||
LT_INIT()
|
||||
dnl See versioning rule:
|
||||
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||
AC_SUBST(LT_CURRENT, 13)
|
||||
AC_SUBST(LT_REVISION, 0)
|
||||
AC_SUBST(LT_REVISION, 1)
|
||||
AC_SUBST(LT_AGE, 8)
|
||||
|
||||
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
|
||||
|
|
|
@ -8,7 +8,7 @@ _nghttp()
|
|||
_get_comp_words_by_ref cur prev
|
||||
case $cur in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W '--verbose --no-dep --get-assets --har --header-table-size --multiply --padding --hexdump --dep-idle --continuation --connection-window-bits --peer-max-concurrent-streams --timeout --data --no-content-length --version --color --cert --upgrade --remote-name --trailer --weight --help --key --null-out --window-bits --stat --header ' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W '--no-push --verbose --no-dep --get-assets --har --header-table-size --multiply --padding --hexdump --continuation --connection-window-bits --peer-max-concurrent-streams --timeout --data --no-content-length --version --color --cert --upgrade --remote-name --trailer --weight --help --key --null-out --window-bits --stat --header ' -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "H2LOAD" "1" "April 08, 2015" "0.7.10" "nghttp2"
|
||||
.TH "H2LOAD" "1" "April 19, 2015" "0.7.12" "nghttp2"
|
||||
.SH NAME
|
||||
h2load \- HTTP/2 benchmarking tool
|
||||
.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
.. program:: h2load
|
||||
|
||||
h2load(1)
|
||||
=========
|
||||
|
||||
|
|
75
doc/nghttp.1
75
doc/nghttp.1
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTP" "1" "April 08, 2015" "0.7.10" "nghttp2"
|
||||
.TH "NGHTTP" "1" "April 19, 2015" "0.7.12" "nghttp2"
|
||||
.SH NAME
|
||||
nghttp \- HTTP/2 experimental client
|
||||
.
|
||||
|
@ -104,8 +104,8 @@ Add a header to the requests. Example: \fI\%\-H\fP\(aq:method: PUT\(aq
|
|||
.B \-\-trailer=<HEADER>
|
||||
Add a trailer header to the requests. <HEADER> must not
|
||||
include pseudo header field (header field name starting
|
||||
with \(aq:\(aq). To send trailer, one must use \fI\-d\fP option to
|
||||
send request body. Example: \fI\-\-trailer\fP \(aqfoo: bar\(aq.
|
||||
with \(aq:\(aq). To send trailer, one must use \fI\%\-d\fP option to
|
||||
send request body. Example: \fI\%\-\-trailer\fP \(aqfoo: bar\(aq.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
|
@ -135,7 +135,7 @@ requested twice. This option disables it too.
|
|||
.TP
|
||||
.B \-u, \-\-upgrade
|
||||
Perform HTTP Upgrade for HTTP/2. This option is ignored
|
||||
if the request URI has https scheme. If \fI\-d\fP is used, the
|
||||
if the request URI has https scheme. If \fI\%\-d\fP is used, the
|
||||
HTTP upgrade request is performed with OPTIONS method.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
|
@ -192,11 +192,6 @@ Don\(aqt send dependency based priority hint to server.
|
|||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-dep\-idle
|
||||
Use idle streams as anchor nodes to express priority.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-hexdump
|
||||
Display the incoming traffic in hexadecimal (Canonical
|
||||
hex+ASCII display). If SSL/TLS is used, decrypted data
|
||||
|
@ -204,6 +199,11 @@ are used.
|
|||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-no\-push
|
||||
Disable server push.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-version
|
||||
Display version information and exit.
|
||||
.UNINDENT
|
||||
|
@ -215,6 +215,63 @@ Display this help and exit.
|
|||
.sp
|
||||
The <SIZE> argument is an integer and an optional unit (e.g., 10K is
|
||||
10 * 1024). Units are K, M and G (powers of 1024).
|
||||
.SH DEPENDENCY BASED PRIORITY
|
||||
.sp
|
||||
nghttp sends priority hints to server by default unless
|
||||
\fI\%\-\-no\-dep\fP is used. nghttp mimics the way Firefox employs to
|
||||
manages dependency using idle streams. We follows the behaviour of
|
||||
Firefox Nightly as of April, 2015, and nghttp\(aqs behaviour is very
|
||||
static and could be different from Firefox in detail. But reproducing
|
||||
the same behaviour of Firefox is not our goal. The goal is provide
|
||||
the easy way to test out the dependency priority in server
|
||||
implementation.
|
||||
.sp
|
||||
When connection is established, nghttp sends 5 PRIORITY frames to idle
|
||||
streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency
|
||||
tree:
|
||||
.INDENT 0.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
+\-\-\-\-\-+
|
||||
|id=0 |
|
||||
+\-\-\-\-\-+
|
||||
^ ^ ^
|
||||
w=201 / | \e w=1
|
||||
/ | \e
|
||||
/ w=101| \e
|
||||
+\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+
|
||||
|id=3 | |id=5 | |id=7 |
|
||||
+\-\-\-\-\-+ +\-\-\-\-\-+ +\-\-\-\-\-+
|
||||
^ ^
|
||||
w=1 | w=1 |
|
||||
| |
|
||||
+\-\-\-\-\-+ +\-\-\-\-\-+
|
||||
|id=11| |id=9 |
|
||||
+\-\-\-\-\-+ +\-\-\-\-\-+
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
In the above figure, \fBid\fP means stream ID, and \fBw\fP means weight.
|
||||
The stream 0 is non\-existence stream, and forms the root of the tree.
|
||||
The stream 7 and 9 are not used for now.
|
||||
.sp
|
||||
The URIs given in the command\-line depend on stream 11 with the weight
|
||||
given in \fI\%\-p\fP option, which defaults to 16.
|
||||
.sp
|
||||
If \fI\%\-a\fP option is used, nghttp parses the resource pointed by
|
||||
URI given in command\-line as html, and extracts resource links from
|
||||
it. When requesting those resources, nghttp uses dependency according
|
||||
to its resource type.
|
||||
.sp
|
||||
For CSS, and Javascript files inside "head" element, they depend on
|
||||
stream 3 with the weight 2. The Javascript files outside "head"
|
||||
element depend on stream 5 with the weight 2. The mages depend on
|
||||
stream 11 with the weight 12. The other resources (e.g., icon) depend
|
||||
on stream 11 with the weight 2.
|
||||
.SH SEE ALSO
|
||||
.sp
|
||||
\fInghttpd(1)\fP, \fInghttpx(1)\fP, \fIh2load(1)\fP
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
.. program:: nghttp
|
||||
|
||||
nghttp(1)
|
||||
=========
|
||||
|
||||
|
@ -143,16 +145,16 @@ OPTIONS
|
|||
|
||||
Don't send dependency based priority hint to server.
|
||||
|
||||
.. option:: --dep-idle
|
||||
|
||||
Use idle streams as anchor nodes to express priority.
|
||||
|
||||
.. option:: --hexdump
|
||||
|
||||
Display the incoming traffic in hexadecimal (Canonical
|
||||
hex+ASCII display). If SSL/TLS is used, decrypted data
|
||||
are used.
|
||||
|
||||
.. option:: --no-push
|
||||
|
||||
Disable server push.
|
||||
|
||||
.. option:: --version
|
||||
|
||||
Display version information and exit.
|
||||
|
@ -166,6 +168,57 @@ OPTIONS
|
|||
The <SIZE> argument is an integer and an optional unit (e.g., 10K is
|
||||
10 * 1024). Units are K, M and G (powers of 1024).
|
||||
|
||||
DEPENDENCY BASED PRIORITY
|
||||
-------------------------
|
||||
|
||||
nghttp sends priority hints to server by default unless
|
||||
:option:`--no-dep` is used. nghttp mimics the way Firefox employs to
|
||||
manages dependency using idle streams. We follows the behaviour of
|
||||
Firefox Nightly as of April, 2015, and nghttp's behaviour is very
|
||||
static and could be different from Firefox in detail. But reproducing
|
||||
the same behaviour of Firefox is not our goal. The goal is provide
|
||||
the easy way to test out the dependency priority in server
|
||||
implementation.
|
||||
|
||||
When connection is established, nghttp sends 5 PRIORITY frames to idle
|
||||
streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency
|
||||
tree::
|
||||
|
||||
+-----+
|
||||
|id=0 |
|
||||
+-----+
|
||||
^ ^ ^
|
||||
w=201 / | \ w=1
|
||||
/ | \
|
||||
/ w=101| \
|
||||
+-----+ +-----+ +-----+
|
||||
|id=3 | |id=5 | |id=7 |
|
||||
+-----+ +-----+ +-----+
|
||||
^ ^
|
||||
w=1 | w=1 |
|
||||
| |
|
||||
+-----+ +-----+
|
||||
|id=11| |id=9 |
|
||||
+-----+ +-----+
|
||||
|
||||
In the above figure, ``id`` means stream ID, and ``w`` means weight.
|
||||
The stream 0 is non-existence stream, and forms the root of the tree.
|
||||
The stream 7 and 9 are not used for now.
|
||||
|
||||
The URIs given in the command-line depend on stream 11 with the weight
|
||||
given in :option:`-p` option, which defaults to 16.
|
||||
|
||||
If :option:`-a` option is used, nghttp parses the resource pointed by
|
||||
URI given in command-line as html, and extracts resource links from
|
||||
it. When requesting those resources, nghttp uses dependency according
|
||||
to its resource type.
|
||||
|
||||
For CSS, and Javascript files inside "head" element, they depend on
|
||||
stream 3 with the weight 2. The Javascript files outside "head"
|
||||
element depend on stream 5 with the weight 2. The mages depend on
|
||||
stream 11 with the weight 12. The other resources (e.g., icon) depend
|
||||
on stream 11 with the weight 2.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,3 +1,54 @@
|
|||
DEPENDENCY BASED PRIORITY
|
||||
-------------------------
|
||||
|
||||
nghttp sends priority hints to server by default unless
|
||||
:option:`--no-dep` is used. nghttp mimics the way Firefox employs to
|
||||
manages dependency using idle streams. We follows the behaviour of
|
||||
Firefox Nightly as of April, 2015, and nghttp's behaviour is very
|
||||
static and could be different from Firefox in detail. But reproducing
|
||||
the same behaviour of Firefox is not our goal. The goal is provide
|
||||
the easy way to test out the dependency priority in server
|
||||
implementation.
|
||||
|
||||
When connection is established, nghttp sends 5 PRIORITY frames to idle
|
||||
streams 3, 5, 7, 9 and 11 to create "anchor" nodes in dependency
|
||||
tree::
|
||||
|
||||
+-----+
|
||||
|id=0 |
|
||||
+-----+
|
||||
^ ^ ^
|
||||
w=201 / | \ w=1
|
||||
/ | \
|
||||
/ w=101| \
|
||||
+-----+ +-----+ +-----+
|
||||
|id=3 | |id=5 | |id=7 |
|
||||
+-----+ +-----+ +-----+
|
||||
^ ^
|
||||
w=1 | w=1 |
|
||||
| |
|
||||
+-----+ +-----+
|
||||
|id=11| |id=9 |
|
||||
+-----+ +-----+
|
||||
|
||||
In the above figure, ``id`` means stream ID, and ``w`` means weight.
|
||||
The stream 0 is non-existence stream, and forms the root of the tree.
|
||||
The stream 7 and 9 are not used for now.
|
||||
|
||||
The URIs given in the command-line depend on stream 11 with the weight
|
||||
given in :option:`-p` option, which defaults to 16.
|
||||
|
||||
If :option:`-a` option is used, nghttp parses the resource pointed by
|
||||
URI given in command-line as html, and extracts resource links from
|
||||
it. When requesting those resources, nghttp uses dependency according
|
||||
to its resource type.
|
||||
|
||||
For CSS, and Javascript files inside "head" element, they depend on
|
||||
stream 3 with the weight 2. The Javascript files outside "head"
|
||||
element depend on stream 5 with the weight 2. The mages depend on
|
||||
stream 11 with the weight 12. The other resources (e.g., icon) depend
|
||||
on stream 11 with the weight 2.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPD" "1" "April 08, 2015" "0.7.10" "nghttp2"
|
||||
.TH "NGHTTPD" "1" "April 19, 2015" "0.7.12" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpd \- HTTP/2 experimental server
|
||||
.
|
||||
|
@ -63,7 +63,7 @@ address determined by getaddrinfo is used.
|
|||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-D, \-\-daemon
|
||||
Run in a background. If \fI\-D\fP is used, the current working
|
||||
Run in a background. If \fI\%\-D\fP is used, the current working
|
||||
directory is changed to \(aq\fI/\fP\(aq. Therefore if this option
|
||||
is used, \fI\%\-d\fP option must be specified.
|
||||
.UNINDENT
|
||||
|
@ -109,7 +109,7 @@ Push resources <PUSH_PATH>s when <PATH> is requested.
|
|||
This option can be used repeatedly to specify multiple
|
||||
push configurations. <PATH> and <PUSH_PATH>s are
|
||||
relative to document root. See \fI\%\-\-htdocs\fP option.
|
||||
Example: \fI\-p\fP/=/foo.png \fI\-p\fP/doc=/bar.css
|
||||
Example: \fI\%\-p\fP/=/foo.png \fI\%\-p\fP/doc=/bar.css
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
.. program:: nghttpd
|
||||
|
||||
nghttpd(1)
|
||||
==========
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPX" "1" "April 08, 2015" "0.7.10" "nghttp2"
|
||||
.TH "NGHTTPX" "1" "April 19, 2015" "0.7.12" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpx \- HTTP/2 experimental proxy
|
||||
.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
.. program:: nghttpx
|
||||
|
||||
nghttpx(1)
|
||||
==========
|
||||
|
||||
|
|
|
@ -1,19 +1,72 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
HEADERS = [
|
||||
':authority',
|
||||
':method',
|
||||
':path',
|
||||
':scheme',
|
||||
':status',
|
||||
"content-length",
|
||||
"host",
|
||||
"te",
|
||||
'connection',
|
||||
'keep-alive',
|
||||
'proxy-connection',
|
||||
'transfer-encoding',
|
||||
'upgrade'
|
||||
(':authority', 0),
|
||||
(':method', 1),
|
||||
(':method', 2),
|
||||
(':path', 3),
|
||||
(':path', 4),
|
||||
(':scheme', 5),
|
||||
(':scheme', 6),
|
||||
(':status', 7),
|
||||
(':status', 8),
|
||||
(':status', 9),
|
||||
(':status', 10),
|
||||
(':status', 11),
|
||||
(':status', 12),
|
||||
(':status', 13),
|
||||
('accept-charset', 14),
|
||||
('accept-encoding', 15),
|
||||
('accept-language', 16),
|
||||
('accept-ranges', 17),
|
||||
('accept', 18),
|
||||
('access-control-allow-origin', 19),
|
||||
('age', 20),
|
||||
('allow', 21),
|
||||
('authorization', 22),
|
||||
('cache-control', 23),
|
||||
('content-disposition', 24),
|
||||
('content-encoding', 25),
|
||||
('content-language', 26),
|
||||
('content-length', 27),
|
||||
('content-location', 28),
|
||||
('content-range', 29),
|
||||
('content-type', 30),
|
||||
('cookie', 31),
|
||||
('date', 32),
|
||||
('etag', 33),
|
||||
('expect', 34),
|
||||
('expires', 35),
|
||||
('from', 36),
|
||||
('host', 37),
|
||||
('if-match', 38),
|
||||
('if-modified-since', 39),
|
||||
('if-none-match', 40),
|
||||
('if-range', 41),
|
||||
('if-unmodified-since', 42),
|
||||
('last-modified', 43),
|
||||
('link', 44),
|
||||
('location', 45),
|
||||
('max-forwards', 46),
|
||||
('proxy-authenticate', 47),
|
||||
('proxy-authorization', 48),
|
||||
('range', 49),
|
||||
('referer', 50),
|
||||
('refresh', 51),
|
||||
('retry-after', 52),
|
||||
('server', 53),
|
||||
('set-cookie', 54),
|
||||
('strict-transport-security', 55),
|
||||
('transfer-encoding', 56),
|
||||
('user-agent', 57),
|
||||
('vary', 58),
|
||||
('via', 59),
|
||||
('www-authenticate', 60),
|
||||
('te', None),
|
||||
('connection', None),
|
||||
('keep-alive',None),
|
||||
('proxy-connection', None),
|
||||
('upgrade', None),
|
||||
]
|
||||
|
||||
def to_enum_hd(k):
|
||||
|
@ -27,7 +80,7 @@ def to_enum_hd(k):
|
|||
|
||||
def build_header(headers):
|
||||
res = {}
|
||||
for k in headers:
|
||||
for k, _ in headers:
|
||||
size = len(k)
|
||||
if size not in res:
|
||||
res[size] = {}
|
||||
|
@ -40,18 +93,20 @@ def build_header(headers):
|
|||
return res
|
||||
|
||||
def gen_enum():
|
||||
print '''\
|
||||
typedef enum {'''
|
||||
for k in sorted(HEADERS):
|
||||
print '''\
|
||||
{},'''.format(to_enum_hd(k))
|
||||
print '''\
|
||||
NGHTTP2_TOKEN_MAXIDX,
|
||||
} nghttp2_token;'''
|
||||
name = ''
|
||||
print 'typedef enum {'
|
||||
for k, token in HEADERS:
|
||||
if token is None:
|
||||
print ' {},'.format(to_enum_hd(k))
|
||||
else:
|
||||
if name != k:
|
||||
name = k
|
||||
print ' {} = {},'.format(to_enum_hd(k), token)
|
||||
print '} nghttp2_token;'
|
||||
|
||||
def gen_index_header():
|
||||
print '''\
|
||||
static int lookup_token(const uint8_t *name, size_t namelen) {
|
||||
static inline int lookup_token(const uint8_t *name, size_t namelen) {
|
||||
switch (namelen) {'''
|
||||
b = build_header(HEADERS)
|
||||
for size in sorted(b.keys()):
|
||||
|
@ -66,7 +121,7 @@ static int lookup_token(const uint8_t *name, size_t namelen) {
|
|||
case '{}':'''.format(c)
|
||||
for k in headers:
|
||||
print '''\
|
||||
if (streq("{}", name, {})) {{
|
||||
if (lstreq("{}", name, {})) {{
|
||||
return {};
|
||||
}}'''.format(k[:-1], size - 1, to_enum_hd(k))
|
||||
print '''\
|
||||
|
|
|
@ -61,6 +61,8 @@ def help2man(infile):
|
|||
description.append(line)
|
||||
|
||||
print '''
|
||||
.. program:: {cmdname}
|
||||
|
||||
{cmdname}(1)
|
||||
{cmdnameunderline}
|
||||
|
||||
|
|
|
@ -558,6 +558,39 @@ func TestH2H1RequestTrailer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH2H1Upgrade tests HTTP Upgrade to HTTP/2
|
||||
func TestH2H1Upgrade(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http1(requestParam{
|
||||
name: "TestH2H1Upgrade",
|
||||
header: []hpack.HeaderField{
|
||||
pair("Connection", "Upgrade, HTTP2-Settings"),
|
||||
pair("Upgrade", "h2c-14"),
|
||||
pair("HTTP2-Settings", "AAMAAABkAAQAAP__"),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http1() = %v", err)
|
||||
}
|
||||
|
||||
if got, want := res.status, 101; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
|
||||
res, err = st.http2(requestParam{
|
||||
httpUpgrade: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("res.status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1GracefulShutdown tests graceful shutdown.
|
||||
func TestH2H1GracefulShutdown(t *testing.T) {
|
||||
st := newServerTester(nil, t, noopHandler)
|
||||
|
|
|
@ -247,15 +247,16 @@ func (st *serverTester) readSpdyFrame() (spdy.Frame, error) {
|
|||
}
|
||||
|
||||
type requestParam struct {
|
||||
name string // name for this request to identify the request in log easily
|
||||
streamID uint32 // stream ID, automatically assigned if 0
|
||||
method string // method, defaults to GET
|
||||
scheme string // scheme, defaults to http
|
||||
authority string // authority, defaults to backend server address
|
||||
path string // path, defaults to /
|
||||
header []hpack.HeaderField // additional request header fields
|
||||
body []byte // request body
|
||||
trailer []hpack.HeaderField // trailer part
|
||||
name string // name for this request to identify the request in log easily
|
||||
streamID uint32 // stream ID, automatically assigned if 0
|
||||
method string // method, defaults to GET
|
||||
scheme string // scheme, defaults to http
|
||||
authority string // authority, defaults to backend server address
|
||||
path string // path, defaults to /
|
||||
header []hpack.HeaderField // additional request header fields
|
||||
body []byte // request body
|
||||
trailer []hpack.HeaderField // trailer part
|
||||
httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade
|
||||
}
|
||||
|
||||
// wrapper for request body to set trailer part
|
||||
|
@ -478,69 +479,70 @@ func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
|
|||
streams := make(map[uint32]*serverResponse)
|
||||
streams[id] = res
|
||||
|
||||
method := "GET"
|
||||
if rp.method != "" {
|
||||
method = rp.method
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":method", method))
|
||||
|
||||
scheme := "http"
|
||||
if rp.scheme != "" {
|
||||
scheme = rp.scheme
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":scheme", scheme))
|
||||
|
||||
authority := st.authority
|
||||
if rp.authority != "" {
|
||||
authority = rp.authority
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":authority", authority))
|
||||
|
||||
path := "/"
|
||||
if rp.path != "" {
|
||||
path = rp.path
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":path", path))
|
||||
|
||||
_ = st.enc.WriteField(pair("test-case", rp.name))
|
||||
|
||||
for _, h := range rp.header {
|
||||
_ = st.enc.WriteField(h)
|
||||
}
|
||||
|
||||
err := st.fr.WriteHeaders(http2.HeadersFrameParam{
|
||||
StreamID: id,
|
||||
EndStream: len(rp.body) == 0 && len(rp.trailer) == 0,
|
||||
EndHeaders: true,
|
||||
BlockFragment: st.headerBlkBuf.Bytes(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rp.body) != 0 {
|
||||
// TODO we assume rp.body fits in 1 frame
|
||||
if err := st.fr.WriteData(id, len(rp.trailer) == 0, rp.body); err != nil {
|
||||
return nil, err
|
||||
if !rp.httpUpgrade {
|
||||
method := "GET"
|
||||
if rp.method != "" {
|
||||
method = rp.method
|
||||
}
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":method", method))
|
||||
|
||||
if len(rp.trailer) != 0 {
|
||||
st.headerBlkBuf.Reset()
|
||||
for _, h := range rp.trailer {
|
||||
scheme := "http"
|
||||
if rp.scheme != "" {
|
||||
scheme = rp.scheme
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":scheme", scheme))
|
||||
|
||||
authority := st.authority
|
||||
if rp.authority != "" {
|
||||
authority = rp.authority
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":authority", authority))
|
||||
|
||||
path := "/"
|
||||
if rp.path != "" {
|
||||
path = rp.path
|
||||
}
|
||||
_ = st.enc.WriteField(pair(":path", path))
|
||||
|
||||
_ = st.enc.WriteField(pair("test-case", rp.name))
|
||||
|
||||
for _, h := range rp.header {
|
||||
_ = st.enc.WriteField(h)
|
||||
}
|
||||
|
||||
err := st.fr.WriteHeaders(http2.HeadersFrameParam{
|
||||
StreamID: id,
|
||||
EndStream: true,
|
||||
EndStream: len(rp.body) == 0 && len(rp.trailer) == 0,
|
||||
EndHeaders: true,
|
||||
BlockFragment: st.headerBlkBuf.Bytes(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(rp.body) != 0 {
|
||||
// TODO we assume rp.body fits in 1 frame
|
||||
if err := st.fr.WriteData(id, len(rp.trailer) == 0, rp.body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(rp.trailer) != 0 {
|
||||
st.headerBlkBuf.Reset()
|
||||
for _, h := range rp.trailer {
|
||||
_ = st.enc.WriteField(h)
|
||||
}
|
||||
err := st.fr.WriteHeaders(http2.HeadersFrameParam{
|
||||
StreamID: id,
|
||||
EndStream: true,
|
||||
EndHeaders: true,
|
||||
BlockFragment: st.headerBlkBuf.Bytes(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
loop:
|
||||
for {
|
||||
fr, err := st.readFrame()
|
||||
|
|
|
@ -2653,7 +2653,9 @@ NGHTTP2_EXTERN uint32_t
|
|||
*
|
||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||
* The |next_stream_id| is strictly less than the value
|
||||
* `nghttp2_session_get_next_stream_id()` returns.
|
||||
* `nghttp2_session_get_next_stream_id()` returns; or
|
||||
* |next_stream_id| is invalid (e.g., even integer for client, or
|
||||
* odd integer for server).
|
||||
*/
|
||||
NGHTTP2_EXTERN int nghttp2_session_set_next_stream_id(nghttp2_session *session,
|
||||
int32_t next_stream_id);
|
||||
|
|
821
lib/nghttp2_hd.c
821
lib/nghttp2_hd.c
File diff suppressed because it is too large
Load Diff
103
lib/nghttp2_hd.h
103
lib/nghttp2_hd.h
|
@ -49,7 +49,68 @@
|
|||
#define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12)
|
||||
|
||||
/* Exported for unit test */
|
||||
extern const size_t NGHTTP2_STATIC_TABLE_LENGTH;
|
||||
#define NGHTTP2_STATIC_TABLE_LENGTH 61
|
||||
|
||||
/* Generated by genlibtokenlookup.py */
|
||||
typedef enum {
|
||||
NGHTTP2_TOKEN__AUTHORITY = 0,
|
||||
NGHTTP2_TOKEN__METHOD = 1,
|
||||
NGHTTP2_TOKEN__PATH = 3,
|
||||
NGHTTP2_TOKEN__SCHEME = 5,
|
||||
NGHTTP2_TOKEN__STATUS = 7,
|
||||
NGHTTP2_TOKEN_ACCEPT_CHARSET = 14,
|
||||
NGHTTP2_TOKEN_ACCEPT_ENCODING = 15,
|
||||
NGHTTP2_TOKEN_ACCEPT_LANGUAGE = 16,
|
||||
NGHTTP2_TOKEN_ACCEPT_RANGES = 17,
|
||||
NGHTTP2_TOKEN_ACCEPT = 18,
|
||||
NGHTTP2_TOKEN_ACCESS_CONTROL_ALLOW_ORIGIN = 19,
|
||||
NGHTTP2_TOKEN_AGE = 20,
|
||||
NGHTTP2_TOKEN_ALLOW = 21,
|
||||
NGHTTP2_TOKEN_AUTHORIZATION = 22,
|
||||
NGHTTP2_TOKEN_CACHE_CONTROL = 23,
|
||||
NGHTTP2_TOKEN_CONTENT_DISPOSITION = 24,
|
||||
NGHTTP2_TOKEN_CONTENT_ENCODING = 25,
|
||||
NGHTTP2_TOKEN_CONTENT_LANGUAGE = 26,
|
||||
NGHTTP2_TOKEN_CONTENT_LENGTH = 27,
|
||||
NGHTTP2_TOKEN_CONTENT_LOCATION = 28,
|
||||
NGHTTP2_TOKEN_CONTENT_RANGE = 29,
|
||||
NGHTTP2_TOKEN_CONTENT_TYPE = 30,
|
||||
NGHTTP2_TOKEN_COOKIE = 31,
|
||||
NGHTTP2_TOKEN_DATE = 32,
|
||||
NGHTTP2_TOKEN_ETAG = 33,
|
||||
NGHTTP2_TOKEN_EXPECT = 34,
|
||||
NGHTTP2_TOKEN_EXPIRES = 35,
|
||||
NGHTTP2_TOKEN_FROM = 36,
|
||||
NGHTTP2_TOKEN_HOST = 37,
|
||||
NGHTTP2_TOKEN_IF_MATCH = 38,
|
||||
NGHTTP2_TOKEN_IF_MODIFIED_SINCE = 39,
|
||||
NGHTTP2_TOKEN_IF_NONE_MATCH = 40,
|
||||
NGHTTP2_TOKEN_IF_RANGE = 41,
|
||||
NGHTTP2_TOKEN_IF_UNMODIFIED_SINCE = 42,
|
||||
NGHTTP2_TOKEN_LAST_MODIFIED = 43,
|
||||
NGHTTP2_TOKEN_LINK = 44,
|
||||
NGHTTP2_TOKEN_LOCATION = 45,
|
||||
NGHTTP2_TOKEN_MAX_FORWARDS = 46,
|
||||
NGHTTP2_TOKEN_PROXY_AUTHENTICATE = 47,
|
||||
NGHTTP2_TOKEN_PROXY_AUTHORIZATION = 48,
|
||||
NGHTTP2_TOKEN_RANGE = 49,
|
||||
NGHTTP2_TOKEN_REFERER = 50,
|
||||
NGHTTP2_TOKEN_REFRESH = 51,
|
||||
NGHTTP2_TOKEN_RETRY_AFTER = 52,
|
||||
NGHTTP2_TOKEN_SERVER = 53,
|
||||
NGHTTP2_TOKEN_SET_COOKIE = 54,
|
||||
NGHTTP2_TOKEN_STRICT_TRANSPORT_SECURITY = 55,
|
||||
NGHTTP2_TOKEN_TRANSFER_ENCODING = 56,
|
||||
NGHTTP2_TOKEN_USER_AGENT = 57,
|
||||
NGHTTP2_TOKEN_VARY = 58,
|
||||
NGHTTP2_TOKEN_VIA = 59,
|
||||
NGHTTP2_TOKEN_WWW_AUTHENTICATE = 60,
|
||||
NGHTTP2_TOKEN_TE,
|
||||
NGHTTP2_TOKEN_CONNECTION,
|
||||
NGHTTP2_TOKEN_KEEP_ALIVE,
|
||||
NGHTTP2_TOKEN_PROXY_CONNECTION,
|
||||
NGHTTP2_TOKEN_UPGRADE,
|
||||
} nghttp2_token;
|
||||
|
||||
typedef enum {
|
||||
NGHTTP2_HD_FLAG_NONE = 0,
|
||||
|
@ -67,18 +128,14 @@ typedef enum {
|
|||
|
||||
typedef struct {
|
||||
nghttp2_nv nv;
|
||||
uint32_t name_hash;
|
||||
uint32_t value_hash;
|
||||
/* nghttp2_token value for nv.name. It could be -1 if we have no
|
||||
token for that header field name. */
|
||||
int token;
|
||||
/* Reference count */
|
||||
uint8_t ref;
|
||||
uint8_t flags;
|
||||
} nghttp2_hd_entry;
|
||||
|
||||
typedef struct {
|
||||
nghttp2_hd_entry ent;
|
||||
size_t index;
|
||||
} nghttp2_hd_static_entry;
|
||||
|
||||
typedef struct {
|
||||
nghttp2_hd_entry **buffer;
|
||||
size_t mask;
|
||||
|
@ -107,6 +164,12 @@ typedef enum {
|
|||
NGHTTP2_HD_STATE_READ_VALUE
|
||||
} nghttp2_hd_inflate_state;
|
||||
|
||||
typedef enum {
|
||||
NGHTTP2_HD_WITH_INDEXING,
|
||||
NGHTTP2_HD_WITHOUT_INDEXING,
|
||||
NGHTTP2_HD_NEVER_INDEXING
|
||||
} nghttp2_hd_indexing_mode;
|
||||
|
||||
typedef struct {
|
||||
/* dynamic header table */
|
||||
nghttp2_hd_ringbuf hd_table;
|
||||
|
@ -176,9 +239,8 @@ struct nghttp2_hd_inflater {
|
|||
* set in the |flags|, the content pointed by the |name| with length
|
||||
* |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit
|
||||
* set in the |flags|, the content pointed by the |value| with length
|
||||
* |valuelen| is copied. The |name_hash| and |value_hash| are hash
|
||||
* value for |name| and |value| respectively. The hash function is
|
||||
* defined in nghttp2_hd.c.
|
||||
* |valuelen| is copied. The |token| is enum number looked up by
|
||||
* |name|. It could be -1 if we don't have that enum value.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
|
@ -188,8 +250,7 @@ struct nghttp2_hd_inflater {
|
|||
*/
|
||||
int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name,
|
||||
size_t namelen, uint8_t *value, size_t valuelen,
|
||||
uint32_t name_hash, uint32_t value_hash,
|
||||
nghttp2_mem *mem);
|
||||
int token, nghttp2_mem *mem);
|
||||
|
||||
void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem);
|
||||
|
||||
|
@ -271,13 +332,25 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem);
|
|||
*/
|
||||
void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater);
|
||||
|
||||
/*
|
||||
* Similar to nghttp2_hd_inflate_hd(), but this takes additional
|
||||
* output parameter |token|. On successful header emission, it
|
||||
* contains nghttp2_token value for nv_out->name. It could be -1 if
|
||||
* we don't have enum value for the name. Other than that return
|
||||
* values and semantics are the same as nghttp2_hd_inflate_hd().
|
||||
*/
|
||||
ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
|
||||
nghttp2_nv *nv_out, int *inflate_flags,
|
||||
int *token, uint8_t *in, size_t inlen,
|
||||
int in_final);
|
||||
|
||||
/* For unittesting purpose */
|
||||
int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index,
|
||||
nghttp2_nv *nv, int inc_indexing);
|
||||
nghttp2_nv *nv, int indexing_mode);
|
||||
|
||||
/* For unittesting purpose */
|
||||
int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
|
||||
int inc_indexing);
|
||||
int indexing_mode);
|
||||
|
||||
/* For unittesting purpose */
|
||||
int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size);
|
||||
|
|
|
@ -29,12 +29,16 @@
|
|||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#include "nghttp2_mem.h"
|
||||
|
||||
#define nghttp2_min(A, B) ((A) < (B) ? (A) : (B))
|
||||
#define nghttp2_max(A, B) ((A) > (B) ? (A) : (B))
|
||||
|
||||
#define lstreq(A, B, N) ((sizeof((A)) - 1) == (N) && memcmp((A), (B), (N)) == 0)
|
||||
|
||||
/*
|
||||
* Copies 2 byte unsigned integer |n| in host byte order to |buf| in
|
||||
* network byte order.
|
||||
|
|
|
@ -28,11 +28,8 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int memeq(const void *a, const void *b, size_t n) {
|
||||
return memcmp(a, b, n) == 0;
|
||||
}
|
||||
|
||||
#define streq(A, B, N) ((sizeof((A)) - 1) == (N) && memeq((A), (B), (N)))
|
||||
#include "nghttp2_hd.h"
|
||||
#include "nghttp2_helper.h"
|
||||
|
||||
static char downcase(char c) {
|
||||
return 'A' <= c && c <= 'Z' ? (c - 'A' + 'a') : c;
|
||||
|
@ -50,129 +47,7 @@ static int memieq(const void *a, const void *b, size_t n) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
#define strieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N)))
|
||||
|
||||
typedef enum {
|
||||
NGHTTP2_TOKEN__AUTHORITY,
|
||||
NGHTTP2_TOKEN__METHOD,
|
||||
NGHTTP2_TOKEN__PATH,
|
||||
NGHTTP2_TOKEN__SCHEME,
|
||||
NGHTTP2_TOKEN__STATUS,
|
||||
NGHTTP2_TOKEN_CONNECTION,
|
||||
NGHTTP2_TOKEN_CONTENT_LENGTH,
|
||||
NGHTTP2_TOKEN_HOST,
|
||||
NGHTTP2_TOKEN_KEEP_ALIVE,
|
||||
NGHTTP2_TOKEN_PROXY_CONNECTION,
|
||||
NGHTTP2_TOKEN_TE,
|
||||
NGHTTP2_TOKEN_TRANSFER_ENCODING,
|
||||
NGHTTP2_TOKEN_UPGRADE,
|
||||
NGHTTP2_TOKEN_MAXIDX,
|
||||
} nghttp2_token;
|
||||
|
||||
/*
|
||||
* This function was generated by genlibtokenlookup.py. Inspired by
|
||||
* h2o header lookup. https://github.com/h2o/h2o
|
||||
*/
|
||||
static int lookup_token(const uint8_t *name, size_t namelen) {
|
||||
switch (namelen) {
|
||||
case 2:
|
||||
switch (name[1]) {
|
||||
case 'e':
|
||||
if (streq("t", name, 1)) {
|
||||
return NGHTTP2_TOKEN_TE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
switch (name[3]) {
|
||||
case 't':
|
||||
if (streq("hos", name, 3)) {
|
||||
return NGHTTP2_TOKEN_HOST;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
switch (name[4]) {
|
||||
case 'h':
|
||||
if (streq(":pat", name, 4)) {
|
||||
return NGHTTP2_TOKEN__PATH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
switch (name[6]) {
|
||||
case 'd':
|
||||
if (streq(":metho", name, 6)) {
|
||||
return NGHTTP2_TOKEN__METHOD;
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (streq(":schem", name, 6)) {
|
||||
return NGHTTP2_TOKEN__SCHEME;
|
||||
}
|
||||
if (streq("upgrad", name, 6)) {
|
||||
return NGHTTP2_TOKEN_UPGRADE;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (streq(":statu", name, 6)) {
|
||||
return NGHTTP2_TOKEN__STATUS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
switch (name[9]) {
|
||||
case 'e':
|
||||
if (streq("keep-aliv", name, 9)) {
|
||||
return NGHTTP2_TOKEN_KEEP_ALIVE;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
if (streq("connectio", name, 9)) {
|
||||
return NGHTTP2_TOKEN_CONNECTION;
|
||||
}
|
||||
break;
|
||||
case 'y':
|
||||
if (streq(":authorit", name, 9)) {
|
||||
return NGHTTP2_TOKEN__AUTHORITY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 14:
|
||||
switch (name[13]) {
|
||||
case 'h':
|
||||
if (streq("content-lengt", name, 13)) {
|
||||
return NGHTTP2_TOKEN_CONTENT_LENGTH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 16:
|
||||
switch (name[15]) {
|
||||
case 'n':
|
||||
if (streq("proxy-connectio", name, 15)) {
|
||||
return NGHTTP2_TOKEN_PROXY_CONNECTION;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 17:
|
||||
switch (name[16]) {
|
||||
case 'g':
|
||||
if (streq("transfer-encodin", name, 16)) {
|
||||
return NGHTTP2_TOKEN_TRANSFER_ENCODING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#define lstrieq(A, B, N) ((sizeof((A)) - 1) == (N) && memieq((A), (B), (N)))
|
||||
|
||||
static int64_t parse_uint(const uint8_t *s, size_t len) {
|
||||
int64_t n = 0;
|
||||
|
@ -238,9 +113,7 @@ static int check_path(nghttp2_stream *stream) {
|
|||
}
|
||||
|
||||
static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
||||
int trailer) {
|
||||
int token;
|
||||
|
||||
int token, int trailer) {
|
||||
if (nv->name[0] == ':') {
|
||||
if (trailer ||
|
||||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
|
||||
|
@ -248,8 +121,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
}
|
||||
|
||||
token = lookup_token(nv->name, nv->namelen);
|
||||
|
||||
switch (token) {
|
||||
case NGHTTP2_TOKEN__AUTHORITY:
|
||||
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
|
||||
|
@ -262,14 +133,14 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
switch (nv->valuelen) {
|
||||
case 4:
|
||||
if (streq("HEAD", nv->value, nv->valuelen)) {
|
||||
if (lstreq("HEAD", nv->value, nv->valuelen)) {
|
||||
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
switch (nv->value[6]) {
|
||||
case 'T':
|
||||
if (streq("CONNECT", nv->value, nv->valuelen)) {
|
||||
if (lstreq("CONNECT", nv->value, nv->valuelen)) {
|
||||
if (stream->stream_id % 2 == 0) {
|
||||
/* we won't allow CONNECT for push */
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
|
@ -282,7 +153,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
break;
|
||||
case 'S':
|
||||
if (streq("OPTIONS", nv->value, nv->valuelen)) {
|
||||
if (lstreq("OPTIONS", nv->value, nv->valuelen)) {
|
||||
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
|
||||
}
|
||||
break;
|
||||
|
@ -338,7 +209,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
case NGHTTP2_TOKEN_UPGRADE:
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
case NGHTTP2_TOKEN_TE:
|
||||
if (!strieq("trailers", nv->value, nv->valuelen)) {
|
||||
if (!lstrieq("trailers", nv->value, nv->valuelen)) {
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
}
|
||||
break;
|
||||
|
@ -356,9 +227,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
|
||||
static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
||||
int trailer) {
|
||||
int token;
|
||||
|
||||
int token, int trailer) {
|
||||
if (nv->name[0] == ':') {
|
||||
if (trailer ||
|
||||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
|
||||
|
@ -366,8 +235,6 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
}
|
||||
|
||||
token = lookup_token(nv->name, nv->namelen);
|
||||
|
||||
switch (token) {
|
||||
case NGHTTP2_TOKEN__STATUS: {
|
||||
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
|
||||
|
@ -400,7 +267,7 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
case NGHTTP2_TOKEN_UPGRADE:
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
case NGHTTP2_TOKEN_TE:
|
||||
if (!strieq("trailers", nv->value, nv->valuelen)) {
|
||||
if (!lstrieq("trailers", nv->value, nv->valuelen)) {
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
}
|
||||
break;
|
||||
|
@ -418,7 +285,8 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
}
|
||||
|
||||
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
|
||||
nghttp2_frame *frame, nghttp2_nv *nv, int trailer) {
|
||||
nghttp2_frame *frame, nghttp2_nv *nv, int token,
|
||||
int trailer) {
|
||||
/* We are strict for pseudo header field. One bad character should
|
||||
lead to fail. OTOH, we should be a bit forgiving for regular
|
||||
headers, since existing public internet has so much illegal
|
||||
|
@ -458,10 +326,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
|
|||
}
|
||||
|
||||
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
||||
return http_request_on_header(stream, nv, trailer);
|
||||
return http_request_on_header(stream, nv, token, trailer);
|
||||
}
|
||||
|
||||
return http_response_on_header(stream, nv, trailer);
|
||||
return http_response_on_header(stream, nv, token, trailer);
|
||||
}
|
||||
|
||||
int nghttp2_http_on_request_headers(nghttp2_stream *stream,
|
||||
|
@ -574,14 +442,15 @@ void nghttp2_http_record_request_method(nghttp2_stream *stream,
|
|||
/* TODO we should do this strictly. */
|
||||
for (i = 0; i < nvlen; ++i) {
|
||||
const nghttp2_nv *nv = &nva[i];
|
||||
if (lookup_token(nv->name, nv->namelen) != NGHTTP2_TOKEN__METHOD) {
|
||||
if (!(nv->namelen == 7 && nv->name[6] == 'd' &&
|
||||
memcmp(":metho", nv->name, nv->namelen - 1) == 0)) {
|
||||
continue;
|
||||
}
|
||||
if (streq("CONNECT", nv->value, nv->valuelen)) {
|
||||
if (lstreq("CONNECT", nv->value, nv->valuelen)) {
|
||||
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
|
||||
return;
|
||||
}
|
||||
if (streq("HEAD", nv->value, nv->valuelen)) {
|
||||
if (lstreq("HEAD", nv->value, nv->valuelen)) {
|
||||
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
/*
|
||||
* This function is called when HTTP header field |nv| in |frame| is
|
||||
* received for |stream|. This function will validate |nv| against
|
||||
* the current state of stream.
|
||||
* the current state of stream. The |token| is nghttp2_token value
|
||||
* for nv->name, or -1 if we don't have enum value for the name.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
|
@ -48,7 +49,8 @@
|
|||
* if it was not received because of compatibility reasons.
|
||||
*/
|
||||
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
|
||||
nghttp2_frame *frame, nghttp2_nv *nv, int trailer);
|
||||
nghttp2_frame *frame, nghttp2_nv *nv, int token,
|
||||
int trailer);
|
||||
|
||||
/*
|
||||
* This function is called when request header is received. This
|
||||
|
|
|
@ -51,7 +51,7 @@ typedef enum {
|
|||
* Invalid HTTP header field was received but it can be treated as
|
||||
* if it was not received because of compatibility reasons.
|
||||
*/
|
||||
NGHTTP2_ERR_IGN_HTTP_HEADER = -105,
|
||||
NGHTTP2_ERR_IGN_HTTP_HEADER = -105
|
||||
} nghttp2_internal_error;
|
||||
|
||||
#endif /* NGHTTP2_INT_H */
|
||||
|
|
|
@ -33,12 +33,12 @@
|
|||
#include "nghttp2_frame.h"
|
||||
#include "nghttp2_mem.h"
|
||||
|
||||
/* A bit higher weight for non-DATA frames */
|
||||
#define NGHTTP2_OB_EX_WEIGHT 300
|
||||
/* Higher weight for SETTINGS */
|
||||
#define NGHTTP2_OB_SETTINGS_WEIGHT 301
|
||||
/* Highest weight for PING */
|
||||
#define NGHTTP2_OB_PING_WEIGHT 302
|
||||
/* A bit higher priority for non-DATA frames */
|
||||
#define NGHTTP2_OB_EX_CYCLE 2
|
||||
/* Even more higher priority for SETTINGS frame */
|
||||
#define NGHTTP2_OB_SETTINGS_CYCLE 1
|
||||
/* Highest priority for PING frame */
|
||||
#define NGHTTP2_OB_PING_CYCLE 0
|
||||
|
||||
/* struct used for HEADERS and PUSH_PROMISE frame */
|
||||
typedef struct {
|
||||
|
@ -108,12 +108,14 @@ typedef struct {
|
|||
nghttp2_frame frame;
|
||||
nghttp2_aux_data aux_data;
|
||||
int64_t seq;
|
||||
/* Reset count of weight. See comment for last_cycle in
|
||||
nghttp2_session.h */
|
||||
/* The priority used in priority comparion. Smaller is served
|
||||
ealier. For PING, SETTINGS and non-DATA frames (excluding
|
||||
response HEADERS frame) have dedicated cycle value defined above.
|
||||
For DATA frame, cycle is computed by taking into account of
|
||||
effective weight and frame payload length previously sent, so
|
||||
that the amount of transmission is distributed across streams
|
||||
proportional to effective weight (inside a tree). */
|
||||
uint64_t cycle;
|
||||
/* The priority used in priority comparion. Larger is served
|
||||
ealier. */
|
||||
int32_t weight;
|
||||
/* nonzero if this object is queued. */
|
||||
uint8_t queued;
|
||||
} nghttp2_outbound_item;
|
||||
|
|
|
@ -230,12 +230,7 @@ static int outbound_item_compar(const void *lhsx, const void *rhsx) {
|
|||
rhs = (const nghttp2_outbound_item *)rhsx;
|
||||
|
||||
if (lhs->cycle == rhs->cycle) {
|
||||
if (lhs->weight == rhs->weight) {
|
||||
return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0);
|
||||
}
|
||||
|
||||
/* Larger weight has higher precedence */
|
||||
return rhs->weight - lhs->weight;
|
||||
return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0);
|
||||
}
|
||||
|
||||
return (lhs->cycle < rhs->cycle) ? -1 : 1;
|
||||
|
@ -369,7 +364,9 @@ static int session_new(nghttp2_session **session_ptr,
|
|||
nghttp2_stream_roots_init(&(*session_ptr)->roots);
|
||||
|
||||
(*session_ptr)->next_seq = 0;
|
||||
(*session_ptr)->last_cycle = 1;
|
||||
/* Do +1 so that any HEADERS/DATA frames are scheduled after urgent
|
||||
frames. */
|
||||
(*session_ptr)->last_cycle = NGHTTP2_OB_EX_CYCLE + 1;
|
||||
|
||||
(*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
|
||||
(*session_ptr)->recv_window_size = 0;
|
||||
|
@ -702,9 +699,7 @@ nghttp2_session_reprioritize_stream(nghttp2_session *session,
|
|||
void nghttp2_session_outbound_item_init(nghttp2_session *session,
|
||||
nghttp2_outbound_item *item) {
|
||||
item->seq = session->next_seq++;
|
||||
/* We use cycle for DATA only */
|
||||
item->cycle = 0;
|
||||
item->weight = NGHTTP2_OB_EX_WEIGHT;
|
||||
item->cycle = NGHTTP2_OB_EX_CYCLE;
|
||||
item->queued = 0;
|
||||
|
||||
memset(&item->aux_data, 0, sizeof(nghttp2_aux_data));
|
||||
|
@ -731,12 +726,12 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
|||
|
||||
break;
|
||||
case NGHTTP2_SETTINGS:
|
||||
item->weight = NGHTTP2_OB_SETTINGS_WEIGHT;
|
||||
item->cycle = NGHTTP2_OB_SETTINGS_CYCLE;
|
||||
|
||||
break;
|
||||
case NGHTTP2_PING:
|
||||
/* Ping has highest priority. */
|
||||
item->weight = NGHTTP2_OB_PING_WEIGHT;
|
||||
item->cycle = NGHTTP2_OB_PING_CYCLE;
|
||||
|
||||
break;
|
||||
default:
|
||||
|
@ -760,7 +755,6 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
|||
item->queued = 1;
|
||||
} else if (stream && (stream->state == NGHTTP2_STREAM_RESERVED ||
|
||||
item->aux_data.headers.attach_stream)) {
|
||||
item->weight = stream->effective_weight;
|
||||
item->cycle = session->last_cycle;
|
||||
|
||||
rv = nghttp2_stream_attach_item(stream, item, session);
|
||||
|
@ -798,7 +792,6 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
|||
return NGHTTP2_ERR_DATA_EXIST;
|
||||
}
|
||||
|
||||
item->weight = stream->effective_weight;
|
||||
item->cycle = session->last_cycle;
|
||||
|
||||
rv = nghttp2_stream_attach_item(stream, item, session);
|
||||
|
@ -1994,7 +1987,8 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
}
|
||||
|
||||
rv = nghttp2_session_pack_data(session, &session->aob.framebufs,
|
||||
next_readmax, frame, &item->aux_data.data);
|
||||
next_readmax, frame, &item->aux_data.data,
|
||||
stream);
|
||||
if (rv == NGHTTP2_ERR_DEFERRED) {
|
||||
rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER,
|
||||
session);
|
||||
|
@ -2077,8 +2071,7 @@ nghttp2_session_get_next_ob_item(nghttp2_session *session) {
|
|||
headers_item = nghttp2_pq_top(&session->ob_ss_pq);
|
||||
|
||||
if (session_is_outgoing_concurrent_streams_max(session) ||
|
||||
item->weight > headers_item->weight ||
|
||||
(item->weight == headers_item->weight && item->seq < headers_item->seq)) {
|
||||
outbound_item_compar(item, headers_item) < 0) {
|
||||
return item;
|
||||
}
|
||||
|
||||
|
@ -2141,8 +2134,7 @@ nghttp2_session_pop_next_ob_item(nghttp2_session *session) {
|
|||
headers_item = nghttp2_pq_top(&session->ob_ss_pq);
|
||||
|
||||
if (session_is_outgoing_concurrent_streams_max(session) ||
|
||||
item->weight > headers_item->weight ||
|
||||
(item->weight == headers_item->weight && item->seq < headers_item->seq)) {
|
||||
outbound_item_compar(item, headers_item) < 0) {
|
||||
nghttp2_pq_pop(&session->ob_pq);
|
||||
|
||||
item->queued = 0;
|
||||
|
@ -2257,21 +2249,21 @@ static int session_close_stream_on_goaway(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void session_outbound_item_cycle_weight(nghttp2_session *session,
|
||||
nghttp2_outbound_item *item,
|
||||
int32_t ini_weight) {
|
||||
if (item->weight == NGHTTP2_MIN_WEIGHT || item->weight > ini_weight) {
|
||||
static void session_outbound_item_schedule(nghttp2_session *session,
|
||||
nghttp2_outbound_item *item,
|
||||
int32_t weight) {
|
||||
/* Schedule next write. Offset proportional to the write size.
|
||||
Stream with heavier weight is scheduled earlier. */
|
||||
size_t delta = item->frame.hd.length * NGHTTP2_MAX_WEIGHT / weight;
|
||||
|
||||
item->weight = ini_weight;
|
||||
|
||||
if (item->cycle == session->last_cycle) {
|
||||
item->cycle = ++session->last_cycle;
|
||||
} else {
|
||||
item->cycle = session->last_cycle;
|
||||
}
|
||||
} else {
|
||||
--item->weight;
|
||||
if (session->last_cycle < item->cycle) {
|
||||
session->last_cycle = item->cycle;
|
||||
}
|
||||
|
||||
/* We pretend to ignore overflow given that the value range of
|
||||
item->cycle, which is uint64_t. nghttp2 won't explode even when
|
||||
overflow occurs, there might be some disturbance of priority. */
|
||||
item->cycle = session->last_cycle + delta;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2591,15 +2583,6 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
|||
assert(stream);
|
||||
next_item = nghttp2_session_get_next_ob_item(session);
|
||||
|
||||
/* Imagine we hit connection window size limit while sending DATA
|
||||
frame. If we decrement weight here, its stream might get
|
||||
inferior share because the other streams' weight is not
|
||||
decremented because of flow control. */
|
||||
if (session->remote_window_size > 0 || stream->remote_window_size <= 0) {
|
||||
session_outbound_item_cycle_weight(session, aob->item,
|
||||
stream->effective_weight);
|
||||
}
|
||||
|
||||
/* If priority of this stream is higher or equal to other stream
|
||||
waiting at the top of the queue, we continue to send this
|
||||
data. */
|
||||
|
@ -2642,7 +2625,7 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
|||
nghttp2_bufs_reset(framebufs);
|
||||
|
||||
rv = nghttp2_session_pack_data(session, framebufs, next_readmax, frame,
|
||||
aux_data);
|
||||
aux_data, stream);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -3282,6 +3265,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
|||
nghttp2_stream *stream;
|
||||
nghttp2_stream *subject_stream;
|
||||
int trailer = 0;
|
||||
int token;
|
||||
|
||||
*readlen_ptr = 0;
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
@ -3297,8 +3281,8 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
|||
DEBUGF(fprintf(stderr, "recv: decoding header block %zu bytes\n", inlen));
|
||||
for (;;) {
|
||||
inflate_flags = 0;
|
||||
proclen = nghttp2_hd_inflate_hd(&session->hd_inflater, &nv, &inflate_flags,
|
||||
in, inlen, final);
|
||||
proclen = nghttp2_hd_inflate_hd2(&session->hd_inflater, &nv, &inflate_flags,
|
||||
&token, in, inlen, final);
|
||||
if (nghttp2_is_fatal((int)proclen)) {
|
||||
return (int)proclen;
|
||||
}
|
||||
|
@ -3333,7 +3317,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
|||
if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
|
||||
rv = 0;
|
||||
if (subject_stream && session_enforce_http_messaging(session)) {
|
||||
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
|
||||
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, token,
|
||||
trailer);
|
||||
if (rv == NGHTTP2_ERR_HTTP_HEADER) {
|
||||
DEBUGF(fprintf(
|
||||
|
@ -6253,7 +6237,8 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
|
|||
|
||||
int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
|
||||
size_t datamax, nghttp2_frame *frame,
|
||||
nghttp2_data_aux_data *aux_data) {
|
||||
nghttp2_data_aux_data *aux_data,
|
||||
nghttp2_stream *stream) {
|
||||
int rv;
|
||||
uint32_t data_flags;
|
||||
ssize_t payloadlen;
|
||||
|
@ -6266,12 +6251,6 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
|
|||
buf = &bufs->cur->buf;
|
||||
|
||||
if (session->callbacks.read_length_callback) {
|
||||
nghttp2_stream *stream;
|
||||
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
if (!stream) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
payloadlen = session->callbacks.read_length_callback(
|
||||
session, frame->hd.type, stream->stream_id, session->remote_window_size,
|
||||
|
@ -6385,6 +6364,9 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
|
|||
return rv;
|
||||
}
|
||||
|
||||
session_outbound_item_schedule(session, stream->item,
|
||||
stream->effective_weight);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -6671,11 +6653,19 @@ int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id,
|
|||
|
||||
int nghttp2_session_set_next_stream_id(nghttp2_session *session,
|
||||
int32_t next_stream_id) {
|
||||
if (next_stream_id < 0 ||
|
||||
if (next_stream_id <= 0 ||
|
||||
session->next_stream_id > (uint32_t)next_stream_id) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (session->server) {
|
||||
if (next_stream_id % 2) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (next_stream_id % 2 == 0) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
session->next_stream_id = next_stream_id;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -705,7 +705,8 @@ nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session,
|
|||
*/
|
||||
int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
|
||||
size_t datamax, nghttp2_frame *frame,
|
||||
nghttp2_data_aux_data *aux_data);
|
||||
nghttp2_data_aux_data *aux_data,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
/*
|
||||
* Returns top of outbound frame queue. This function returns NULL if
|
||||
|
|
|
@ -63,7 +63,6 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
|||
stream->effective_weight = stream->weight;
|
||||
stream->sum_dep_weight = 0;
|
||||
stream->sum_norest_weight = 0;
|
||||
stream->sum_top_weight = 0;
|
||||
|
||||
stream->roots = roots;
|
||||
stream->root_prev = NULL;
|
||||
|
@ -102,11 +101,12 @@ static int stream_push_item(nghttp2_stream *stream, nghttp2_session *session) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (item->weight > stream->effective_weight) {
|
||||
item->weight = stream->effective_weight;
|
||||
}
|
||||
|
||||
item->cycle = session->last_cycle;
|
||||
/* Penalize item by delaying scheduling according to effective
|
||||
weight. This will delay low priority stream, which is good.
|
||||
OTOH, this may incur delay for high priority item. Will see. */
|
||||
item->cycle =
|
||||
session->last_cycle +
|
||||
NGHTTP2_DATA_PAYLOADLEN * NGHTTP2_MAX_WEIGHT / stream->effective_weight;
|
||||
|
||||
switch (item->frame.hd.type) {
|
||||
case NGHTTP2_DATA:
|
||||
|
@ -178,18 +178,6 @@ int32_t nghttp2_stream_dep_distributed_effective_weight(nghttp2_stream *stream,
|
|||
return nghttp2_max(1, weight);
|
||||
}
|
||||
|
||||
static int32_t
|
||||
stream_dep_distributed_top_effective_weight(nghttp2_stream *stream,
|
||||
int32_t weight) {
|
||||
if (stream->sum_top_weight == 0) {
|
||||
return stream->effective_weight;
|
||||
}
|
||||
|
||||
weight = stream->effective_weight * weight / stream->sum_top_weight;
|
||||
|
||||
return nghttp2_max(1, weight);
|
||||
}
|
||||
|
||||
static void stream_update_dep_set_rest(nghttp2_stream *stream);
|
||||
|
||||
/* Updates effective_weight of descendant streams in subtree of
|
||||
|
@ -199,10 +187,9 @@ static void stream_update_dep_effective_weight(nghttp2_stream *stream) {
|
|||
nghttp2_stream *si;
|
||||
|
||||
DEBUGF(fprintf(stderr, "stream: update_dep_effective_weight "
|
||||
"stream(%p)=%d, weight=%d, sum_norest_weight=%d, "
|
||||
"sum_top_weight=%d\n",
|
||||
"stream(%p)=%d, weight=%d, sum_norest_weight=%d\n",
|
||||
stream, stream->stream_id, stream->weight,
|
||||
stream->sum_norest_weight, stream->sum_top_weight));
|
||||
stream->sum_norest_weight));
|
||||
|
||||
/* stream->sum_norest_weight == 0 means there is no
|
||||
NGHTTP2_STREAM_DPRI_TOP under stream */
|
||||
|
@ -211,47 +198,13 @@ static void stream_update_dep_effective_weight(nghttp2_stream *stream) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* If there is no direct descendant whose dpri is
|
||||
NGHTTP2_STREAM_DPRI_TOP, indirect descendants have the chance to
|
||||
send data, so recursively set weight for descendants. */
|
||||
if (stream->sum_top_weight == 0) {
|
||||
for (si = stream->dep_next; si; si = si->sib_next) {
|
||||
if (si->dpri != NGHTTP2_STREAM_DPRI_REST) {
|
||||
si->effective_weight =
|
||||
nghttp2_stream_dep_distributed_effective_weight(stream, si->weight);
|
||||
}
|
||||
|
||||
stream_update_dep_effective_weight(si);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* If there is at least one direct descendant whose dpri is
|
||||
NGHTTP2_STREAM_DPRI_TOP, we won't give a chance to indirect
|
||||
descendants, since closed or blocked stream's weight is
|
||||
distributed among its siblings */
|
||||
for (si = stream->dep_next; si; si = si->sib_next) {
|
||||
if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
||||
if (si->dpri != NGHTTP2_STREAM_DPRI_REST) {
|
||||
si->effective_weight =
|
||||
stream_dep_distributed_top_effective_weight(stream, si->weight);
|
||||
|
||||
DEBUGF(fprintf(stderr, "stream: stream=%d top eweight=%d\n",
|
||||
si->stream_id, si->effective_weight));
|
||||
|
||||
continue;
|
||||
nghttp2_stream_dep_distributed_effective_weight(stream, si->weight);
|
||||
}
|
||||
|
||||
if (si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM) {
|
||||
DEBUGF(fprintf(stderr, "stream: stream=%d no_item, ignored\n",
|
||||
si->stream_id));
|
||||
|
||||
/* Since we marked NGHTTP2_STREAM_DPRI_TOP under si, we make
|
||||
them NGHTTP2_STREAM_DPRI_REST again. */
|
||||
stream_update_dep_set_rest(si->dep_next);
|
||||
} else {
|
||||
DEBUGF(
|
||||
fprintf(stderr, "stream: stream=%d rest, ignored\n", si->stream_id));
|
||||
}
|
||||
stream_update_dep_effective_weight(si);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,25 +300,20 @@ static int stream_update_dep_queue_top(nghttp2_stream *stream,
|
|||
}
|
||||
|
||||
/*
|
||||
* Updates stream->sum_norest_weight and stream->sum_top_weight
|
||||
* recursively. We have to gather effective sum of weight of
|
||||
* descendants. If stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM, we
|
||||
* have to go deeper and check that any of its descendants has dpri
|
||||
* value of NGHTTP2_STREAM_DPRI_TOP. If so, we have to add weight of
|
||||
* its direct descendants to stream->sum_norest_weight. To make this
|
||||
* work, this function returns 1 if any of its descendants has dpri
|
||||
* value of NGHTTP2_STREAM_DPRI_TOP, otherwise 0.
|
||||
*
|
||||
* Calculating stream->sum_top-weight is very simple compared to
|
||||
* stream->sum_norest_weight. It just adds up the weight of direct
|
||||
* descendants whose dpri is NGHTTP2_STREAM_DPRI_TOP.
|
||||
* Updates stream->sum_norest_weight recursively. We have to gather
|
||||
* effective sum of weight of descendants. If stream->dpri ==
|
||||
* NGHTTP2_STREAM_DPRI_NO_ITEM, we have to go deeper and check that
|
||||
* any of its descendants has dpri value of NGHTTP2_STREAM_DPRI_TOP.
|
||||
* If so, we have to add weight of its direct descendants to
|
||||
* stream->sum_norest_weight. To make this work, this function
|
||||
* returns 1 if any of its descendants has dpri value of
|
||||
* NGHTTP2_STREAM_DPRI_TOP, otherwise 0.
|
||||
*/
|
||||
static int stream_update_dep_sum_norest_weight(nghttp2_stream *stream) {
|
||||
nghttp2_stream *si;
|
||||
int rv;
|
||||
|
||||
stream->sum_norest_weight = 0;
|
||||
stream->sum_top_weight = 0;
|
||||
|
||||
if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
||||
return 1;
|
||||
|
@ -383,10 +331,6 @@ static int stream_update_dep_sum_norest_weight(nghttp2_stream *stream) {
|
|||
rv = 1;
|
||||
stream->sum_norest_weight += si->weight;
|
||||
}
|
||||
|
||||
if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
|
||||
stream->sum_top_weight += si->weight;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
|
|
@ -217,9 +217,6 @@ struct nghttp2_stream {
|
|||
descendant with dpri == NGHTTP2_STREAM_DPRI_TOP. We use this
|
||||
value to calculate effective weight. */
|
||||
int32_t sum_norest_weight;
|
||||
/* sum of weight of direct descendants whose dpri value is
|
||||
NGHTTP2_STREAM_DPRI_TOP */
|
||||
int32_t sum_top_weight;
|
||||
nghttp2_stream_state state;
|
||||
/* status code from remote server */
|
||||
int16_t status_code;
|
||||
|
|
|
@ -10,39 +10,17 @@
|
|||
from __future__ import unicode_literals
|
||||
import re, sys
|
||||
|
||||
def hash(s):
|
||||
h = 0
|
||||
for c in s:
|
||||
h = h * 31 + ord(c)
|
||||
return h & ((1 << 32) - 1)
|
||||
|
||||
entries = []
|
||||
for line in sys.stdin:
|
||||
m = re.match(r'(\d+)\s+(\S+)\s+(\S.*)?', line)
|
||||
val = m.group(3).strip() if m.group(3) else ''
|
||||
entries.append((hash(m.group(2)), int(m.group(1)), m.group(2), val))
|
||||
|
||||
entries.sort()
|
||||
|
||||
print '/* Sorted by hash(name) and its table index */'
|
||||
print 'static nghttp2_hd_static_entry static_table[] = {'
|
||||
for ent in entries:
|
||||
print 'MAKE_STATIC_ENT({}, "{}", "{}", {}u, {}u),'\
|
||||
.format(ent[1] - 1, ent[2], ent[3], ent[0], hash(ent[3]))
|
||||
print '};'
|
||||
|
||||
print ''
|
||||
|
||||
print '/* Index to the position in static_table */'
|
||||
print 'const size_t static_table_index[] = {'
|
||||
for i in range(len(entries)):
|
||||
for j, ent in enumerate(entries):
|
||||
if ent[1] - 1 == i:
|
||||
sys.stdout.write('{: <2d},'.format(j))
|
||||
break
|
||||
if (i + 1) % 16 == 0:
|
||||
sys.stdout.write('\n')
|
||||
else:
|
||||
sys.stdout.write(' ')
|
||||
entries.append((int(m.group(1)), m.group(2), val))
|
||||
|
||||
print 'static nghttp2_hd_entry static_table[] = {'
|
||||
idx = 0
|
||||
for i, ent in enumerate(entries):
|
||||
if entries[idx][1] != ent[1]:
|
||||
idx = i
|
||||
print 'MAKE_STATIC_ENT("{}", "{}", {}),'\
|
||||
.format(ent[1], ent[2], entries[idx][0] - 1)
|
||||
print '};'
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
|
||||
namespace nghttp2 {
|
||||
|
||||
ParserData::ParserData(const std::string &base_uri) : base_uri(base_uri) {}
|
||||
ParserData::ParserData(const std::string &base_uri)
|
||||
: base_uri(base_uri), inside_head(0) {}
|
||||
|
||||
HtmlParser::HtmlParser(const std::string &base_uri)
|
||||
: base_uri_(base_uri), parser_ctx_(nullptr), parser_data_(base_uri) {}
|
||||
|
@ -52,13 +53,13 @@ const char *get_attr(const xmlChar **attrs, const char *name) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void add_link(ParserData *parser_data, const char *uri, RequestPriority pri) {
|
||||
void add_link(ParserData *parser_data, const char *uri, ResourceType res_type) {
|
||||
auto u = xmlBuildURI(
|
||||
reinterpret_cast<const xmlChar *>(uri),
|
||||
reinterpret_cast<const xmlChar *>(parser_data->base_uri.c_str()));
|
||||
if (u) {
|
||||
parser_data->links.push_back(
|
||||
std::make_pair(reinterpret_cast<char *>(u), pri));
|
||||
std::make_pair(reinterpret_cast<char *>(u), res_type));
|
||||
free(u);
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +69,9 @@ namespace {
|
|||
void start_element_func(void *user_data, const xmlChar *name,
|
||||
const xmlChar **attrs) {
|
||||
auto parser_data = static_cast<ParserData *>(user_data);
|
||||
if (util::strieq(reinterpret_cast<const char *>(name), "head")) {
|
||||
++parser_data->inside_head;
|
||||
}
|
||||
if (util::strieq(reinterpret_cast<const char *>(name), "link")) {
|
||||
auto rel_attr = get_attr(attrs, "rel");
|
||||
auto href_attr = get_attr(attrs, "href");
|
||||
|
@ -75,22 +79,35 @@ void start_element_func(void *user_data, const xmlChar *name,
|
|||
return;
|
||||
}
|
||||
if (util::strieq(rel_attr, "shortcut icon")) {
|
||||
add_link(parser_data, href_attr, REQ_PRI_LOWEST);
|
||||
add_link(parser_data, href_attr, REQ_OTHERS);
|
||||
} else if (util::strieq(rel_attr, "stylesheet")) {
|
||||
add_link(parser_data, href_attr, REQ_PRI_MEDIUM);
|
||||
add_link(parser_data, href_attr, REQ_CSS);
|
||||
}
|
||||
} else if (util::strieq(reinterpret_cast<const char *>(name), "img")) {
|
||||
auto src_attr = get_attr(attrs, "src");
|
||||
if (!src_attr) {
|
||||
return;
|
||||
}
|
||||
add_link(parser_data, src_attr, REQ_PRI_LOWEST);
|
||||
add_link(parser_data, src_attr, REQ_IMG);
|
||||
} else if (util::strieq(reinterpret_cast<const char *>(name), "script")) {
|
||||
auto src_attr = get_attr(attrs, "src");
|
||||
if (!src_attr) {
|
||||
return;
|
||||
}
|
||||
add_link(parser_data, src_attr, REQ_PRI_LOW);
|
||||
if (parser_data->inside_head) {
|
||||
add_link(parser_data, src_attr, REQ_JS);
|
||||
} else {
|
||||
add_link(parser_data, src_attr, REQ_UNBLOCK_JS);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void end_element_func(void *user_data, const xmlChar *name) {
|
||||
auto parser_data = static_cast<ParserData *>(user_data);
|
||||
if (util::strieq(reinterpret_cast<const char *>(name), "head")) {
|
||||
--parser_data->inside_head;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -112,7 +129,7 @@ xmlSAXHandler saxHandler = {
|
|||
nullptr, // startDocumentSAXFunc
|
||||
nullptr, // endDocumentSAXFunc
|
||||
&start_element_func, // startElementSAXFunc
|
||||
nullptr, // endElementSAXFunc
|
||||
&end_element_func, // endElementSAXFunc
|
||||
nullptr, // referenceSAXFunc
|
||||
nullptr, // charactersSAXFunc
|
||||
nullptr, // ignorableWhitespaceSAXFunc
|
||||
|
@ -160,7 +177,7 @@ int HtmlParser::parse_chunk_internal(const char *chunk, size_t size, int fin) {
|
|||
}
|
||||
}
|
||||
|
||||
const std::vector<std::pair<std::string, RequestPriority>> &
|
||||
const std::vector<std::pair<std::string, ResourceType>> &
|
||||
HtmlParser::get_links() const {
|
||||
return parser_data_.links;
|
||||
}
|
||||
|
|
|
@ -38,16 +38,19 @@
|
|||
|
||||
namespace nghttp2 {
|
||||
|
||||
enum RequestPriority {
|
||||
REQ_PRI_HIGH = 0,
|
||||
REQ_PRI_MEDIUM = 1,
|
||||
REQ_PRI_LOW = 2,
|
||||
REQ_PRI_LOWEST = 3
|
||||
enum ResourceType {
|
||||
REQ_CSS = 1,
|
||||
REQ_JS,
|
||||
REQ_UNBLOCK_JS,
|
||||
REQ_IMG,
|
||||
REQ_OTHERS,
|
||||
};
|
||||
|
||||
struct ParserData {
|
||||
std::string base_uri;
|
||||
std::vector<std::pair<std::string, RequestPriority>> links;
|
||||
std::vector<std::pair<std::string, ResourceType>> links;
|
||||
// > 0 if we are inside "head" element.
|
||||
int inside_head;
|
||||
ParserData(const std::string &base_uri);
|
||||
};
|
||||
|
||||
|
@ -58,7 +61,7 @@ public:
|
|||
HtmlParser(const std::string &base_uri);
|
||||
~HtmlParser();
|
||||
int parse_chunk(const char *chunk, size_t size, int fin);
|
||||
const std::vector<std::pair<std::string, RequestPriority>> &get_links() const;
|
||||
const std::vector<std::pair<std::string, ResourceType>> &get_links() const;
|
||||
void clear_links();
|
||||
|
||||
private:
|
||||
|
@ -75,14 +78,13 @@ class HtmlParser {
|
|||
public:
|
||||
HtmlParser(const std::string &base_uri) {}
|
||||
int parse_chunk(const char *chunk, size_t size, int fin) { return 0; }
|
||||
const std::vector<std::pair<std::string, RequestPriority>> &
|
||||
get_links() const {
|
||||
const std::vector<std::pair<std::string, ResourceType>> &get_links() const {
|
||||
return links_;
|
||||
}
|
||||
void clear_links() {}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::string, RequestPriority>> links_;
|
||||
std::vector<std::pair<std::string, ResourceType>> links_;
|
||||
};
|
||||
|
||||
#endif // !HAVE_LIBXML2
|
||||
|
|
|
@ -57,11 +57,6 @@
|
|||
namespace nghttp2 {
|
||||
|
||||
namespace {
|
||||
const std::string STATUS_200 = "200";
|
||||
const std::string STATUS_301 = "301";
|
||||
const std::string STATUS_304 = "304";
|
||||
const std::string STATUS_400 = "400";
|
||||
const std::string STATUS_404 = "404";
|
||||
const std::string DEFAULT_HTML = "index.html";
|
||||
const std::string NGHTTPD_SERVER = "nghttpd nghttp2/" NGHTTP2_VERSION;
|
||||
} // namespace
|
||||
|
@ -167,9 +162,10 @@ void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
|
|||
|
||||
class Sessions {
|
||||
public:
|
||||
Sessions(struct ev_loop *loop, const Config *config, SSL_CTX *ssl_ctx)
|
||||
: loop_(loop), config_(config), ssl_ctx_(ssl_ctx), callbacks_(nullptr),
|
||||
next_session_id_(1), tstamp_cached_(ev_now(loop)),
|
||||
Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
|
||||
SSL_CTX *ssl_ctx)
|
||||
: sv_(sv), loop_(loop), config_(config), ssl_ctx_(ssl_ctx),
|
||||
callbacks_(nullptr), next_session_id_(1), tstamp_cached_(ev_now(loop)),
|
||||
cached_date_(util::http_date(tstamp_cached_)) {
|
||||
nghttp2_session_callbacks_new(&callbacks_);
|
||||
|
||||
|
@ -240,9 +236,37 @@ public:
|
|||
}
|
||||
return cached_date_;
|
||||
}
|
||||
FileEntry *get_cached_fd(const std::string &path) {
|
||||
auto i = fd_cache_.find(path);
|
||||
if (i == std::end(fd_cache_)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto &ent = (*i).second;
|
||||
++ent.usecount;
|
||||
return &ent;
|
||||
}
|
||||
FileEntry *cache_fd(const std::string &path, const FileEntry &ent) {
|
||||
auto rv = fd_cache_.emplace(path, ent);
|
||||
return &(*rv.first).second;
|
||||
}
|
||||
void release_fd(const std::string &path) {
|
||||
auto i = fd_cache_.find(path);
|
||||
if (i == std::end(fd_cache_)) {
|
||||
return;
|
||||
}
|
||||
auto &ent = (*i).second;
|
||||
if (--ent.usecount == 0) {
|
||||
close(ent.fd);
|
||||
fd_cache_.erase(i);
|
||||
}
|
||||
}
|
||||
const HttpServer *get_server() const { return sv_; }
|
||||
|
||||
private:
|
||||
std::set<Http2Handler *> handlers_;
|
||||
// cache for file descriptors to read file.
|
||||
std::map<std::string, FileEntry> fd_cache_;
|
||||
HttpServer *sv_;
|
||||
struct ev_loop *loop_;
|
||||
const Config *config_;
|
||||
SSL_CTX *ssl_ctx_;
|
||||
|
@ -253,7 +277,8 @@ private:
|
|||
};
|
||||
|
||||
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
||||
: handler(handler), body_left(0), stream_id(stream_id), file(-1) {
|
||||
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
|
||||
stream_id(stream_id) {
|
||||
auto config = handler->get_config();
|
||||
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
|
||||
ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
|
||||
|
@ -266,8 +291,9 @@ Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
|||
}
|
||||
|
||||
Stream::~Stream() {
|
||||
if (file != -1) {
|
||||
close(file);
|
||||
if (file_ent != nullptr) {
|
||||
auto sessions = handler->get_sessions();
|
||||
sessions->release_fd(file_ent->path);
|
||||
}
|
||||
|
||||
auto loop = handler->get_loop();
|
||||
|
@ -830,12 +856,12 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
auto hd = static_cast<Http2Handler *>(user_data);
|
||||
auto stream = hd->get_stream(stream_id);
|
||||
|
||||
size_t nread = std::min(stream->body_left, static_cast<int64_t>(length));
|
||||
auto nread = std::min(stream->body_length - stream->body_offset,
|
||||
static_cast<int64_t>(length));
|
||||
|
||||
*data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
|
||||
|
||||
stream->body_left -= nread;
|
||||
if (nread == 0 || stream->body_left <= 0) {
|
||||
if (nread == 0 || stream->body_length == stream->body_offset + nread) {
|
||||
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||
|
||||
auto config = hd->get_config();
|
||||
|
@ -867,59 +893,27 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
}
|
||||
|
||||
namespace {
|
||||
void prepare_status_response(Stream *stream, Http2Handler *hd,
|
||||
const std::string &status) {
|
||||
int pipefd[2];
|
||||
if (status == STATUS_304 || pipe(pipefd) == -1) {
|
||||
hd->submit_response(status, stream->stream_id, 0);
|
||||
return;
|
||||
}
|
||||
std::string body;
|
||||
body.reserve(256);
|
||||
body = "<html><head><title>";
|
||||
body += status;
|
||||
body += "</title></head><body><h1>";
|
||||
body += status;
|
||||
body += "</h1><hr><address>";
|
||||
body += NGHTTPD_SERVER;
|
||||
body += " at port ";
|
||||
body += util::utos(hd->get_config()->port);
|
||||
body += "</address>";
|
||||
body += "</body></html>";
|
||||
void prepare_status_response(Stream *stream, Http2Handler *hd, int status) {
|
||||
auto sessions = hd->get_sessions();
|
||||
auto status_page = sessions->get_server()->get_status_page(status);
|
||||
auto file_ent = &status_page->file_ent;
|
||||
|
||||
// we don't set stream->file_ent since we don't want to expire it.
|
||||
stream->body_length = file_ent->length;
|
||||
nghttp2_data_provider data_prd;
|
||||
data_prd.source.fd = file_ent->fd;
|
||||
data_prd.read_callback = file_read_callback;
|
||||
|
||||
Headers headers;
|
||||
if (hd->get_config()->error_gzip) {
|
||||
gzFile write_fd = gzdopen(pipefd[1], "w");
|
||||
gzwrite(write_fd, body.c_str(), body.size());
|
||||
gzclose(write_fd);
|
||||
headers.emplace_back("content-encoding", "gzip");
|
||||
} else {
|
||||
ssize_t rv;
|
||||
|
||||
while ((rv = write(pipefd[1], body.c_str(), body.size())) == -1 &&
|
||||
errno == EINTR)
|
||||
;
|
||||
|
||||
if (rv != static_cast<ssize_t>(body.size())) {
|
||||
std::cerr << "Could not write all response body: " << rv << std::endl;
|
||||
}
|
||||
}
|
||||
close(pipefd[1]);
|
||||
|
||||
stream->file = pipefd[0];
|
||||
stream->body_left = body.size();
|
||||
nghttp2_data_provider data_prd;
|
||||
data_prd.source.fd = pipefd[0];
|
||||
data_prd.read_callback = file_read_callback;
|
||||
headers.emplace_back("content-type", "text/html; charset=UTF-8");
|
||||
hd->submit_response(status, stream->stream_id, headers, &data_prd);
|
||||
hd->submit_response(status_page->status, stream->stream_id, headers,
|
||||
&data_prd);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void prepare_redirect_response(Stream *stream, Http2Handler *hd,
|
||||
const std::string &path,
|
||||
const std::string &status) {
|
||||
const std::string &path, int status) {
|
||||
auto scheme =
|
||||
http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers);
|
||||
auto authority =
|
||||
|
@ -936,7 +930,10 @@ void prepare_redirect_response(Stream *stream, Http2Handler *hd,
|
|||
|
||||
auto headers = Headers{{"location", redirect_url}};
|
||||
|
||||
hd->submit_response(status, stream->stream_id, headers, nullptr);
|
||||
auto sessions = hd->get_sessions();
|
||||
auto status_page = sessions->get_server()->get_status_page(status);
|
||||
|
||||
hd->submit_response(status_page->status, stream->stream_id, headers, nullptr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -970,7 +967,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
|
|||
|
||||
url = util::percentDecode(url.begin(), url.end());
|
||||
if (!util::check_path(url)) {
|
||||
prepare_status_response(stream, hd, STATUS_404);
|
||||
prepare_status_response(stream, hd, 404);
|
||||
return;
|
||||
}
|
||||
auto push_itr = hd->get_config()->push.find(url);
|
||||
|
@ -987,51 +984,66 @@ void prepare_response(Stream *stream, Http2Handler *hd,
|
|||
if (path[path.size() - 1] == '/') {
|
||||
path += DEFAULT_HTML;
|
||||
}
|
||||
int file = open(path.c_str(), O_RDONLY | O_BINARY);
|
||||
if (file == -1) {
|
||||
prepare_status_response(stream, hd, STATUS_404);
|
||||
|
||||
return;
|
||||
}
|
||||
auto sessions = hd->get_sessions();
|
||||
auto file_ent = sessions->get_cached_fd(path);
|
||||
|
||||
struct stat buf;
|
||||
if (file_ent == nullptr) {
|
||||
int file = open(path.c_str(), O_RDONLY | O_BINARY);
|
||||
if (file == -1) {
|
||||
prepare_status_response(stream, hd, 404);
|
||||
|
||||
if (fstat(file, &buf) == -1) {
|
||||
close(file);
|
||||
prepare_status_response(stream, hd, STATUS_404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.st_mode & S_IFDIR) {
|
||||
close(file);
|
||||
|
||||
if (query_pos == std::string::npos) {
|
||||
reqpath += "/";
|
||||
} else {
|
||||
reqpath.insert(query_pos, "/");
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_redirect_response(stream, hd, reqpath, STATUS_301);
|
||||
struct stat buf;
|
||||
|
||||
if (fstat(file, &buf) == -1) {
|
||||
close(file);
|
||||
prepare_status_response(stream, hd, 404);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.st_mode & S_IFDIR) {
|
||||
close(file);
|
||||
|
||||
if (query_pos == std::string::npos) {
|
||||
reqpath += "/";
|
||||
} else {
|
||||
reqpath.insert(query_pos, "/");
|
||||
}
|
||||
|
||||
prepare_redirect_response(stream, hd, reqpath, 301);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (last_mod_found && buf.st_mtime <= last_mod) {
|
||||
close(file);
|
||||
prepare_status_response(stream, hd, 304);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
file_ent = sessions->cache_fd(
|
||||
path, FileEntry(path, buf.st_size, buf.st_mtime, file));
|
||||
} else if (last_mod_found && file_ent->mtime <= last_mod) {
|
||||
sessions->release_fd(file_ent->path);
|
||||
prepare_status_response(stream, hd, 304);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stream->file = file;
|
||||
stream->body_left = buf.st_size;
|
||||
stream->file_ent = file_ent;
|
||||
stream->body_length = file_ent->length;
|
||||
|
||||
nghttp2_data_provider data_prd;
|
||||
|
||||
data_prd.source.fd = file;
|
||||
data_prd.source.fd = file_ent->fd;
|
||||
data_prd.read_callback = file_read_callback;
|
||||
|
||||
if (last_mod_found && buf.st_mtime <= last_mod) {
|
||||
prepare_status_response(stream, hd, STATUS_304);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
hd->submit_file_response(STATUS_200, stream, buf.st_mtime, buf.st_size,
|
||||
hd->submit_file_response("200", stream, file_ent->mtime, file_ent->length,
|
||||
&data_prd);
|
||||
}
|
||||
} // namespace
|
||||
|
@ -1215,6 +1227,7 @@ int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
|
|||
auto hd = static_cast<Http2Handler *>(user_data);
|
||||
auto wb = hd->get_wb();
|
||||
auto padlen = frame->data.padlen;
|
||||
auto stream = hd->get_stream(frame->hd.stream_id);
|
||||
|
||||
if (wb->wleft() < 9 + length + padlen) {
|
||||
return NGHTTP2_ERR_WOULDBLOCK;
|
||||
|
@ -1232,17 +1245,18 @@ int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
|
|||
|
||||
while (length) {
|
||||
ssize_t nread;
|
||||
while ((nread = read(fd, p, length)) == -1 && errno == EINTR)
|
||||
while ((nread = pread(fd, p, length, stream->body_offset)) == -1 &&
|
||||
errno == EINTR)
|
||||
;
|
||||
|
||||
if (nread == -1) {
|
||||
auto stream = hd->get_stream(frame->hd.stream_id);
|
||||
remove_stream_read_timeout(stream);
|
||||
remove_stream_write_timeout(stream);
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
stream->body_offset += nread;
|
||||
length -= nread;
|
||||
p += nread;
|
||||
}
|
||||
|
@ -1375,7 +1389,7 @@ void run_worker(Worker *worker) {
|
|||
|
||||
class AcceptHandler {
|
||||
public:
|
||||
AcceptHandler(Sessions *sessions, const Config *config)
|
||||
AcceptHandler(HttpServer *sv, Sessions *sessions, const Config *config)
|
||||
: sessions_(sessions), config_(config), next_worker_(0) {
|
||||
if (config_->num_worker == 1) {
|
||||
return;
|
||||
|
@ -1387,7 +1401,7 @@ public:
|
|||
auto worker = make_unique<Worker>();
|
||||
auto loop = ev_loop_new(0);
|
||||
worker->sessions =
|
||||
make_unique<Sessions>(loop, config_, sessions_->get_ssl_ctx());
|
||||
make_unique<Sessions>(sv, loop, config_, sessions_->get_ssl_ctx());
|
||||
ev_async_init(&worker->w, worker_acceptcb);
|
||||
worker->w.data = worker.get();
|
||||
ev_async_start(loop, &worker->w);
|
||||
|
@ -1471,7 +1485,61 @@ void acceptcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
HttpServer::HttpServer(const Config *config) : config_(config) {}
|
||||
namespace {
|
||||
FileEntry make_status_body(int status, uint16_t port) {
|
||||
std::string body;
|
||||
body = "<html><head><title>";
|
||||
body += http2::get_status_string(status);
|
||||
body += "</title></head><body><h1>";
|
||||
body += http2::get_status_string(status);
|
||||
body += "</h1><hr><address>";
|
||||
body += NGHTTPD_SERVER;
|
||||
body += " at port ";
|
||||
body += util::utos(port);
|
||||
body += "</address>";
|
||||
body += "</body></html>";
|
||||
|
||||
char tempfn[] = "/tmp/nghttpd.temp.XXXXXX";
|
||||
int fd = mkstemp(tempfn);
|
||||
if (fd == -1) {
|
||||
auto error = errno;
|
||||
std::cerr << "Could not open status response body file: errno=" << error;
|
||||
assert(0);
|
||||
}
|
||||
unlink(tempfn);
|
||||
ssize_t nwrite;
|
||||
while ((nwrite = write(fd, body.c_str(), body.size())) == -1 &&
|
||||
errno == EINTR)
|
||||
;
|
||||
if (nwrite == -1) {
|
||||
auto error = errno;
|
||||
std::cerr << "Could not write status response body into file: errno="
|
||||
<< error;
|
||||
assert(0);
|
||||
}
|
||||
|
||||
return FileEntry(util::utos(status), nwrite, 0, fd);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// index into HttpServer::status_pages_
|
||||
enum {
|
||||
IDX_200,
|
||||
IDX_301,
|
||||
IDX_304,
|
||||
IDX_400,
|
||||
IDX_404,
|
||||
};
|
||||
|
||||
HttpServer::HttpServer(const Config *config) : config_(config) {
|
||||
status_pages_ = std::vector<StatusPage>{
|
||||
{"200", make_status_body(200, config_->port)},
|
||||
{"301", make_status_body(301, config_->port)},
|
||||
{"304", make_status_body(304, config_->port)},
|
||||
{"400", make_status_body(400, config_->port)},
|
||||
{"404", make_status_body(404, config_->port)},
|
||||
};
|
||||
}
|
||||
|
||||
namespace {
|
||||
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
|
||||
|
@ -1492,14 +1560,14 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
int start_listen(struct ev_loop *loop, Sessions *sessions,
|
||||
int start_listen(HttpServer *sv, struct ev_loop *loop, Sessions *sessions,
|
||||
const Config *config) {
|
||||
addrinfo hints;
|
||||
int r;
|
||||
bool ok = false;
|
||||
const char *addr = nullptr;
|
||||
|
||||
auto acceptor = std::make_shared<AcceptHandler>(sessions, config);
|
||||
auto acceptor = std::make_shared<AcceptHandler>(sv, sessions, config);
|
||||
auto service = util::utos(config->port);
|
||||
|
||||
memset(&hints, 0, sizeof(addrinfo));
|
||||
|
@ -1694,8 +1762,8 @@ int HttpServer::run() {
|
|||
|
||||
auto loop = EV_DEFAULT;
|
||||
|
||||
Sessions sessions(loop, config_, ssl_ctx);
|
||||
if (start_listen(loop, &sessions, config_) != 0) {
|
||||
Sessions sessions(this, loop, config_, ssl_ctx);
|
||||
if (start_listen(this, loop, &sessions, config_) != 0) {
|
||||
std::cerr << "Could not listen" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
@ -1706,4 +1774,22 @@ int HttpServer::run() {
|
|||
|
||||
const Config *HttpServer::get_config() const { return config_; }
|
||||
|
||||
const StatusPage *HttpServer::get_status_page(int status) const {
|
||||
switch (status) {
|
||||
case 200:
|
||||
return &status_pages_[IDX_200];
|
||||
case 301:
|
||||
return &status_pages_[IDX_301];
|
||||
case 304:
|
||||
return &status_pages_[IDX_304];
|
||||
case 400:
|
||||
return &status_pages_[IDX_400];
|
||||
case 404:
|
||||
return &status_pages_[IDX_404];
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace nghttp2
|
||||
|
|
|
@ -77,14 +77,27 @@ struct Config {
|
|||
|
||||
class Http2Handler;
|
||||
|
||||
struct FileEntry {
|
||||
FileEntry(std::string path, int64_t length, int64_t mtime, int fd)
|
||||
: path(std::move(path)), length(length), mtime(mtime), dlprev(nullptr),
|
||||
dlnext(nullptr), fd(fd), usecount(1) {}
|
||||
std::string path;
|
||||
int64_t length;
|
||||
int64_t mtime;
|
||||
FileEntry *dlprev, *dlnext;
|
||||
int fd;
|
||||
int usecount;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
Headers headers;
|
||||
Http2Handler *handler;
|
||||
FileEntry *file_ent;
|
||||
ev_timer rtimer;
|
||||
ev_timer wtimer;
|
||||
int64_t body_left;
|
||||
int64_t body_length;
|
||||
int64_t body_offset;
|
||||
int32_t stream_id;
|
||||
int file;
|
||||
http2::HeaderIndex hdidx;
|
||||
Stream(Http2Handler *handler, int32_t stream_id);
|
||||
~Stream();
|
||||
|
@ -159,14 +172,21 @@ private:
|
|||
int fd_;
|
||||
};
|
||||
|
||||
struct StatusPage {
|
||||
std::string status;
|
||||
FileEntry file_ent;
|
||||
};
|
||||
|
||||
class HttpServer {
|
||||
public:
|
||||
HttpServer(const Config *config);
|
||||
int listen();
|
||||
int run();
|
||||
const Config *get_config() const;
|
||||
const StatusPage *get_status_page(int status) const;
|
||||
|
||||
private:
|
||||
std::vector<StatusPage> status_pages_;
|
||||
const Config *config_;
|
||||
};
|
||||
|
||||
|
|
|
@ -85,10 +85,7 @@ void session_impl::connected(tcp::resolver::iterator endpoint_it) {
|
|||
}
|
||||
|
||||
void session_impl::not_connected(const boost::system::error_code &ec) {
|
||||
auto &error_cb = on_error();
|
||||
if (error_cb) {
|
||||
error_cb(ec);
|
||||
}
|
||||
call_error_cb(ec);
|
||||
}
|
||||
|
||||
void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
|
||||
|
@ -99,6 +96,14 @@ const connect_cb &session_impl::on_connect() const { return connect_cb_; }
|
|||
|
||||
const error_cb &session_impl::on_error() const { return error_cb_; }
|
||||
|
||||
void session_impl::call_error_cb(const boost::system::error_code &ec) {
|
||||
auto &error_cb = on_error();
|
||||
if (!error_cb) {
|
||||
return;
|
||||
}
|
||||
error_cb(ec);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int on_begin_headers_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data) {
|
||||
|
@ -304,10 +309,7 @@ bool session_impl::setup_session() {
|
|||
|
||||
auto rv = nghttp2_session_client_new(&session_, callbacks, this);
|
||||
if (rv != 0) {
|
||||
auto &error_cb = on_error();
|
||||
if (error_cb) {
|
||||
error_cb(make_error_code(static_cast<nghttp2_error>(rv)));
|
||||
}
|
||||
call_error_cb(make_error_code(static_cast<nghttp2_error>(rv)));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -524,6 +526,7 @@ void session_impl::do_read() {
|
|||
std::size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
if (ec.value() == boost::asio::error::operation_aborted) {
|
||||
call_error_cb(ec);
|
||||
shutdown_socket();
|
||||
}
|
||||
return;
|
||||
|
@ -536,6 +539,8 @@ void session_impl::do_read() {
|
|||
nghttp2_session_mem_recv(session_, rb_.data(), bytes_transferred);
|
||||
|
||||
if (rv != static_cast<ssize_t>(bytes_transferred)) {
|
||||
call_error_cb(make_error_code(
|
||||
static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
|
||||
shutdown_socket();
|
||||
return;
|
||||
}
|
||||
|
@ -573,6 +578,7 @@ void session_impl::do_write() {
|
|||
const uint8_t *data;
|
||||
auto n = nghttp2_session_mem_send(session_, &data);
|
||||
if (n < 0) {
|
||||
call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
|
||||
shutdown_socket();
|
||||
return;
|
||||
}
|
||||
|
@ -602,6 +608,8 @@ void session_impl::do_write() {
|
|||
|
||||
write_socket([this](const boost::system::error_code &ec, std::size_t n) {
|
||||
if (ec) {
|
||||
call_error_cb(ec);
|
||||
shutdown_socket();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ protected:
|
|||
private:
|
||||
bool should_stop() const;
|
||||
bool setup_session();
|
||||
void call_error_cb(const boost::system::error_code &ec);
|
||||
|
||||
boost::asio::io_service &io_service_;
|
||||
tcp::resolver resolver_;
|
||||
|
|
|
@ -57,6 +57,9 @@ json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value,
|
|||
size_t valuelen) {
|
||||
json_t *nv_pair = json_object();
|
||||
char *cname = malloc(namelen + 1);
|
||||
if (cname == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(cname, name, namelen);
|
||||
cname[namelen] = '\0';
|
||||
json_object_set_new(nv_pair, cname, json_pack("s#", value, valuelen));
|
||||
|
|
|
@ -119,7 +119,7 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
auto config = client->worker->config;
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||
|
||||
assert(req_stat);
|
||||
ssize_t nread;
|
||||
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||
-1 &&
|
||||
|
|
63
src/http2.cc
63
src/http2.cc
|
@ -718,6 +718,30 @@ InputIt skip_to_right_dquote(InputIt first, InputIt last) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
// Returns true if link-param does not match pattern |pat| of length
|
||||
// |patlen| or it has empty value (""). |pat| should be parmname
|
||||
// followed by "=".
|
||||
bool check_link_param_empty(const char *first, const char *last,
|
||||
const char *pat, size_t patlen) {
|
||||
if (first + patlen <= last) {
|
||||
if (std::equal(pat, pat + patlen, first, util::CaseCmp())) {
|
||||
// we only accept URI if pat is followd by "" (e.g.,
|
||||
// loadpolicy="") here.
|
||||
if (first + patlen + 2 <= last) {
|
||||
if (*(first + patlen) != '"' || *(first + patlen + 1) != '"') {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// here we got invalid production (anchor=") or anchor=?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::pair<LinkHeader, const char *>
|
||||
parse_next_link_header_once(const char *first, const char *last) {
|
||||
|
@ -755,11 +779,11 @@ parse_next_link_header_once(const char *first, const char *last) {
|
|||
// we expect link-param
|
||||
|
||||
// rel can take several relations using quoted form.
|
||||
static const char PLP[] = "rel=\"";
|
||||
static const size_t PLPLEN = sizeof(PLP) - 1;
|
||||
static constexpr char PLP[] = "rel=\"";
|
||||
static constexpr size_t PLPLEN = sizeof(PLP) - 1;
|
||||
|
||||
static const char PLT[] = "preload";
|
||||
static const size_t PLTLEN = sizeof(PLT) - 1;
|
||||
static constexpr char PLT[] = "preload";
|
||||
static constexpr size_t PLTLEN = sizeof(PLT) - 1;
|
||||
if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' &&
|
||||
std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) {
|
||||
// we have to search preload in whitespace separated list:
|
||||
|
@ -804,8 +828,8 @@ parse_next_link_header_once(const char *first, const char *last) {
|
|||
}
|
||||
// we are only interested in rel=preload parameter. Others are
|
||||
// simply skipped.
|
||||
static const char PL[] = "rel=preload";
|
||||
static const size_t PLLEN = sizeof(PL) - 1;
|
||||
static constexpr char PL[] = "rel=preload";
|
||||
static constexpr size_t PLLEN = sizeof(PL) - 1;
|
||||
if (first + PLLEN == last) {
|
||||
if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) {
|
||||
ok = true;
|
||||
|
@ -834,20 +858,19 @@ parse_next_link_header_once(const char *first, const char *last) {
|
|||
}
|
||||
}
|
||||
// we have to reject URI if we have nonempty anchor parameter.
|
||||
static const char ANCHOR[] = "anchor=";
|
||||
static const size_t ANCHORLEN = sizeof(ANCHOR) - 1;
|
||||
if (!ign && first + ANCHORLEN <= last) {
|
||||
if (std::equal(ANCHOR, ANCHOR + ANCHORLEN, first, util::CaseCmp())) {
|
||||
// we only accept URI if anchor="" here.
|
||||
if (first + ANCHORLEN + 2 <= last) {
|
||||
if (*(first + ANCHORLEN) != '"' || *(first + ANCHORLEN + 1) != '"') {
|
||||
ign = true;
|
||||
}
|
||||
} else {
|
||||
// here we got invalid production (anchor=") or anchor=?
|
||||
ign = true;
|
||||
}
|
||||
}
|
||||
static constexpr char ANCHOR[] = "anchor=";
|
||||
static constexpr size_t ANCHORLEN = sizeof(ANCHOR) - 1;
|
||||
if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) {
|
||||
ign = true;
|
||||
}
|
||||
|
||||
// reject URI if we have non-empty loadpolicy. This could be
|
||||
// tightened up to just pick up "next" or "insert".
|
||||
static constexpr char LOADPOLICY[] = "loadpolicy=";
|
||||
static constexpr size_t LOADPOLICYLEN = sizeof(LOADPOLICY) - 1;
|
||||
if (!ign &&
|
||||
!check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) {
|
||||
ign = true;
|
||||
}
|
||||
|
||||
auto param_first = first;
|
||||
|
|
|
@ -627,6 +627,19 @@ void test_http2_parse_link_header(void) {
|
|||
CU_ASSERT(1 == res.size());
|
||||
CU_ASSERT(std::make_pair(&s[36], &s[39]) == res[0].uri);
|
||||
}
|
||||
{
|
||||
// With loadpolicy="next", url should be ignored
|
||||
const char s[] = R"(<url>; rel=preload; loadpolicy="next")";
|
||||
auto res = http2::parse_link_header(s, sizeof(s) - 1);
|
||||
CU_ASSERT(0 == res.size());
|
||||
}
|
||||
{
|
||||
// url should be picked up if empty loadpolicy is specified
|
||||
const char s[] = R"(<url>; rel=preload; loadpolicy="")";
|
||||
auto res = http2::parse_link_header(s, sizeof(s) - 1);
|
||||
CU_ASSERT(1 == res.size());
|
||||
CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
|
||||
}
|
||||
{
|
||||
// case-insensitive match
|
||||
const char s[] = R"(<url>; rel=preload; ANCHOR="#foo", <url>; )"
|
||||
|
|
|
@ -20,5 +20,8 @@
|
|||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
if ENABLE_ASIO_LIB
|
||||
nobase_include_HEADERS = nghttp2/asio_http2.h nghttp2/asio_http2_client.h \
|
||||
nghttp2/asio_http2_server.h
|
||||
endif # ENABLE_ASIO_LIB
|
||||
|
|
249
src/nghttp.cc
249
src/nghttp.cc
|
@ -61,17 +61,33 @@
|
|||
|
||||
namespace nghttp2 {
|
||||
|
||||
// stream ID of anchor stream node when --dep-idle is enabled. These
|
||||
// * portion of ANCHOR_ID_* matches RequestPriority in HtmlParser.h.
|
||||
// The stream ID = 1 is excluded since it is used as first stream in
|
||||
// upgrade case.
|
||||
enum {
|
||||
ANCHOR_ID_HIGH = 3,
|
||||
ANCHOR_ID_MEDIUM = 5,
|
||||
ANCHOR_ID_LOW = 7,
|
||||
ANCHOR_ID_LOWEST = 9,
|
||||
// The anchor stream nodes when --no-dep is not used. The stream ID =
|
||||
// 1 is excluded since it is used as first stream in upgrade case. We
|
||||
// follows the same dependency anchor nodes as Firefox does.
|
||||
struct Anchor {
|
||||
int32_t stream_id;
|
||||
// stream ID this anchor depends on
|
||||
int32_t dep_stream_id;
|
||||
// .. with this weight.
|
||||
int32_t weight;
|
||||
};
|
||||
|
||||
// This is index into anchors. Firefox uses ANCHOR_FOLLOWERS for html
|
||||
// file.
|
||||
enum {
|
||||
ANCHOR_LEADERS,
|
||||
ANCHOR_UNBLOCKED,
|
||||
ANCHOR_BACKGROUND,
|
||||
ANCHOR_SPECULATIVE,
|
||||
ANCHOR_FOLLOWERS,
|
||||
};
|
||||
|
||||
namespace {
|
||||
auto anchors = std::array<Anchor, 5>{{
|
||||
{3, 0, 201}, {5, 0, 101}, {7, 0, 1}, {9, 7, 1}, {11, 3, 1},
|
||||
}};
|
||||
} // namespace
|
||||
|
||||
Config::Config()
|
||||
: output_upper_thres(1024 * 1024), padding(0),
|
||||
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
|
||||
|
@ -79,7 +95,7 @@ Config::Config()
|
|||
timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0),
|
||||
null_out(false), remote_name(false), get_assets(false), stat(false),
|
||||
upgrade(false), continuation(false), no_content_length(false),
|
||||
no_dep(false), dep_idle(false), hexdump(false) {
|
||||
no_dep(false), hexdump(false), no_push(false) {
|
||||
nghttp2_option_new(&http2_option);
|
||||
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
|
||||
peer_max_concurrent_streams);
|
||||
|
@ -111,12 +127,10 @@ std::string strip_fragment(const char *raw_uri) {
|
|||
|
||||
Request::Request(const std::string &uri, const http_parser_url &u,
|
||||
const nghttp2_data_provider *data_prd, int64_t data_length,
|
||||
const nghttp2_priority_spec &pri_spec,
|
||||
std::shared_ptr<Dependency> dep, int pri, int level)
|
||||
: uri(uri), u(u), dep(std::move(dep)), pri_spec(pri_spec),
|
||||
data_length(data_length), data_offset(0), response_len(0),
|
||||
inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
|
||||
stream_id(-1), status(0), level(level), pri(pri),
|
||||
const nghttp2_priority_spec &pri_spec, int level)
|
||||
: uri(uri), u(u), pri_spec(pri_spec), data_length(data_length),
|
||||
data_offset(0), response_len(0), inflater(nullptr), html_parser(nullptr),
|
||||
data_prd(data_prd), stream_id(-1), status(0), level(level),
|
||||
expect_final_response(false) {
|
||||
http2::init_hdidx(res_hdidx);
|
||||
http2::init_hdidx(req_hdidx);
|
||||
|
@ -155,77 +169,41 @@ std::string Request::make_reqpath() const {
|
|||
return path;
|
||||
}
|
||||
|
||||
int32_t Request::find_dep_stream_id(int start) {
|
||||
for (auto i = start; i >= 0; --i) {
|
||||
for (auto req : dep->deps[i]) {
|
||||
return req->stream_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
nghttp2_priority_spec Request::resolve_dep(int32_t pri) {
|
||||
namespace {
|
||||
nghttp2_priority_spec resolve_dep(int res_type) {
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int exclusive = 0;
|
||||
int32_t stream_id = -1;
|
||||
|
||||
nghttp2_priority_spec_default_init(&pri_spec);
|
||||
|
||||
if (config.no_dep) {
|
||||
nghttp2_priority_spec_default_init(&pri_spec);
|
||||
|
||||
return pri_spec;
|
||||
}
|
||||
|
||||
if (config.dep_idle) {
|
||||
int32_t anchor_id = 0;
|
||||
switch (pri) {
|
||||
case REQ_PRI_HIGH:
|
||||
anchor_id = ANCHOR_ID_HIGH;
|
||||
break;
|
||||
case REQ_PRI_MEDIUM:
|
||||
anchor_id = ANCHOR_ID_MEDIUM;
|
||||
break;
|
||||
case REQ_PRI_LOW:
|
||||
anchor_id = ANCHOR_ID_LOW;
|
||||
break;
|
||||
case REQ_PRI_LOWEST:
|
||||
anchor_id = ANCHOR_ID_LOWEST;
|
||||
break;
|
||||
}
|
||||
nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT, 0);
|
||||
return pri_spec;
|
||||
int32_t anchor_id;
|
||||
int32_t weight;
|
||||
switch (res_type) {
|
||||
case REQ_CSS:
|
||||
case REQ_JS:
|
||||
anchor_id = anchors[ANCHOR_LEADERS].stream_id;
|
||||
weight = 2;
|
||||
break;
|
||||
case REQ_UNBLOCK_JS:
|
||||
anchor_id = anchors[ANCHOR_UNBLOCKED].stream_id;
|
||||
weight = 2;
|
||||
break;
|
||||
case REQ_IMG:
|
||||
anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
|
||||
weight = 12;
|
||||
break;
|
||||
default:
|
||||
anchor_id = anchors[ANCHOR_FOLLOWERS].stream_id;
|
||||
weight = 2;
|
||||
}
|
||||
|
||||
if (pri == 0) {
|
||||
return pri_spec;
|
||||
}
|
||||
|
||||
auto start = std::min(pri, (int)dep->deps.size() - 1);
|
||||
|
||||
for (auto i = start; i >= 0; --i) {
|
||||
if (dep->deps[i][0]->pri < pri) {
|
||||
stream_id = find_dep_stream_id(i);
|
||||
|
||||
if (i != (int)dep->deps.size() - 1) {
|
||||
exclusive = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (dep->deps[i][0]->pri == pri) {
|
||||
stream_id = find_dep_stream_id(i - 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_id == -1) {
|
||||
return pri_spec;
|
||||
}
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, stream_id, NGHTTP2_DEFAULT_WEIGHT,
|
||||
exclusive);
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, anchor_id, weight, 0);
|
||||
return pri_spec;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool Request::is_ipv6_literal_addr() const {
|
||||
if (util::has_uri_field(u, UF_HOST)) {
|
||||
|
@ -531,12 +509,13 @@ int HttpClient::initiate_connection() {
|
|||
SSL_set_fd(ssl, fd);
|
||||
SSL_set_connect_state(ssl);
|
||||
|
||||
// If the user overrode the host header, use that value for
|
||||
// the SNI extension
|
||||
// If the user overrode the :authority or host header, use that
|
||||
// value for the SNI extension
|
||||
const char *host_string = nullptr;
|
||||
auto i =
|
||||
std::find_if(std::begin(config.headers), std::end(config.headers),
|
||||
[](const Header &nv) { return "host" == nv.name; });
|
||||
auto i = std::find_if(std::begin(config.headers),
|
||||
std::end(config.headers), [](const Header &nv) {
|
||||
return ":authority" == nv.name || "host" == nv.name;
|
||||
});
|
||||
if (i != std::end(config.headers)) {
|
||||
host_string = (*i).value.c_str();
|
||||
} else {
|
||||
|
@ -758,6 +737,13 @@ size_t populate_settings(nghttp2_settings_entry *iv) {
|
|||
iv[niv].value = config.header_table_size;
|
||||
++niv;
|
||||
}
|
||||
|
||||
if (config.no_push) {
|
||||
iv[niv].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
iv[niv].value = 0;
|
||||
++niv;
|
||||
}
|
||||
|
||||
return niv;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -766,7 +752,7 @@ int HttpClient::on_upgrade_connect() {
|
|||
ssize_t rv;
|
||||
record_connect_end_time();
|
||||
assert(!reqvec.empty());
|
||||
std::array<nghttp2_settings_entry, 32> iv;
|
||||
std::array<nghttp2_settings_entry, 16> iv;
|
||||
size_t niv = populate_settings(iv.data());
|
||||
assert(settings_payload.size() >= 8 * niv);
|
||||
rv = nghttp2_pack_settings_payload(settings_payload.data(),
|
||||
|
@ -973,26 +959,22 @@ int HttpClient::connection_made() {
|
|||
return -1;
|
||||
}
|
||||
}
|
||||
if (!config.no_dep && config.dep_idle) {
|
||||
if (!config.no_dep) {
|
||||
// Create anchor stream nodes
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int32_t dep_stream_id = 0;
|
||||
|
||||
for (auto stream_id :
|
||||
{ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW, ANCHOR_ID_LOWEST}) {
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, dep_stream_id,
|
||||
NGHTTP2_DEFAULT_WEIGHT, 0);
|
||||
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id,
|
||||
for (auto &anchor : anchors) {
|
||||
nghttp2_priority_spec_init(&pri_spec, anchor.dep_stream_id, anchor.weight,
|
||||
0);
|
||||
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, anchor.stream_id,
|
||||
&pri_spec);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dep_stream_id = stream_id;
|
||||
}
|
||||
|
||||
rv = nghttp2_session_set_next_stream_id(session, ANCHOR_ID_LOWEST + 2);
|
||||
rv = nghttp2_session_set_next_stream_id(
|
||||
session, anchors[ANCHOR_FOLLOWERS].stream_id + 2);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -1000,7 +982,8 @@ int HttpClient::connection_made() {
|
|||
if (need_upgrade()) {
|
||||
// Amend the priority because we cannot send priority in
|
||||
// HTTP/1.1 Upgrade.
|
||||
nghttp2_priority_spec_init(&pri_spec, ANCHOR_ID_HIGH, config.weight, 0);
|
||||
auto &anchor = anchors[ANCHOR_FOLLOWERS];
|
||||
nghttp2_priority_spec_init(&pri_spec, anchor.stream_id, config.weight, 0);
|
||||
|
||||
rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
|
||||
if (rv != 0) {
|
||||
|
@ -1233,9 +1216,7 @@ void HttpClient::update_hostport() {
|
|||
bool HttpClient::add_request(const std::string &uri,
|
||||
const nghttp2_data_provider *data_prd,
|
||||
int64_t data_length,
|
||||
const nghttp2_priority_spec &pri_spec,
|
||||
std::shared_ptr<Dependency> dep, int pri,
|
||||
int level) {
|
||||
const nghttp2_priority_spec &pri_spec, int level) {
|
||||
http_parser_url u;
|
||||
memset(&u, 0, sizeof(u));
|
||||
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
|
||||
|
@ -1249,8 +1230,8 @@ bool HttpClient::add_request(const std::string &uri,
|
|||
path_cache.insert(uri);
|
||||
}
|
||||
|
||||
reqvec.push_back(make_unique<Request>(uri, u, data_prd, data_length, pri_spec,
|
||||
std::move(dep), pri, level));
|
||||
reqvec.push_back(
|
||||
make_unique<Request>(uri, u, data_prd, data_length, pri_spec, level));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1268,37 +1249,9 @@ void HttpClient::record_connect_end_time() {
|
|||
}
|
||||
|
||||
void HttpClient::request_done(Request *req) {
|
||||
if (req->pri == 0 && req->dep) {
|
||||
assert(req->dep->deps.empty());
|
||||
|
||||
req->dep->deps.push_back(std::vector<Request *>{req});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->stream_id % 2 == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto itr = std::begin(req->dep->deps);
|
||||
for (; itr != std::end(req->dep->deps); ++itr) {
|
||||
if ((*itr)[0]->pri == req->pri) {
|
||||
(*itr).push_back(req);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ((*itr)[0]->pri > req->pri) {
|
||||
auto v = std::vector<Request *>{req};
|
||||
req->dep->deps.insert(itr, std::move(v));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (itr == std::end(req->dep->deps)) {
|
||||
req->dep->deps.push_back(std::vector<Request *>{req});
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_JANSSON
|
||||
|
@ -1481,7 +1434,7 @@ void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
|
|||
|
||||
for (auto &p : req->html_parser->get_links()) {
|
||||
auto uri = strip_fragment(p.first.c_str());
|
||||
auto pri = p.second;
|
||||
auto res_type = p.second;
|
||||
|
||||
http_parser_url u;
|
||||
memset(&u, 0, sizeof(u));
|
||||
|
@ -1490,10 +1443,9 @@ void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
|
|||
util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
|
||||
util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) {
|
||||
// No POST data for assets
|
||||
auto pri_spec = req->resolve_dep(pri);
|
||||
auto pri_spec = resolve_dep(res_type);
|
||||
|
||||
if (client->add_request(uri, nullptr, 0, pri_spec, req->dep, pri,
|
||||
req->level + 1)) {
|
||||
if (client->add_request(uri, nullptr, 0, pri_spec, req->level + 1)) {
|
||||
|
||||
submit_request(client, config.headers, client->reqvec.back().get());
|
||||
}
|
||||
|
@ -1656,7 +1608,7 @@ int on_begin_headers_callback(nghttp2_session *session,
|
|||
|
||||
nghttp2_priority_spec_default_init(&pri_spec);
|
||||
|
||||
auto req = make_unique<Request>("", u, nullptr, 0, pri_spec, nullptr);
|
||||
auto req = make_unique<Request>("", u, nullptr, 0, pri_spec);
|
||||
req->stream_id = stream_id;
|
||||
|
||||
nghttp2_session_set_stream_user_data(session, stream_id, req.get());
|
||||
|
@ -1928,7 +1880,7 @@ see http://www.w3.org/TR/resource-timing/#processing-model
|
|||
|
||||
sorted by 'complete'
|
||||
|
||||
responseEnd requestStart process code size request path)" << std::endl;
|
||||
id responseEnd requestStart process code size request path)" << std::endl;
|
||||
|
||||
const auto &base = client.timing.connect_end_time;
|
||||
for (const auto &req : reqs) {
|
||||
|
@ -1940,8 +1892,9 @@ responseEnd requestStart process code size request path)" << std::endl;
|
|||
req->timing.response_end_time - req->timing.request_start_time);
|
||||
auto pushed = req->stream_id % 2 == 0;
|
||||
|
||||
std::cout << std::setw(11) << ("+" + util::format_duration(response_end))
|
||||
<< " " << (pushed ? "*" : " ") << std::setw(11)
|
||||
std::cout << std::setw(3) << req->stream_id << " " << std::setw(11)
|
||||
<< ("+" + util::format_duration(response_end)) << " "
|
||||
<< (pushed ? "*" : " ") << std::setw(11)
|
||||
<< ("+" + util::format_duration(request_start)) << " "
|
||||
<< std::setw(8) << util::format_duration(total) << " "
|
||||
<< std::setw(4) << req->status << " " << std::setw(4)
|
||||
|
@ -2054,17 +2007,16 @@ int communicate(
|
|||
nghttp2_priority_spec pri_spec;
|
||||
int32_t dep_stream_id = 0;
|
||||
|
||||
if (!config.no_dep && config.dep_idle) {
|
||||
dep_stream_id = ANCHOR_ID_HIGH;
|
||||
if (!config.no_dep) {
|
||||
dep_stream_id = anchors[ANCHOR_FOLLOWERS].stream_id;
|
||||
}
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, dep_stream_id, config.weight, 0);
|
||||
|
||||
for (auto req : requests) {
|
||||
for (int i = 0; i < config.multiply; ++i) {
|
||||
auto dep = std::make_shared<Dependency>();
|
||||
client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
|
||||
pri_spec, std::move(dep));
|
||||
pri_spec);
|
||||
}
|
||||
}
|
||||
client.update_hostport();
|
||||
|
@ -2421,10 +2373,10 @@ Options:
|
|||
--no-content-length
|
||||
Don't send content-length header field.
|
||||
--no-dep Don't send dependency based priority hint to server.
|
||||
--dep-idle Use idle streams as anchor nodes to express priority.
|
||||
--hexdump Display the incoming traffic in hexadecimal (Canonical
|
||||
hex+ASCII display). If SSL/TLS is used, decrypted data
|
||||
are used.
|
||||
--no-push Disable server push.
|
||||
--version Display version information and exit.
|
||||
-h, --help Display this help and exit.
|
||||
|
||||
|
@ -2465,9 +2417,9 @@ int main(int argc, char **argv) {
|
|||
{"version", no_argument, &flag, 5},
|
||||
{"no-content-length", no_argument, &flag, 6},
|
||||
{"no-dep", no_argument, &flag, 7},
|
||||
{"dep-idle", no_argument, &flag, 8},
|
||||
{"trailer", required_argument, &flag, 9},
|
||||
{"hexdump", no_argument, &flag, 10},
|
||||
{"no-push", no_argument, &flag, 11},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
|
||||
|
@ -2561,10 +2513,7 @@ int main(int argc, char **argv) {
|
|||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// To test "never index" repr, don't index authorization header
|
||||
// field unconditionally.
|
||||
auto no_index = util::strieq_l("authorization", header);
|
||||
config.headers.emplace_back(header, value, no_index);
|
||||
config.headers.emplace_back(header, value, false);
|
||||
util::inp_strlower(config.headers.back().name);
|
||||
break;
|
||||
}
|
||||
|
@ -2626,10 +2575,6 @@ int main(int argc, char **argv) {
|
|||
// no-dep option
|
||||
config.no_dep = true;
|
||||
break;
|
||||
case 8:
|
||||
// dep-idle option
|
||||
config.dep_idle = true;
|
||||
break;
|
||||
case 9: {
|
||||
// trailer option
|
||||
auto header = optarg;
|
||||
|
@ -2658,6 +2603,10 @@ int main(int argc, char **argv) {
|
|||
// hexdump option
|
||||
config.hexdump = true;
|
||||
break;
|
||||
case 11:
|
||||
// no-push option
|
||||
config.no_push = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
21
src/nghttp.h
21
src/nghttp.h
|
@ -83,8 +83,8 @@ struct Config {
|
|||
bool continuation;
|
||||
bool no_content_length;
|
||||
bool no_dep;
|
||||
bool dep_idle;
|
||||
bool hexdump;
|
||||
bool no_push;
|
||||
};
|
||||
|
||||
enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
|
||||
|
@ -103,18 +103,11 @@ struct RequestTiming {
|
|||
RequestTiming() : state(RequestState::INITIAL) {}
|
||||
};
|
||||
|
||||
struct Request;
|
||||
|
||||
struct Dependency {
|
||||
std::vector<std::vector<Request *>> deps;
|
||||
};
|
||||
|
||||
struct Request {
|
||||
// For pushed request, |uri| is empty and |u| is zero-cleared.
|
||||
Request(const std::string &uri, const http_parser_url &u,
|
||||
const nghttp2_data_provider *data_prd, int64_t data_length,
|
||||
const nghttp2_priority_spec &pri_spec,
|
||||
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
|
||||
const nghttp2_priority_spec &pri_spec, int level = 0);
|
||||
~Request();
|
||||
|
||||
void init_inflater();
|
||||
|
@ -124,10 +117,6 @@ struct Request {
|
|||
|
||||
std::string make_reqpath() const;
|
||||
|
||||
int32_t find_dep_stream_id(int start);
|
||||
|
||||
nghttp2_priority_spec resolve_dep(int32_t pri);
|
||||
|
||||
bool is_ipv6_literal_addr() const;
|
||||
|
||||
bool response_pseudo_header_allowed(int16_t token) const;
|
||||
|
@ -145,7 +134,6 @@ struct Request {
|
|||
// URI without fragment
|
||||
std::string uri;
|
||||
http_parser_url u;
|
||||
std::shared_ptr<Dependency> dep;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
RequestTiming timing;
|
||||
int64_t data_length;
|
||||
|
@ -159,8 +147,6 @@ struct Request {
|
|||
int status;
|
||||
// Recursion level: 0: first entity, 1: entity linked from first entity
|
||||
int level;
|
||||
// RequestPriority value defined in HtmlParser.h
|
||||
int pri;
|
||||
http2::HeaderIndex res_hdidx;
|
||||
// used for incoming PUSH_PROMISE
|
||||
http2::HeaderIndex req_hdidx;
|
||||
|
@ -220,8 +206,7 @@ struct HttpClient {
|
|||
void update_hostport();
|
||||
bool add_request(const std::string &uri,
|
||||
const nghttp2_data_provider *data_prd, int64_t data_length,
|
||||
const nghttp2_priority_spec &pri_spec,
|
||||
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
|
||||
const nghttp2_priority_spec &pri_spec, int level = 0);
|
||||
|
||||
void record_start_time();
|
||||
void record_domain_lookup_end_time();
|
||||
|
|
46
src/shrpx.cc
46
src/shrpx.cc
|
@ -417,9 +417,7 @@ namespace {
|
|||
void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||
auto conn_handler = static_cast<ConnectionHandler *>(w->data);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Reopening log files: main";
|
||||
}
|
||||
LOG(NOTICE) << "Reopening log files: main";
|
||||
|
||||
(void)reopen_log_files();
|
||||
redirect_stderr_to_errorlog();
|
||||
|
@ -555,7 +553,9 @@ void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
|
|||
|
||||
conn_handler->graceful_shutdown_worker();
|
||||
|
||||
if (get_config()->num_worker == 1) {
|
||||
if (get_config()->num_worker == 1 &&
|
||||
conn_handler->get_single_worker()->get_worker_stat()->num_connections >
|
||||
0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -565,31 +565,14 @@ void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto conn_handler = static_cast<ConnectionHandler *>(w->data);
|
||||
auto worker = conn_handler->get_single_worker();
|
||||
|
||||
// In multi threaded mode (get_config()->num_worker > 1), we have to
|
||||
// wait for event notification to workers to finish.
|
||||
if (get_config()->num_worker == 1 && conn_handler->get_graceful_shutdown() &&
|
||||
(!worker || worker->get_worker_stat()->num_connections == 0)) {
|
||||
ev_break(loop);
|
||||
}
|
||||
|
||||
conn_handler->handle_ocsp_completion();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto conn_handler = static_cast<ConnectionHandler *>(w->data);
|
||||
const auto &old_ticket_keys = conn_handler->get_ticket_keys();
|
||||
|
||||
auto ticket_keys = std::make_shared<TicketKeys>();
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "renew ticket key";
|
||||
}
|
||||
LOG(NOTICE) << "Renew ticket keys: main";
|
||||
|
||||
// We store at most 2 ticket keys
|
||||
if (old_ticket_keys) {
|
||||
auto &old_keys = old_ticket_keys->keys;
|
||||
|
@ -742,13 +725,8 @@ int event_loop() {
|
|||
graceful_shutdown_sig.data = conn_handler.get();
|
||||
ev_signal_start(loop, &graceful_shutdown_sig);
|
||||
|
||||
ev_timer refresh_timer;
|
||||
ev_timer_init(&refresh_timer, refresh_cb, 0., 1.);
|
||||
refresh_timer.data = conn_handler.get();
|
||||
ev_timer_again(loop, &refresh_timer);
|
||||
|
||||
if (!get_config()->upstream_no_tls && !get_config()->no_ocsp) {
|
||||
conn_handler->update_ocsp_async();
|
||||
conn_handler->proceed_next_cert_ocsp();
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -758,7 +736,7 @@ int event_loop() {
|
|||
ev_run(loop, 0);
|
||||
|
||||
conn_handler->join_worker();
|
||||
conn_handler->join_ocsp_thread();
|
||||
conn_handler->cancel_ocsp_update();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2035,12 +2013,6 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
if (!get_config()->upstream_no_tls && !get_config()->no_ocsp) {
|
||||
#ifdef NOTHREADS
|
||||
mod_config()->no_ocsp = true;
|
||||
LOG(WARN)
|
||||
<< "OCSP stapling has been disabled since it requires threading but"
|
||||
"threading disabled at build time.";
|
||||
#else // !NOTHREADS
|
||||
struct stat buf;
|
||||
if (stat(get_config()->fetch_ocsp_response_file.get(), &buf) != 0) {
|
||||
mod_config()->no_ocsp = true;
|
||||
|
@ -2048,7 +2020,6 @@ int main(int argc, char **argv) {
|
|||
<< get_config()->fetch_ocsp_response_file.get()
|
||||
<< " not found. OCSP stapling has been disabled.";
|
||||
}
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
if (get_config()->downstream_addrs.empty()) {
|
||||
|
@ -2143,7 +2114,6 @@ int main(int argc, char **argv) {
|
|||
memset(&act, 0, sizeof(struct sigaction));
|
||||
act.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &act, nullptr);
|
||||
sigaction(SIGCHLD, &act, nullptr);
|
||||
|
||||
event_loop();
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "shrpx_connection_handler.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <thread>
|
||||
|
@ -67,7 +69,25 @@ void ocsp_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
|||
return;
|
||||
}
|
||||
|
||||
h->update_ocsp_async();
|
||||
LOG(NOTICE) << "Start ocsp update";
|
||||
|
||||
h->proceed_next_cert_ocsp();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void ocsp_read_cb(struct ev_loop *loop, ev_io *w, int revent) {
|
||||
auto h = static_cast<ConnectionHandler *>(w->data);
|
||||
|
||||
h->read_ocsp_chunk();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void ocsp_chld_cb(struct ev_loop *loop, ev_child *w, int revent) {
|
||||
auto h = static_cast<ConnectionHandler *>(w->data);
|
||||
|
||||
h->handle_ocsp_complete();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -79,6 +99,17 @@ ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
|
|||
|
||||
ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.);
|
||||
ocsp_timer_.data = this;
|
||||
|
||||
ev_io_init(&ocsp_.rev, ocsp_read_cb, -1, EV_READ);
|
||||
ocsp_.rev.data = this;
|
||||
|
||||
ev_child_init(&ocsp_.chldev, ocsp_chld_cb, 0, 0);
|
||||
ocsp_.chldev.data = this;
|
||||
|
||||
ocsp_.next = 0;
|
||||
ocsp_.fd = -1;
|
||||
|
||||
reset_ocsp();
|
||||
}
|
||||
|
||||
ConnectionHandler::~ConnectionHandler() {
|
||||
|
@ -315,80 +346,192 @@ bool ConnectionHandler::get_graceful_shutdown() const {
|
|||
return graceful_shutdown_;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void update_ocsp_ssl_ctx(SSL_CTX *ssl_ctx) {
|
||||
void ConnectionHandler::cancel_ocsp_update() {
|
||||
if (ocsp_.pid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
kill(ocsp_.pid, SIGTERM);
|
||||
}
|
||||
|
||||
// inspired by h2o_read_command function from h2o project:
|
||||
// https://github.com/h2o/h2o
|
||||
int ConnectionHandler::start_ocsp_update(const char *cert_file) {
|
||||
int rv;
|
||||
int pfd[2];
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Start ocsp update for " << cert_file;
|
||||
}
|
||||
|
||||
assert(!ev_is_active(&ocsp_.rev));
|
||||
assert(!ev_is_active(&ocsp_.chldev));
|
||||
|
||||
char *const argv[] = {
|
||||
const_cast<char *>(get_config()->fetch_ocsp_response_file.get()),
|
||||
const_cast<char *>(cert_file), nullptr};
|
||||
char *const envp[] = {nullptr};
|
||||
|
||||
#ifdef O_CLOEXEC
|
||||
if (pipe2(pfd, O_CLOEXEC) == -1) {
|
||||
return -1;
|
||||
}
|
||||
#else // !O_CLOEXEC
|
||||
if (pipe(pfd) == -1) {
|
||||
return -1;
|
||||
}
|
||||
util::make_socket_closeonexec(pfd[0]);
|
||||
util::make_socket_closeonexec(pfd[1]);
|
||||
#endif // !O_CLOEXEC
|
||||
|
||||
auto closer = defer([&pfd]() {
|
||||
if (pfd[0] != -1) {
|
||||
close(pfd[0]);
|
||||
}
|
||||
|
||||
if (pfd[1] != -1) {
|
||||
close(pfd[1]);
|
||||
}
|
||||
});
|
||||
|
||||
auto pid = fork();
|
||||
if (pid == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "Could not execute ocsp query command for " << cert_file
|
||||
<< ": " << argv[0] << ", fork() failed, errno=" << error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// child process
|
||||
dup2(pfd[1], 1);
|
||||
close(pfd[0]);
|
||||
|
||||
rv = execve(argv[0], argv, envp);
|
||||
if (rv == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "Could not execute ocsp query command: " << argv[0]
|
||||
<< ", execve() faild, errno=" << error;
|
||||
_Exit(EXIT_FAILURE);
|
||||
}
|
||||
// unreachable
|
||||
}
|
||||
|
||||
// parent process
|
||||
close(pfd[1]);
|
||||
pfd[1] = -1;
|
||||
|
||||
ocsp_.pid = pid;
|
||||
ocsp_.fd = pfd[0];
|
||||
pfd[0] = -1;
|
||||
|
||||
util::make_socket_nonblocking(ocsp_.fd);
|
||||
ev_io_set(&ocsp_.rev, ocsp_.fd, EV_READ);
|
||||
ev_io_start(loop_, &ocsp_.rev);
|
||||
|
||||
ev_child_set(&ocsp_.chldev, ocsp_.pid, 0);
|
||||
ev_child_start(loop_, &ocsp_.chldev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConnectionHandler::read_ocsp_chunk() {
|
||||
std::array<uint8_t, 4096> buf;
|
||||
for (;;) {
|
||||
ssize_t n;
|
||||
while ((n = read(ocsp_.fd, buf.data(), buf.size())) == -1 && errno == EINTR)
|
||||
;
|
||||
|
||||
if (n == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
return;
|
||||
}
|
||||
auto error = errno;
|
||||
LOG(WARN) << "Reading from ocsp query command failed: errno=" << error;
|
||||
ocsp_.error = error;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::copy_n(std::begin(buf), n, std::back_inserter(ocsp_.resp));
|
||||
}
|
||||
|
||||
ev_io_stop(loop_, &ocsp_.rev);
|
||||
}
|
||||
|
||||
void ConnectionHandler::handle_ocsp_complete() {
|
||||
ev_io_stop(loop_, &ocsp_.rev);
|
||||
ev_child_stop(loop_, &ocsp_.chldev);
|
||||
|
||||
assert(ocsp_.next < all_ssl_ctx_.size());
|
||||
|
||||
auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
|
||||
auto tls_ctx_data =
|
||||
static_cast<ssl::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||
auto cert_file = tls_ctx_data->cert_file;
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
if (ssl::get_ocsp_response(out, cert_file) != 0) {
|
||||
LOG(WARN) << "ocsp update for " << cert_file << " failed";
|
||||
auto rstatus = ocsp_.chldev.rstatus;
|
||||
auto status = WEXITSTATUS(rstatus);
|
||||
if (ocsp_.error || !WIFEXITED(rstatus) || status != 0) {
|
||||
LOG(WARN) << "ocsp query command for " << tls_ctx_data->cert_file
|
||||
<< " failed: error=" << ocsp_.error << ", rstatus=" << rstatus
|
||||
<< ", status=" << status;
|
||||
++ocsp_.next;
|
||||
proceed_next_cert_ocsp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "ocsp update for " << cert_file << " finished successfully";
|
||||
LOG(INFO) << "ocsp update for " << tls_ctx_data->cert_file
|
||||
<< " finished successfully";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(tls_ctx_data->mu);
|
||||
tls_ctx_data->ocsp_data = std::move(out);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConnectionHandler::update_ocsp() {
|
||||
for (auto ssl_ctx : all_ssl_ctx_) {
|
||||
update_ocsp_ssl_ctx(ssl_ctx);
|
||||
{
|
||||
std::lock_guard<std::mutex> g(tls_ctx_data->mu);
|
||||
tls_ctx_data->ocsp_data = std::move(ocsp_.resp);
|
||||
}
|
||||
|
||||
++ocsp_.next;
|
||||
proceed_next_cert_ocsp();
|
||||
}
|
||||
|
||||
void ConnectionHandler::update_ocsp_async() {
|
||||
#ifndef NOTHREADS
|
||||
ocsp_result_ = std::async(std::launch::async, [this]() {
|
||||
// Log files are managed per thread. We have to open log files
|
||||
// for this thread. We don't reopen log files in this thread when
|
||||
// signal is received. This is future TODO.
|
||||
reopen_log_files();
|
||||
auto closer = defer([]() {
|
||||
auto lgconf = log_config();
|
||||
if (lgconf->accesslog_fd != -1) {
|
||||
close(lgconf->accesslog_fd);
|
||||
}
|
||||
if (lgconf->errorlog_fd != -1) {
|
||||
close(lgconf->errorlog_fd);
|
||||
}
|
||||
});
|
||||
void ConnectionHandler::reset_ocsp() {
|
||||
if (ocsp_.fd != -1) {
|
||||
close(ocsp_.fd);
|
||||
}
|
||||
|
||||
update_ocsp();
|
||||
});
|
||||
#endif // !NOTHREADS
|
||||
ocsp_.fd = -1;
|
||||
ocsp_.pid = 0;
|
||||
ocsp_.error = 0;
|
||||
ocsp_.resp = std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
void ConnectionHandler::handle_ocsp_completion() {
|
||||
#ifndef NOTHREADS
|
||||
if (!ocsp_result_.valid()) {
|
||||
return;
|
||||
void ConnectionHandler::proceed_next_cert_ocsp() {
|
||||
for (;;) {
|
||||
reset_ocsp();
|
||||
if (ocsp_.next == all_ssl_ctx_.size()) {
|
||||
ocsp_.next = 0;
|
||||
// We have updated all ocsp response, and schedule next update.
|
||||
ev_timer_set(&ocsp_timer_, get_config()->ocsp_update_interval, 0.);
|
||||
ev_timer_start(loop_, &ocsp_timer_);
|
||||
return;
|
||||
}
|
||||
|
||||
auto ssl_ctx = all_ssl_ctx_[ocsp_.next];
|
||||
auto tls_ctx_data =
|
||||
static_cast<ssl::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||
auto cert_file = tls_ctx_data->cert_file;
|
||||
|
||||
if (start_ocsp_update(cert_file) != 0) {
|
||||
++ocsp_.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (ocsp_result_.wait_for(std::chrono::seconds(0)) !=
|
||||
std::future_status::ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
ocsp_result_.get();
|
||||
|
||||
ev_timer_set(&ocsp_timer_, get_config()->ocsp_update_interval, 0.);
|
||||
ev_timer_start(loop_, &ocsp_timer_);
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
void ConnectionHandler::join_ocsp_thread() {
|
||||
#ifndef NOTHREADS
|
||||
if (!ocsp_result_.valid()) {
|
||||
return;
|
||||
}
|
||||
ocsp_result_.get();
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -32,9 +32,6 @@
|
|||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // !NOTHREADS
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
|
@ -51,6 +48,22 @@ class Worker;
|
|||
struct WorkerStat;
|
||||
struct TicketKeys;
|
||||
|
||||
struct OCSPUpdateContext {
|
||||
// ocsp response buffer
|
||||
std::vector<uint8_t> resp;
|
||||
// index to ConnectionHandler::all_ssl_ctx_, which points to next
|
||||
// SSL_CTX to update ocsp response cache.
|
||||
size_t next;
|
||||
ev_child chldev;
|
||||
ev_io rev;
|
||||
// fd to read response from fetch-ocsp-response script
|
||||
int fd;
|
||||
// errno encountered while processing response
|
||||
int error;
|
||||
// pid of forked fetch-ocsp-response script process
|
||||
pid_t pid;
|
||||
};
|
||||
|
||||
class ConnectionHandler {
|
||||
public:
|
||||
ConnectionHandler(struct ev_loop *loop);
|
||||
|
@ -79,23 +92,25 @@ public:
|
|||
void set_graceful_shutdown(bool f);
|
||||
bool get_graceful_shutdown() const;
|
||||
void join_worker();
|
||||
// Updates OCSP response cache for all server side SSL_CTX object
|
||||
void update_ocsp();
|
||||
// Just like update_ocsp(), but performed in new thread. Call
|
||||
// handle_ocsp_completion() to handle its completion and scheduling
|
||||
// next update.
|
||||
void update_ocsp_async();
|
||||
// Handles asynchronous OCSP update completion and schedules next
|
||||
|
||||
// Cancels ocsp update process
|
||||
void cancel_ocsp_update();
|
||||
// Starts ocsp update for certficate |cert_file|.
|
||||
int start_ocsp_update(const char *cert_file);
|
||||
// Reads incoming data from ocsp update process
|
||||
void read_ocsp_chunk();
|
||||
// Handles the completion of one ocsp update
|
||||
void handle_ocsp_complete();
|
||||
// Resets ocsp_;
|
||||
void reset_ocsp();
|
||||
// Proceeds to the next certificate's ocsp update. If all
|
||||
// certificates' ocsp update has been done, schedule next ocsp
|
||||
// update.
|
||||
void handle_ocsp_completion();
|
||||
// Waits for OCSP thread finishes if it is still running.
|
||||
void join_ocsp_thread();
|
||||
void proceed_next_cert_ocsp();
|
||||
|
||||
private:
|
||||
#ifndef NOTHREADS
|
||||
std::future<void> ocsp_result_;
|
||||
#endif // !NOTHREADS
|
||||
std::vector<SSL_CTX *> all_ssl_ctx_;
|
||||
OCSPUpdateContext ocsp_;
|
||||
// Worker instances when multi threaded mode (-nN, N >= 2) is used.
|
||||
std::vector<std::unique_ptr<Worker>> workers_;
|
||||
// Worker instance used when single threaded mode (-n1) is used.
|
||||
|
|
|
@ -757,12 +757,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
|
||||
if (token == http2::HD_CONTENT_LENGTH) {
|
||||
// libnghttp2 guarantees this can be parsed
|
||||
auto len = util::parse_uint(value, valuelen);
|
||||
downstream->set_response_content_length(len);
|
||||
}
|
||||
|
||||
downstream->add_response_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
|
@ -844,27 +838,9 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (downstream->get_response_content_length() == -1 &&
|
||||
downstream->expect_response_body()) {
|
||||
// Here we have response body but Content-Length is not known in
|
||||
// advance.
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
(downstream->get_request_major() <= 1 &&
|
||||
downstream->get_request_minor() <= 0)) {
|
||||
// We simply close connection for pre-HTTP/1.1 in this case.
|
||||
downstream->set_response_connection_close(true);
|
||||
} else if (downstream->get_request_method() != "CONNECT") {
|
||||
// Otherwise, use chunked encoding to keep upstream connection
|
||||
// open. In HTTP2, we are supporsed not to receive
|
||||
// transfer-encoding.
|
||||
downstream->add_response_header("transfer-encoding", "chunked",
|
||||
http2::HD_TRANSFER_ENCODING);
|
||||
downstream->set_chunked_response(true);
|
||||
}
|
||||
}
|
||||
|
||||
downstream->set_response_state(Downstream::HEADER_COMPLETE);
|
||||
downstream->check_upgrade_fulfilled();
|
||||
|
||||
if (downstream->get_upgraded()) {
|
||||
downstream->set_response_connection_close(true);
|
||||
// On upgrade sucess, both ends can send data
|
||||
|
@ -878,11 +854,35 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
SSLOG(INFO, http2session)
|
||||
<< "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
|
||||
}
|
||||
} else if (downstream->get_request_method() == "CONNECT") {
|
||||
// If request is CONNECT, terminate request body to avoid for
|
||||
// stream to stall.
|
||||
downstream->end_upload_data();
|
||||
} else {
|
||||
auto content_length =
|
||||
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
||||
if (content_length) {
|
||||
// libnghttp2 guarantees this can be parsed
|
||||
auto len = util::parse_uint(content_length->value);
|
||||
downstream->set_response_content_length(len);
|
||||
}
|
||||
|
||||
if (downstream->get_response_content_length() == -1 &&
|
||||
downstream->expect_response_body()) {
|
||||
// Here we have response body but Content-Length is not known in
|
||||
// advance.
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
(downstream->get_request_major() == 1 &&
|
||||
downstream->get_request_minor() == 0)) {
|
||||
// We simply close connection for pre-HTTP/1.1 in this case.
|
||||
downstream->set_response_connection_close(true);
|
||||
} else {
|
||||
// Otherwise, use chunked encoding to keep upstream connection
|
||||
// open. In HTTP2, we are supporsed not to receive
|
||||
// transfer-encoding.
|
||||
downstream->add_response_header("transfer-encoding", "chunked",
|
||||
http2::HD_TRANSFER_ENCODING);
|
||||
downstream->set_chunked_response(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rv = upstream->on_downstream_header_complete(downstream);
|
||||
if (rv != 0) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
|
|
|
@ -163,6 +163,8 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
|||
downstream->set_request_http2_scheme(scheme);
|
||||
|
||||
auto ptr = downstream.get();
|
||||
|
||||
nghttp2_session_set_stream_user_data(session_, 1, ptr);
|
||||
downstream_queue_.add_pending(std::move(downstream));
|
||||
downstream_queue_.mark_active(ptr);
|
||||
|
||||
|
@ -231,12 +233,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
|
||||
if (token == http2::HD_CONTENT_LENGTH) {
|
||||
// libnghttp2 guarantees this can be parsed
|
||||
auto len = util::parse_uint(value, valuelen);
|
||||
downstream->set_request_content_length(len);
|
||||
}
|
||||
|
||||
downstream->add_request_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
|
@ -298,6 +294,14 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
|||
http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
|
||||
}
|
||||
|
||||
auto content_length =
|
||||
downstream->get_request_header(http2::HD_CONTENT_LENGTH);
|
||||
if (content_length) {
|
||||
// libnghttp2 guarantees this can be parsed
|
||||
auto len = util::parse_uint(content_length->value);
|
||||
downstream->set_request_content_length(len);
|
||||
}
|
||||
|
||||
auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
|
||||
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||
|
@ -500,6 +504,10 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
auto downstream = static_cast<Downstream *>(
|
||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||
|
||||
if (!downstream) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For tunneling, issue RST_STREAM to finish the stream.
|
||||
if (downstream->get_upgraded() ||
|
||||
nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
|
||||
|
|
|
@ -520,6 +520,9 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
}
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
// Reset content-length because we reuse same Downstream for the
|
||||
// next response.
|
||||
downstream->set_response_content_length(-1);
|
||||
// For non-final response code, we just call
|
||||
// on_downstream_header_complete() without changing response
|
||||
// state.
|
||||
|
@ -537,7 +540,11 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
downstream->inspect_http1_response();
|
||||
downstream->check_upgrade_fulfilled();
|
||||
if (downstream->get_upgraded()) {
|
||||
// content-length must be ignored for upgraded connection.
|
||||
downstream->set_response_content_length(-1);
|
||||
downstream->set_response_connection_close(true);
|
||||
// transfer-encoding not applied to upgraded connection
|
||||
downstream->set_chunked_response(false);
|
||||
}
|
||||
if (upstream->on_downstream_header_complete(downstream) != 0) {
|
||||
return -1;
|
||||
|
|
112
src/shrpx_ssl.cc
112
src/shrpx_ssl.cc
|
@ -29,10 +29,6 @@
|
|||
#include <netinet/tcp.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#ifndef NOTHREADS
|
||||
#include <spawn.h>
|
||||
#endif // !NOTHREADS
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
@ -1025,114 +1021,6 @@ CertLookupTree *create_cert_lookup_tree() {
|
|||
return new ssl::CertLookupTree();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// inspired by h2o_read_command function from h2o project:
|
||||
// https://github.com/h2o/h2o
|
||||
int exec_read_stdout(std::vector<uint8_t> &out, char *const *argv,
|
||||
char *const *envp) {
|
||||
#ifndef NOTHREADS
|
||||
int rv;
|
||||
int pfd[2];
|
||||
|
||||
#ifdef O_CLOEXEC
|
||||
if (pipe2(pfd, O_CLOEXEC) == -1) {
|
||||
return -1;
|
||||
}
|
||||
#else // !O_CLOEXEC
|
||||
if (pipe(pfd) == -1) {
|
||||
return -1;
|
||||
}
|
||||
util::make_socket_closeonexec(pfd[0]);
|
||||
util::make_socket_closeonexec(pfd[1]);
|
||||
#endif // !O_CLOEXEC
|
||||
|
||||
auto closer = defer([pfd]() {
|
||||
close(pfd[0]);
|
||||
|
||||
if (pfd[1] != -1) {
|
||||
close(pfd[1]);
|
||||
}
|
||||
});
|
||||
|
||||
// posix_spawn family functions are really interesting. They makes
|
||||
// fork + dup2 + execve pattern easier.
|
||||
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
|
||||
if (posix_spawn_file_actions_init(&file_actions) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto file_actions_del =
|
||||
defer(posix_spawn_file_actions_destroy, &file_actions);
|
||||
|
||||
if (posix_spawn_file_actions_adddup2(&file_actions, pfd[1], 1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (posix_spawn_file_actions_addclose(&file_actions, pfd[0]) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
rv = posix_spawn(&pid, argv[0], &file_actions, nullptr, argv, envp);
|
||||
if (rv != 0) {
|
||||
LOG(WARN) << "Cannot execute ocsp query command: " << argv[0]
|
||||
<< ", errno=" << rv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(pfd[1]);
|
||||
pfd[1] = -1;
|
||||
|
||||
std::array<uint8_t, 4096> buf;
|
||||
for (;;) {
|
||||
ssize_t n;
|
||||
while ((n = read(pfd[0], buf.data(), buf.size())) == -1 && errno == EINTR)
|
||||
;
|
||||
|
||||
if (n == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "Reading from ocsp query command failed: errno=" << error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::copy_n(std::begin(buf), n, std::back_inserter(out));
|
||||
}
|
||||
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "waitpid for ocsp query command failed: errno=" << error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status)) {
|
||||
LOG(WARN) << "ocsp query command did not exit normally: " << status;
|
||||
return -1;
|
||||
}
|
||||
#endif // !NOTHREADS
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int get_ocsp_response(std::vector<uint8_t> &out, const char *cert_file) {
|
||||
char *const argv[] = {
|
||||
const_cast<char *>(get_config()->fetch_ocsp_response_file.get()),
|
||||
const_cast<char *>(cert_file), nullptr};
|
||||
char *const envp[] = {nullptr};
|
||||
|
||||
if (exec_read_stdout(out, argv, envp) != 0 || out.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace ssl
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -170,8 +170,6 @@ SSL_CTX *setup_client_ssl_context();
|
|||
// this function returns nullptr.
|
||||
CertLookupTree *create_cert_lookup_tree();
|
||||
|
||||
int get_ocsp_response(std::vector<uint8_t> &out, const char *cert_file);
|
||||
|
||||
} // namespace ssl
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -160,17 +160,13 @@ void Worker::process_events() {
|
|||
break;
|
||||
}
|
||||
case RENEW_TICKET_KEYS:
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(INFO, this) << "Renew ticket keys: worker(" << this << ")";
|
||||
}
|
||||
WLOG(NOTICE, this) << "Renew ticket keys: worker(" << this << ")";
|
||||
|
||||
ticket_keys_ = wev.ticket_keys;
|
||||
|
||||
break;
|
||||
case REOPEN_LOG:
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(INFO, this) << "Reopening log files: worker(" << this << ")";
|
||||
}
|
||||
WLOG(NOTICE, this) << "Reopening log files: worker(" << this << ")";
|
||||
|
||||
reopen_log_files();
|
||||
|
||||
|
|
|
@ -737,10 +737,16 @@ char *get_exec_path(int argc, char **const argv, const char *cwd) {
|
|||
|
||||
if (argv0[0] == '/') {
|
||||
path = static_cast<char *>(malloc(len + 1));
|
||||
if (path == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(path, argv0, len + 1);
|
||||
} else {
|
||||
auto cwdlen = strlen(cwd);
|
||||
path = static_cast<char *>(malloc(len + 1 + cwdlen + 1));
|
||||
if (path == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(path, cwd, cwdlen);
|
||||
path[cwdlen] = '/';
|
||||
memcpy(path + cwdlen + 1, argv0, len + 1);
|
||||
|
|
|
@ -140,9 +140,9 @@ void test_nghttp2_hd_deflate(void) {
|
|||
void test_nghttp2_hd_deflate_same_indexed_repr(void) {
|
||||
nghttp2_hd_deflater deflater;
|
||||
nghttp2_hd_inflater inflater;
|
||||
nghttp2_nv nva1[] = {MAKE_NV("cookie", "alpha"), MAKE_NV("cookie", "alpha")};
|
||||
nghttp2_nv nva2[] = {MAKE_NV("cookie", "alpha"), MAKE_NV("cookie", "alpha"),
|
||||
MAKE_NV("cookie", "alpha")};
|
||||
nghttp2_nv nva1[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha")};
|
||||
nghttp2_nv nva2[] = {MAKE_NV("host", "alpha"), MAKE_NV("host", "alpha"),
|
||||
MAKE_NV("host", "alpha")};
|
||||
nghttp2_bufs bufs;
|
||||
ssize_t blocklen;
|
||||
nva_out out;
|
||||
|
@ -250,7 +250,8 @@ void test_nghttp2_hd_inflate_indname_noinc(void) {
|
|||
nghttp2_hd_inflate_init(&inflater, mem);
|
||||
|
||||
for (i = 0; i < ARRLEN(nv); ++i) {
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv[i], 0));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv[i],
|
||||
NGHTTP2_HD_WITHOUT_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -283,7 +284,8 @@ void test_nghttp2_hd_inflate_indname_inc(void) {
|
|||
nva_out_init(&out);
|
||||
nghttp2_hd_inflate_init(&inflater, mem);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv, 1));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv,
|
||||
NGHTTP2_HD_WITH_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -325,10 +327,14 @@ void test_nghttp2_hd_inflate_indname_inc_eviction(void) {
|
|||
|
||||
nv.flags = NGHTTP2_NV_FLAG_NONE;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv, 1));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv, 1));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv, 1));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv, 1));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv,
|
||||
NGHTTP2_HD_WITH_INDEXING));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv,
|
||||
NGHTTP2_HD_WITH_INDEXING));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv,
|
||||
NGHTTP2_HD_WITH_INDEXING));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv,
|
||||
NGHTTP2_HD_WITH_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -372,7 +378,8 @@ void test_nghttp2_hd_inflate_newname_noinc(void) {
|
|||
nva_out_init(&out);
|
||||
nghttp2_hd_inflate_init(&inflater, mem);
|
||||
for (i = 0; i < ARRLEN(nv); ++i) {
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv[i], 0));
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv[i],
|
||||
NGHTTP2_HD_WITHOUT_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -405,7 +412,8 @@ void test_nghttp2_hd_inflate_newname_inc(void) {
|
|||
nva_out_init(&out);
|
||||
nghttp2_hd_inflate_init(&inflater, mem);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
|
||||
CU_ASSERT(
|
||||
0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -450,7 +458,8 @@ void test_nghttp2_hd_inflate_clearall_inc(void) {
|
|||
|
||||
nghttp2_hd_inflate_init(&inflater, mem);
|
||||
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
|
||||
CU_ASSERT(
|
||||
0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
@ -477,7 +486,8 @@ void test_nghttp2_hd_inflate_clearall_inc(void) {
|
|||
header table */
|
||||
nv.valuelen = sizeof(value) - 2;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
|
||||
CU_ASSERT(
|
||||
0 == nghttp2_hd_emit_newname_block(&bufs, &nv, NGHTTP2_HD_WITH_INDEXING));
|
||||
|
||||
blocklen = nghttp2_bufs_len(&bufs);
|
||||
|
||||
|
|
|
@ -6154,11 +6154,12 @@ void test_nghttp2_session_stream_attach_item(void) {
|
|||
CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == d->dpri);
|
||||
|
||||
CU_ASSERT(16 * 16 / 16 == b->effective_weight);
|
||||
CU_ASSERT(16 * 16 / 32 == b->effective_weight);
|
||||
CU_ASSERT(16 * 16 / 32 == d->effective_weight);
|
||||
|
||||
CU_ASSERT(0 == dd->queued);
|
||||
CU_ASSERT(1 == dd->queued);
|
||||
|
||||
nghttp2_stream_detach_item(b, session);
|
||||
|
||||
|
@ -6324,12 +6325,13 @@ void test_nghttp2_session_stream_attach_item_subtree(void) {
|
|||
CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == d->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
|
||||
CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
|
||||
|
||||
CU_ASSERT(16 == b->effective_weight);
|
||||
CU_ASSERT(16 * 16 / 16 == e->effective_weight);
|
||||
CU_ASSERT(16 * 16 / 32 == e->effective_weight);
|
||||
CU_ASSERT(16 * 16 / 32 == e->effective_weight);
|
||||
|
||||
CU_ASSERT(32 == a->sum_norest_weight);
|
||||
CU_ASSERT(16 == c->sum_norest_weight);
|
||||
|
|
Loading…
Reference in New Issue