mirror of
https://gitee.com/openharmony/third_party_nghttp2
synced 2024-11-23 16:00:07 +00:00
Merge branch 'master' into simple-extensions
This commit is contained in:
commit
9c84f60ba0
20
.travis.yml
20
.travis.yml
@ -27,12 +27,26 @@ before_install:
|
||||
- $CC --version
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.9" CC="gcc-4.9"; fi
|
||||
- $CC --version
|
||||
- go version
|
||||
before_script:
|
||||
# First build spdylay, since integration tests require it.
|
||||
# spdylay is going to be built under third-party/spdylay
|
||||
- cd third-party
|
||||
- git clone https://github.com/tatsuhiro-t/spdylay.git
|
||||
- cd spdylay
|
||||
- autoreconf -i
|
||||
- ./configure --disable-src --disable-examples
|
||||
- make check
|
||||
- export SPDYLAY_HOME=$PWD
|
||||
- cd ../..
|
||||
# Now build nghttp2
|
||||
- autoreconf -i
|
||||
- automake
|
||||
- autoconf
|
||||
- git submodule update --init
|
||||
- ./configure --enable-werror --with-mruby
|
||||
- ./configure --enable-werror --with-mruby --with-neverbleed LIBSPDYLAY_CFLAGS="-I$SPDYLAY_HOME/lib/includes" LIBSPDYLAY_LIBS="-L$SPDYLAY_HOME/lib/.libs -lspdylay"
|
||||
script:
|
||||
- make
|
||||
- make check
|
||||
- cd integration-tests
|
||||
- export GOPATH="$PWD/integration-tests/golang"
|
||||
- make itprep-local
|
||||
- make it-local
|
||||
|
@ -25,7 +25,7 @@ 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], [1.4.1-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
AC_INIT([nghttp2], [1.5.1-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
AC_CONFIG_AUX_DIR([.])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
@ -46,9 +46,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||
|
||||
dnl See versioning rule:
|
||||
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||
AC_SUBST(LT_CURRENT, 16)
|
||||
AC_SUBST(LT_CURRENT, 17)
|
||||
AC_SUBST(LT_REVISION, 0)
|
||||
AC_SUBST(LT_AGE, 2)
|
||||
AC_SUBST(LT_AGE, 3)
|
||||
|
||||
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
|
||||
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
|
||||
|
@ -89,6 +89,7 @@ APIDOCS= \
|
||||
nghttp2_session_consume.rst \
|
||||
nghttp2_session_consume_connection.rst \
|
||||
nghttp2_session_consume_stream.rst \
|
||||
nghttp2_session_create_idle_stream.rst \
|
||||
nghttp2_session_del.rst \
|
||||
nghttp2_session_find_stream.rst \
|
||||
nghttp2_session_get_effective_local_window_size.rst \
|
||||
@ -108,7 +109,9 @@ APIDOCS= \
|
||||
nghttp2_session_mem_recv.rst \
|
||||
nghttp2_session_mem_send.rst \
|
||||
nghttp2_session_recv.rst \
|
||||
nghttp2_session_change_stream_priority.rst \
|
||||
nghttp2_session_check_request_allowed.rst \
|
||||
nghttp2_session_check_server_session.rst \
|
||||
nghttp2_session_resume_data.rst \
|
||||
nghttp2_session_send.rst \
|
||||
nghttp2_session_server_new.rst \
|
||||
|
@ -8,7 +8,7 @@ _h2load()
|
||||
_get_comp_words_by_ref cur prev
|
||||
case $cur in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W '--threads --connection-window-bits --rate-period --input-file --npn-list --help --requests --timing-script-file --data --verbose --base-uri --ciphers --connection-active-timeout --rate --connection-inactivity-timeout --window-bits --clients --no-tls-proto --version --header --max-concurrent-streams ' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W '--connection-window-bits --clients --verbose --ciphers --rate --no-tls-proto --requests --base-uri --h1 --threads --npn-list --rate-period --data --version --connection-inactivity-timeout --timing-script-file --max-concurrent-streams --connection-active-timeout --input-file --header --window-bits --help ' -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
|
@ -8,7 +8,7 @@ _nghttpd()
|
||||
_get_comp_words_by_ref cur prev
|
||||
case $cur in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W '--mime-types-file --error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "H2LOAD" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
|
||||
.TH "H2LOAD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
||||
.SH NAME
|
||||
h2load \- HTTP/2 benchmarking tool
|
||||
.
|
||||
@ -227,6 +227,13 @@ Default: \fBh2,h2\-16,h2\-14,spdy/3.1,spdy/3,spdy/2,http/1.1\fP
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-h1
|
||||
Short hand for \fI\%\-\-npn\-list\fP=http/1.1
|
||||
\fI\%\-\-no\-tls\-proto\fP=http/1.1, which effectively force
|
||||
http/1.1 for both http and https URI.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-v, \-\-verbose
|
||||
Output debug information.
|
||||
.UNINDENT
|
||||
|
@ -188,6 +188,12 @@ OPTIONS
|
||||
|
||||
Default: ``h2,h2-16,h2-14,spdy/3.1,spdy/3,spdy/2,http/1.1``
|
||||
|
||||
.. option:: --h1
|
||||
|
||||
Short hand for :option:`--npn-list`\=http/1.1
|
||||
:option:`--no-tls-proto`\=http/1.1, which effectively force
|
||||
http/1.1 for both http and https URI.
|
||||
|
||||
.. option:: -v, --verbose
|
||||
|
||||
Output debug information.
|
||||
|
@ -86,6 +86,21 @@ time for 1st byte (of (decrypted in case of TLS) application data)
|
||||
deviation range (mean +/- sd) against total number of successful
|
||||
connections.
|
||||
|
||||
req/s (client)
|
||||
min
|
||||
The minimum request per second among all clients.
|
||||
max
|
||||
The maximum request per second among all clients.
|
||||
mean
|
||||
The mean request per second among all clients.
|
||||
sd
|
||||
The standard deviation of request per second among all clients.
|
||||
server.
|
||||
+/- sd
|
||||
The fraction of the number of connections within standard
|
||||
deviation range (mean +/- sd) against total number of successful
|
||||
connections.
|
||||
|
||||
FLOW CONTROL
|
||||
------------
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTP" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
|
||||
.TH "NGHTTP" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
||||
.SH NAME
|
||||
nghttp \- HTTP/2 client
|
||||
.
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPD" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
|
||||
.TH "NGHTTPD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpd \- HTTP/2 server
|
||||
.
|
||||
|
@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPX" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
|
||||
.TH "NGHTTPX" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpx \- HTTP/2 proxy
|
||||
.
|
||||
@ -685,9 +685,14 @@ protocol security.
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-no\-server\-push
|
||||
Disable HTTP/2 server push. Server push is only
|
||||
supported by default mode and HTTP/2 frontend. SPDY
|
||||
frontend does not support server push.
|
||||
Disable HTTP/2 server push. Server push is supported by
|
||||
default mode and HTTP/2 frontend via Link header field.
|
||||
It is also supported if both frontend and backend are
|
||||
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP mode).
|
||||
In this case, server push from backend session is
|
||||
relayed to frontend, and server push via Link header
|
||||
field is also supported. HTTP SPDY frontend does not
|
||||
support server push.
|
||||
.UNINDENT
|
||||
.SS Mode
|
||||
.INDENT 0.0
|
||||
@ -1053,8 +1058,8 @@ than master process.
|
||||
.UNINDENT
|
||||
.SH SERVER PUSH
|
||||
.sp
|
||||
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
|
||||
for Link header field (\fI\%RFC 5988\fP) in response headers from
|
||||
nghttpx supports HTTP/2 server push in default mode with Link header
|
||||
field. nghttpx looks for Link header field (\fI\%RFC 5988\fP) in response headers from
|
||||
backend server and extracts URI\-reference with parameter
|
||||
\fBrel=preload\fP (see \fI\%preload\fP)
|
||||
and pushes those URIs to the frontend client. Here is a sample Link
|
||||
@ -1079,6 +1084,14 @@ associated stream\(aqs status code must be 200.
|
||||
.UNINDENT
|
||||
.sp
|
||||
This limitation may be loosened in the future release.
|
||||
.sp
|
||||
nghttpx also supports server push if both frontend and backend are
|
||||
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP).
|
||||
In this case, in addition to server push via Link header field, server
|
||||
push from backend is relayed to frontend HTTP/2 session.
|
||||
.sp
|
||||
HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP or
|
||||
\fI\%\-\-client\-proxy\fP is used.
|
||||
.SH UNIX DOMAIN SOCKET
|
||||
.sp
|
||||
nghttpx supports UNIX domain socket with a filename for both frontend
|
||||
|
@ -613,9 +613,14 @@ HTTP/2 and SPDY
|
||||
|
||||
.. option:: --no-server-push
|
||||
|
||||
Disable HTTP/2 server push. Server push is only
|
||||
supported by default mode and HTTP/2 frontend. SPDY
|
||||
frontend does not support server push.
|
||||
Disable HTTP/2 server push. Server push is supported by
|
||||
default mode and HTTP/2 frontend via Link header field.
|
||||
It is also supported if both frontend and backend are
|
||||
HTTP/2 (which implies :option:`--http2-bridge` or :option:`\--client` mode).
|
||||
In this case, server push from backend session is
|
||||
relayed to frontend, and server push via Link header
|
||||
field is also supported. HTTP SPDY frontend does not
|
||||
support server push.
|
||||
|
||||
|
||||
Mode
|
||||
@ -954,8 +959,8 @@ SIGUSR2
|
||||
SERVER PUSH
|
||||
-----------
|
||||
|
||||
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
|
||||
for Link header field (`RFC 5988
|
||||
nghttpx supports HTTP/2 server push in default mode with Link header
|
||||
field. nghttpx looks for Link header field (`RFC 5988
|
||||
<http://tools.ietf.org/html/rfc5988>`_) in response headers from
|
||||
backend server and extracts URI-reference with parameter
|
||||
``rel=preload`` (see `preload
|
||||
@ -975,6 +980,14 @@ Currently, the following restriction is applied for server push:
|
||||
|
||||
This limitation may be loosened in the future release.
|
||||
|
||||
nghttpx also supports server push if both frontend and backend are
|
||||
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
|
||||
In this case, in addition to server push via Link header field, server
|
||||
push from backend is relayed to frontend HTTP/2 session.
|
||||
|
||||
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
|
||||
:option:`--client-proxy` is used.
|
||||
|
||||
UNIX DOMAIN SOCKET
|
||||
------------------
|
||||
|
||||
@ -1035,7 +1048,9 @@ has to deploy key generator program to update keys frequently (e.g.,
|
||||
every 1 hour). The example key generator tlsticketupdate.go is
|
||||
available under contrib directory in nghttp2 archive. The memcached
|
||||
entry key is ``nghttpx:tls-ticket-key``. The data format stored in
|
||||
memcached is the binary format described below::
|
||||
memcached is the binary format described below:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
+--------------+-------+----------------+
|
||||
| VERSION (4) |LEN (2)|KEY(48 or 80) ...
|
||||
|
@ -60,8 +60,8 @@ SIGUSR2
|
||||
SERVER PUSH
|
||||
-----------
|
||||
|
||||
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
|
||||
for Link header field (`RFC 5988
|
||||
nghttpx supports HTTP/2 server push in default mode with Link header
|
||||
field. nghttpx looks for Link header field (`RFC 5988
|
||||
<http://tools.ietf.org/html/rfc5988>`_) in response headers from
|
||||
backend server and extracts URI-reference with parameter
|
||||
``rel=preload`` (see `preload
|
||||
@ -81,6 +81,14 @@ Currently, the following restriction is applied for server push:
|
||||
|
||||
This limitation may be loosened in the future release.
|
||||
|
||||
nghttpx also supports server push if both frontend and backend are
|
||||
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
|
||||
In this case, in addition to server push via Link header field, server
|
||||
push from backend is relayed to frontend HTTP/2 session.
|
||||
|
||||
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
|
||||
:option:`--client-proxy` is used.
|
||||
|
||||
UNIX DOMAIN SOCKET
|
||||
------------------
|
||||
|
||||
@ -141,7 +149,9 @@ has to deploy key generator program to update keys frequently (e.g.,
|
||||
every 1 hour). The example key generator tlsticketupdate.go is
|
||||
available under contrib directory in nghttp2 archive. The memcached
|
||||
entry key is ``nghttpx:tls-ticket-key``. The data format stored in
|
||||
memcached is the binary format described below::
|
||||
memcached is the binary format described below:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
+--------------+-------+----------------+
|
||||
| VERSION (4) |LEN (2)|KEY(48 or 80) ...
|
||||
|
@ -613,7 +613,16 @@ typedef enum {
|
||||
|
||||
/**
|
||||
* @macro
|
||||
* Default maximum concurrent streams.
|
||||
*
|
||||
* Default maximum number of incoming concurrent streams. Use
|
||||
* `nghttp2_submit_settings()` with
|
||||
* :enum:`NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS` to change the
|
||||
* maximum number of incoming concurrent streams.
|
||||
*
|
||||
* .. note::
|
||||
*
|
||||
* The maximum number of outgoing concurrent streams is 100 by
|
||||
* default.
|
||||
*/
|
||||
#define NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1)
|
||||
|
||||
@ -2988,6 +2997,85 @@ NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session,
|
||||
int32_t stream_id,
|
||||
size_t size);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Changes priority of existing stream denoted by |stream_id|. The
|
||||
* new priority specification is |pri_spec|.
|
||||
*
|
||||
* The priority is changed silently and instantly, and no PRIORITY
|
||||
* frame will be sent to notify the peer of this change. This
|
||||
* function may be useful for server to change the priority of pushed
|
||||
* stream.
|
||||
*
|
||||
* If |session| is initialized as server, and ``pri_spec->stream_id``
|
||||
* points to the idle stream, the idle stream is created if it does
|
||||
* not exist. The created idle stream will depend on root stream
|
||||
* (stream 0) with weight 16.
|
||||
*
|
||||
* Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
|
||||
* found, we use default priority instead of given |pri_spec|. That
|
||||
* is make stream depend on root stream with weight 16.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* :enum:`NGHTTP2_ERR_NOMEM`
|
||||
* Out of memory.
|
||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||
* Attempted to depend on itself; or no stream exist for the given
|
||||
* |stream_id|; or |stream_id| is 0
|
||||
*/
|
||||
NGHTTP2_EXTERN int
|
||||
nghttp2_session_change_stream_priority(nghttp2_session *session,
|
||||
int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Creates idle stream with the given |stream_id|, and priority
|
||||
* |pri_spec|.
|
||||
*
|
||||
* The stream creation is done without sending PRIORITY frame, which
|
||||
* means that peer does not know about the existence of this idle
|
||||
* stream in the local endpoint.
|
||||
*
|
||||
* RFC 7540 does not disallow the use of creation of idle stream with
|
||||
* odd or even stream ID regardless of client or server. So this
|
||||
* function can create odd or even stream ID regardless of client or
|
||||
* server. But probably it is a bit safer to use the stream ID the
|
||||
* local endpoint can initiate (in other words, use odd stream ID for
|
||||
* client, and even stream ID for server), to avoid potential
|
||||
* collision from peer's instruction. Also we can use
|
||||
* `nghttp2_session_set_next_stream_id()` to avoid to open created
|
||||
* idle streams accidentally if we follow this recommendation.
|
||||
*
|
||||
* If |session| is initialized as server, and ``pri_spec->stream_id``
|
||||
* points to the idle stream, the idle stream is created if it does
|
||||
* not exist. The created idle stream will depend on root stream
|
||||
* (stream 0) with weight 16.
|
||||
*
|
||||
* Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
|
||||
* found, we use default priority instead of given |pri_spec|. That
|
||||
* is make stream depend on root stream with weight 16.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of the following
|
||||
* negative error codes:
|
||||
*
|
||||
* :enum:`NGHTTP2_ERR_NOMEM`
|
||||
* Out of memory.
|
||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||
* Attempted to depend on itself; or stream denoted by |stream_id|
|
||||
* already exists; or |stream_id| cannot be used to create idle
|
||||
* stream (in other words, local endpoint has already opened
|
||||
* stream ID greater than or equal to the given stream ID; or
|
||||
* |stream_id| is 0
|
||||
*/
|
||||
NGHTTP2_EXTERN int
|
||||
nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
@ -3629,14 +3717,20 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
|
||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||
* The |stream_id| is 0; The |stream_id| does not designate stream
|
||||
* that peer initiated.
|
||||
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
|
||||
* The stream was alreay closed; or the |stream_id| is invalid.
|
||||
*
|
||||
* .. warning::
|
||||
*
|
||||
* This function returns assigned promised stream ID if it succeeds.
|
||||
* But that stream is not opened yet. The application must not
|
||||
* submit frame to that stream ID before
|
||||
* :type:`nghttp2_before_frame_send_callback` is called for this
|
||||
* frame.
|
||||
* As of 1.16.0, stream object for pushed resource is created when
|
||||
* this function succeeds. In that case, the application can submit
|
||||
* push response for the promised frame.
|
||||
*
|
||||
* In 1.15.0 or prior versions, pushed stream is not opened yet when
|
||||
* this function succeeds. The application must not submit frame to
|
||||
* that stream ID before :type:`nghttp2_before_frame_send_callback`
|
||||
* is called for this frame.
|
||||
*
|
||||
*/
|
||||
NGHTTP2_EXTERN int32_t
|
||||
@ -3751,6 +3845,14 @@ nghttp2_session_get_last_proc_stream_id(nghttp2_session *session);
|
||||
NGHTTP2_EXTERN int
|
||||
nghttp2_session_check_request_allowed(nghttp2_session *session);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
* Returns nonzero if |session| is initialized as server side session.
|
||||
*/
|
||||
NGHTTP2_EXTERN int
|
||||
nghttp2_session_check_server_session(nghttp2_session *session);
|
||||
|
||||
/**
|
||||
* @function
|
||||
*
|
||||
|
@ -42,3 +42,11 @@ int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec) {
|
||||
return pri_spec->stream_id == 0 &&
|
||||
pri_spec->weight == NGHTTP2_DEFAULT_WEIGHT && pri_spec->exclusive == 0;
|
||||
}
|
||||
|
||||
void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec) {
|
||||
if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) {
|
||||
pri_spec->weight = NGHTTP2_MIN_WEIGHT;
|
||||
} else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) {
|
||||
pri_spec->weight = NGHTTP2_MAX_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
@ -31,4 +31,12 @@
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
/*
|
||||
* This function normalizes pri_spec->weight if it is out of range.
|
||||
* If pri_spec->weight is less than NGHTTP2_MIN_WEIGHT, it is set to
|
||||
* NGHTTP2_MIN_WEIGHT. If pri_spec->weight is larger than
|
||||
* NGHTTP2_MAX_WEIGHT, it is set to NGHTTP2_MAX_WEIGHT.
|
||||
*/
|
||||
void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec);
|
||||
|
||||
#endif /* NGHTTP2_PRIORITY_SPEC_H */
|
||||
|
@ -372,6 +372,9 @@ static int session_new(nghttp2_session **session_ptr,
|
||||
(*session_ptr)->max_incoming_reserved_streams =
|
||||
NGHTTP2_MAX_INCOMING_RESERVED_STREAMS;
|
||||
|
||||
/* Limit max outgoing concurrent streams to sensible value */
|
||||
(*session_ptr)->remote_settings.max_concurrent_streams = 100;
|
||||
|
||||
if (option) {
|
||||
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
|
||||
option->no_auto_window_update) {
|
||||
@ -665,6 +668,21 @@ int nghttp2_session_reprioritize_stream(
|
||||
}
|
||||
}
|
||||
|
||||
assert(dep_stream);
|
||||
|
||||
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
|
||||
/* This is minor optimization when just weight is changed.
|
||||
Currently, we don't reschedule stream in this case, since we
|
||||
don't retain enough information to do that
|
||||
(descendant_last_cycle we used to schedule it). This means new
|
||||
weight is only applied in the next scheduling, and if weight is
|
||||
drastically increased, library is not responding very quickly.
|
||||
If this is really an issue, we will do workaround for this. */
|
||||
dep_stream->sum_dep_weight += pri_spec->weight - stream->weight;
|
||||
stream->weight = pri_spec->weight;
|
||||
return 0;
|
||||
}
|
||||
|
||||
nghttp2_stream_dep_remove_subtree(stream);
|
||||
|
||||
/* We have to update weight after removing stream from tree */
|
||||
@ -740,6 +758,31 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
||||
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
||||
item->queued = 1;
|
||||
break;
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
nghttp2_headers_aux_data *aux_data;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
|
||||
aux_data = &item->aux_data.headers;
|
||||
|
||||
if (!stream) {
|
||||
return NGHTTP2_ERR_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
|
||||
NGHTTP2_DEFAULT_WEIGHT, 0);
|
||||
|
||||
if (!nghttp2_session_open_stream(
|
||||
session, frame->push_promise.promised_stream_id,
|
||||
NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED,
|
||||
aux_data->stream_user_data)) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
|
||||
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
||||
item->queued = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
case NGHTTP2_WINDOW_UPDATE:
|
||||
if (stream) {
|
||||
stream->window_update_queued = 1;
|
||||
@ -965,15 +1008,6 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
||||
}
|
||||
}
|
||||
|
||||
/* We don't have to track dependency of received reserved stream */
|
||||
if (stream->shut_flags & NGHTTP2_SHUT_WR) {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/* TODO Client does not have to track dependencies of streams except
|
||||
for those which have upload data. Currently, we just track
|
||||
everything. */
|
||||
|
||||
if (pri_spec->stream_id == 0) {
|
||||
dep_stream = &session->root;
|
||||
}
|
||||
@ -997,6 +1031,7 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
|
||||
int rv;
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_mem *mem;
|
||||
int is_my_stream_id;
|
||||
|
||||
mem = &session->mem;
|
||||
stream = nghttp2_session_get_stream(session, stream_id);
|
||||
@ -1044,14 +1079,16 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
|
||||
}
|
||||
}
|
||||
|
||||
is_my_stream_id = nghttp2_session_is_my_stream_id(session, stream_id);
|
||||
|
||||
/* pushed streams which is not opened yet is not counted toward max
|
||||
concurrent limits */
|
||||
if ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH)) {
|
||||
if (!nghttp2_session_is_my_stream_id(session, stream_id)) {
|
||||
if (!is_my_stream_id) {
|
||||
--session->num_incoming_reserved_streams;
|
||||
}
|
||||
} else {
|
||||
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
|
||||
if (is_my_stream_id) {
|
||||
--session->num_outgoing_streams;
|
||||
} else {
|
||||
--session->num_incoming_streams;
|
||||
@ -1061,7 +1098,8 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
|
||||
/* Closes both directions just in case they are not closed yet */
|
||||
stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED;
|
||||
|
||||
if (session->server && nghttp2_stream_in_dep_tree(stream)) {
|
||||
if (session->server && !is_my_stream_id &&
|
||||
nghttp2_stream_in_dep_tree(stream)) {
|
||||
/* On server side, retain stream at most MAX_CONCURRENT_STREAMS
|
||||
combined with the current active incoming streams to make
|
||||
dependency tree work better. */
|
||||
@ -1420,6 +1458,9 @@ static int session_predicate_response_headers_send(nghttp2_session *session,
|
||||
* RST_STREAM was queued for this stream.
|
||||
* NGHTTP2_ERR_SESSION_CLOSING
|
||||
* This session is closing.
|
||||
* NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
|
||||
* New stream cannot be created because GOAWAY is already sent or
|
||||
* received.
|
||||
*/
|
||||
static int
|
||||
session_predicate_push_response_headers_send(nghttp2_session *session,
|
||||
@ -1437,6 +1478,9 @@ session_predicate_push_response_headers_send(nghttp2_session *session,
|
||||
if (stream->state == NGHTTP2_STREAM_CLOSING) {
|
||||
return NGHTTP2_ERR_STREAM_CLOSING;
|
||||
}
|
||||
if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) {
|
||||
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1835,40 +1879,41 @@ static int session_prep_frame(nghttp2_session *session,
|
||||
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
||||
if (session_predicate_push_response_headers_send(session, stream) ==
|
||||
0) {
|
||||
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
||||
if (stream && stream->state == NGHTTP2_STREAM_RESERVED) {
|
||||
rv = session_predicate_push_response_headers_send(session, stream);
|
||||
if (rv == 0) {
|
||||
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
||||
|
||||
if (aux_data->stream_user_data) {
|
||||
stream->stream_user_data = aux_data->stream_user_data;
|
||||
if (aux_data->stream_user_data) {
|
||||
stream->stream_user_data = aux_data->stream_user_data;
|
||||
}
|
||||
}
|
||||
} else if (session_predicate_response_headers_send(session, stream) ==
|
||||
0) {
|
||||
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
|
||||
rv = 0;
|
||||
} else {
|
||||
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
|
||||
|
||||
rv = session_predicate_headers_send(session, stream);
|
||||
}
|
||||
|
||||
if (rv != 0) {
|
||||
// If stream was alreay closed,
|
||||
// nghttp2_session_get_stream() returns NULL, but item is
|
||||
// still attached to the stream. Search stream including
|
||||
// closed again.
|
||||
stream =
|
||||
nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||
if (stream && stream->item == item) {
|
||||
int rv2;
|
||||
if (rv != 0) {
|
||||
// If stream was alreay closed, nghttp2_session_get_stream()
|
||||
// returns NULL, but item is still attached to the stream.
|
||||
// Search stream including closed again.
|
||||
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||
if (stream && stream->item == item) {
|
||||
int rv2;
|
||||
|
||||
rv2 = nghttp2_stream_detach_item(stream);
|
||||
rv2 = nghttp2_stream_detach_item(stream);
|
||||
|
||||
if (nghttp2_is_fatal(rv2)) {
|
||||
return rv2;
|
||||
}
|
||||
if (nghttp2_is_fatal(rv2)) {
|
||||
return rv2;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1930,30 +1975,8 @@ static int session_prep_frame(nghttp2_session *session,
|
||||
}
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_headers_aux_data *aux_data;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
size_t estimated_payloadlen;
|
||||
|
||||
aux_data = &item->aux_data.headers;
|
||||
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
||||
/* stream could be NULL if associated stream was already
|
||||
closed. */
|
||||
if (stream) {
|
||||
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
|
||||
NGHTTP2_DEFAULT_WEIGHT, 0);
|
||||
} else {
|
||||
nghttp2_priority_spec_default_init(&pri_spec);
|
||||
}
|
||||
|
||||
if (!nghttp2_session_open_stream(
|
||||
session, frame->push_promise.promised_stream_id,
|
||||
NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED,
|
||||
aux_data->stream_user_data)) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
|
||||
estimated_payloadlen = session_estimate_headers_payload(
|
||||
session, frame->push_promise.nva, frame->push_promise.nvlen, 0);
|
||||
|
||||
@ -1961,6 +1984,10 @@ static int session_prep_frame(nghttp2_session *session,
|
||||
return NGHTTP2_ERR_FRAME_SIZE_ERROR;
|
||||
}
|
||||
|
||||
/* stream could be NULL if associated stream was already
|
||||
closed. */
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
||||
/* predicte should fail if stream is NULL. */
|
||||
rv = session_predicate_push_promise_send(session, stream);
|
||||
if (rv != 0) {
|
||||
@ -2243,16 +2270,19 @@ static int session_close_stream_on_goaway(nghttp2_session *session,
|
||||
nghttp2_stream *stream, *next_stream;
|
||||
nghttp2_close_stream_on_goaway_arg arg = {session, NULL, last_stream_id,
|
||||
incoming};
|
||||
uint32_t error_code;
|
||||
|
||||
rv = nghttp2_map_each(&session->streams, find_stream_on_goaway_func, &arg);
|
||||
assert(rv == 0);
|
||||
|
||||
error_code =
|
||||
session->server && incoming ? NGHTTP2_REFUSED_STREAM : NGHTTP2_CANCEL;
|
||||
|
||||
stream = arg.head;
|
||||
while (stream) {
|
||||
next_stream = stream->closed_next;
|
||||
stream->closed_next = NULL;
|
||||
rv = nghttp2_session_close_stream(session, stream->stream_id,
|
||||
NGHTTP2_REFUSED_STREAM);
|
||||
rv = nghttp2_session_close_stream(session, stream->stream_id, error_code);
|
||||
|
||||
/* stream may be deleted here */
|
||||
|
||||
@ -3360,9 +3390,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||
}
|
||||
|
||||
/*
|
||||
* Decompress header blocks of incoming request HEADERS and also call
|
||||
* additional callbacks. This function can be called again if this
|
||||
* function returns NGHTTP2_ERR_PAUSE.
|
||||
* Call this function when HEADERS frame was completely received.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of negative error
|
||||
* codes:
|
||||
@ -3372,71 +3400,20 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_end_request_headers_received(nghttp2_session *session _U_,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream) {
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
}
|
||||
/* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decompress header blocks of incoming (push-)response HEADERS and
|
||||
* also call additional callbacks. This function can be called again
|
||||
* if this function returns NGHTTP2_ERR_PAUSE.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of negative error
|
||||
* codes:
|
||||
*
|
||||
* NGHTTP2_ERR_CALLBACK_FAILURE
|
||||
* The callback function failed.
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_end_response_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream) {
|
||||
static int session_end_stream_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream) {
|
||||
int rv;
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
/* This is the last frame of this stream, so disallow
|
||||
further receptions. */
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decompress header blocks of incoming HEADERS and also call
|
||||
* additional callbacks. This function can be called again if this
|
||||
* function returns NGHTTP2_ERR_PAUSE.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one of negative error
|
||||
* codes:
|
||||
*
|
||||
* NGHTTP2_ERR_CALLBACK_FAILURE
|
||||
* The callback function failed.
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
*/
|
||||
int nghttp2_session_end_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream) {
|
||||
int rv;
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||
}
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -3517,19 +3494,7 @@ static int session_after_header_block_received(nghttp2_session *session) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (frame->headers.cat) {
|
||||
case NGHTTP2_HCAT_REQUEST:
|
||||
return nghttp2_session_end_request_headers_received(session, frame, stream);
|
||||
case NGHTTP2_HCAT_RESPONSE:
|
||||
case NGHTTP2_HCAT_PUSH_RESPONSE:
|
||||
return nghttp2_session_end_response_headers_received(session, frame,
|
||||
stream);
|
||||
case NGHTTP2_HCAT_HEADERS:
|
||||
return nghttp2_session_end_headers_received(session, frame, stream);
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return 0;
|
||||
return session_end_stream_headers_received(session, frame, stream);
|
||||
}
|
||||
|
||||
int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
||||
@ -4287,9 +4252,8 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
||||
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
|
||||
}
|
||||
}
|
||||
rv = nghttp2_session_add_rst_stream(session,
|
||||
frame->push_promise.promised_stream_id,
|
||||
NGHTTP2_REFUSED_STREAM);
|
||||
rv = nghttp2_session_add_rst_stream(
|
||||
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
@ -6903,9 +6867,67 @@ int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) {
|
||||
|
||||
nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session,
|
||||
int32_t stream_id) {
|
||||
if (stream_id == 0) {
|
||||
return &session->root;
|
||||
}
|
||||
|
||||
return nghttp2_session_get_stream_raw(session, stream_id);
|
||||
}
|
||||
|
||||
nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) {
|
||||
return &session->root;
|
||||
}
|
||||
|
||||
int nghttp2_session_check_server_session(nghttp2_session *session) {
|
||||
return session->server;
|
||||
}
|
||||
|
||||
int nghttp2_session_change_stream_priority(
|
||||
nghttp2_session *session, int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec) {
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_priority_spec pri_spec_copy;
|
||||
|
||||
if (stream_id == 0 || stream_id == pri_spec->stream_id) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
stream = nghttp2_session_get_stream_raw(session, stream_id);
|
||||
if (!stream) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pri_spec_copy = *pri_spec;
|
||||
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
|
||||
|
||||
return nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
|
||||
}
|
||||
|
||||
int nghttp2_session_create_idle_stream(nghttp2_session *session,
|
||||
int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec) {
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_priority_spec pri_spec_copy;
|
||||
|
||||
if (stream_id == 0 || stream_id == pri_spec->stream_id ||
|
||||
!session_detect_idle_stream(session, stream_id)) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
stream = nghttp2_session_get_stream_raw(session, stream_id);
|
||||
if (stream) {
|
||||
return NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pri_spec_copy = *pri_spec;
|
||||
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
|
||||
|
||||
stream =
|
||||
nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE,
|
||||
&pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL);
|
||||
if (!stream) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -191,11 +191,13 @@ struct nghttp2_session {
|
||||
updated when one frame was written. */
|
||||
uint64_t last_cycle;
|
||||
void *user_data;
|
||||
/* Points to the latest closed stream. NULL if there is no closed
|
||||
stream. Only used when session is initialized as server. */
|
||||
/* Points to the latest incoming closed stream. NULL if there is no
|
||||
closed stream. Only used when session is initialized as
|
||||
server. */
|
||||
nghttp2_stream *closed_stream_head;
|
||||
/* Points to the oldest closed stream. NULL if there is no closed
|
||||
stream. Only used when session is initialized as server. */
|
||||
/* Points to the oldest incoming closed stream. NULL if there is no
|
||||
closed stream. Only used when session is initialized as
|
||||
server. */
|
||||
nghttp2_stream *closed_stream_tail;
|
||||
/* Points to the latest idle stream. NULL if there is no idle
|
||||
stream. Only used when session is initialized as server .*/
|
||||
@ -341,7 +343,7 @@ int nghttp2_session_is_my_stream_id(nghttp2_session *session,
|
||||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory.
|
||||
* NGHTTP2_ERR_STREAM_CLOSED
|
||||
* Stream already closed (DATA frame only)
|
||||
* Stream already closed (DATA and PUSH_PROMISE frame only)
|
||||
*/
|
||||
int nghttp2_session_add_item(nghttp2_session *session,
|
||||
nghttp2_outbound_item *item);
|
||||
@ -567,18 +569,6 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session);
|
||||
int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
int nghttp2_session_end_request_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
int nghttp2_session_end_response_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
int nghttp2_session_end_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
||||
nghttp2_frame *frame);
|
||||
|
||||
|
@ -30,13 +30,14 @@
|
||||
#include "nghttp2_session.h"
|
||||
#include "nghttp2_helper.h"
|
||||
|
||||
static int stream_weight_less(const void *lhsx, const void *rhsx) {
|
||||
static int stream_less(const void *lhsx, const void *rhsx) {
|
||||
const nghttp2_stream *lhs, *rhs;
|
||||
|
||||
lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
|
||||
rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
|
||||
|
||||
return lhs->cycle < rhs->cycle;
|
||||
return lhs->cycle < rhs->cycle ||
|
||||
(lhs->cycle == rhs->cycle && lhs->seq < rhs->seq);
|
||||
}
|
||||
|
||||
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||
@ -45,7 +46,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||
int32_t local_initial_window_size,
|
||||
void *stream_user_data, nghttp2_mem *mem) {
|
||||
nghttp2_map_entry_init(&stream->map_entry, (key_type)stream_id);
|
||||
nghttp2_pq_init(&stream->obq, stream_weight_less, mem);
|
||||
nghttp2_pq_init(&stream->obq, stream_less, mem);
|
||||
|
||||
stream->stream_id = stream_id;
|
||||
stream->flags = flags;
|
||||
@ -79,6 +80,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||
stream->queued = 0;
|
||||
stream->descendant_last_cycle = 0;
|
||||
stream->cycle = 0;
|
||||
stream->descendant_next_seq = 0;
|
||||
stream->seq = 0;
|
||||
stream->last_writelen = 0;
|
||||
}
|
||||
|
||||
@ -124,6 +127,7 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
||||
stream = dep_stream, dep_stream = dep_stream->dep_prev) {
|
||||
stream->cycle =
|
||||
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||
stream->seq = dep_stream->descendant_next_seq++;
|
||||
|
||||
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%ld\n",
|
||||
stream->stream_id, stream->cycle));
|
||||
@ -166,6 +170,7 @@ static void stream_obq_remove(nghttp2_stream *stream) {
|
||||
stream->queued = 0;
|
||||
stream->cycle = 0;
|
||||
stream->descendant_last_cycle = 0;
|
||||
stream->last_writelen = 0;
|
||||
|
||||
if (stream_subtree_active(dep_stream)) {
|
||||
return;
|
||||
@ -206,12 +211,19 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
|
||||
dep_stream->descendant_last_cycle = 0;
|
||||
stream->cycle = 0;
|
||||
} else {
|
||||
/* We update descendant_last_cycle here, and we don't do it when
|
||||
no data is written for stream. This effectively means that
|
||||
we treat these streams as if they are not scheduled at all.
|
||||
This does not cause disruption in scheduling machinery. It
|
||||
just makes new streams scheduled a bit early. */
|
||||
dep_stream->descendant_last_cycle = stream->cycle;
|
||||
|
||||
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
|
||||
|
||||
stream->cycle =
|
||||
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||
stream->seq = dep_stream->descendant_next_seq++;
|
||||
|
||||
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
|
||||
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,15 @@ struct nghttp2_stream {
|
||||
int64_t content_length;
|
||||
/* Received body so far */
|
||||
int64_t recv_content_length;
|
||||
/* Base last_cycle for direct descendent streams. */
|
||||
uint64_t descendant_last_cycle;
|
||||
/* Next scheduled time to sent item */
|
||||
uint64_t cycle;
|
||||
/* Next seq used for direct descendant streams */
|
||||
uint64_t descendant_next_seq;
|
||||
/* Secondary key for prioritization to break a tie for cycle. This
|
||||
value is monotonically increased for single parent stream. */
|
||||
uint64_t seq;
|
||||
/* pointers to form dependency tree. If multiple streams depend on
|
||||
a stream, only one stream (left most) has non-NULL dep_prev which
|
||||
points to the stream it depends on. The remaining streams are
|
||||
@ -164,6 +173,8 @@ struct nghttp2_stream {
|
||||
void *stream_user_data;
|
||||
/* Item to send */
|
||||
nghttp2_outbound_item *item;
|
||||
/* Last written length of frame payload */
|
||||
size_t last_writelen;
|
||||
/* stream ID */
|
||||
int32_t stream_id;
|
||||
/* Current remote window size. This value is computed against the
|
||||
@ -202,12 +213,6 @@ struct nghttp2_stream {
|
||||
then its ancestors, except for root, are also queued. This
|
||||
invariant may break in fatal error condition. */
|
||||
uint8_t queued;
|
||||
/* Base last_cycle for direct descendent streams. */
|
||||
uint64_t descendant_last_cycle;
|
||||
/* Next scheduled time to sent item */
|
||||
uint64_t cycle;
|
||||
/* Last written length of frame payload */
|
||||
size_t last_writelen;
|
||||
/* This flag is used to reduce excessive queuing of WINDOW_UPDATE to
|
||||
this stream. The nonzero does not necessarily mean WINDOW_UPDATE
|
||||
is not queued. */
|
||||
|
@ -117,14 +117,6 @@ fail2:
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void adjust_priority_spec_weight(nghttp2_priority_spec *pri_spec) {
|
||||
if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) {
|
||||
pri_spec->weight = NGHTTP2_MIN_WEIGHT;
|
||||
} else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) {
|
||||
pri_spec->weight = NGHTTP2_MAX_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t submit_headers_shared_nva(nghttp2_session *session,
|
||||
uint8_t flags, int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec,
|
||||
@ -141,7 +133,7 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
|
||||
|
||||
if (pri_spec) {
|
||||
copy_pri_spec = *pri_spec;
|
||||
adjust_priority_spec_weight(©_pri_spec);
|
||||
nghttp2_priority_spec_normalize_weight(©_pri_spec);
|
||||
} else {
|
||||
nghttp2_priority_spec_default_init(©_pri_spec);
|
||||
}
|
||||
@ -206,7 +198,7 @@ int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags _U_,
|
||||
|
||||
copy_pri_spec = *pri_spec;
|
||||
|
||||
adjust_priority_spec_weight(©_pri_spec);
|
||||
nghttp2_priority_spec_normalize_weight(©_pri_spec);
|
||||
|
||||
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
|
||||
|
||||
|
@ -288,6 +288,10 @@ cdef extern from 'nghttp2/nghttp2.h':
|
||||
|
||||
const char* nghttp2_strerror(int lib_error_code)
|
||||
|
||||
int nghttp2_session_check_server_session(nghttp2_session *session)
|
||||
|
||||
int nghttp2_session_get_stream_remote_close(nghttp2_session *session, int32_t stream_id)
|
||||
|
||||
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
|
||||
size_t deflate_hd_table_bufsize_max)
|
||||
|
||||
|
@ -473,6 +473,13 @@ cdef int server_on_frame_send(cnghttp2.nghttp2_session *session,
|
||||
if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0:
|
||||
return 0
|
||||
http2._start_settings_timer()
|
||||
elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
|
||||
if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM) and \
|
||||
cnghttp2.nghttp2_session_check_server_session(session):
|
||||
# Send RST_STREAM if remote is not closed yet
|
||||
if cnghttp2.nghttp2_session_get_stream_remote_close(
|
||||
session, frame.hd.stream_id) == 0:
|
||||
http2._rst_stream(frame.hd.stream_id, cnghttp2.NGHTTP2_NO_ERROR)
|
||||
|
||||
cdef int server_on_frame_not_send(cnghttp2.nghttp2_session *session,
|
||||
const cnghttp2.nghttp2_frame *frame,
|
||||
@ -539,6 +546,11 @@ cdef ssize_t data_source_read(cnghttp2.nghttp2_session *session,
|
||||
|
||||
if flag == DATA_EOF:
|
||||
data_flags[0] = cnghttp2.NGHTTP2_DATA_FLAG_EOF
|
||||
if cnghttp2.nghttp2_session_check_server_session(session):
|
||||
# Send RST_STREAM if remote is not closed yet
|
||||
if cnghttp2.nghttp2_session_get_stream_remote_close(
|
||||
session, stream_id) == 0:
|
||||
http2._rst_stream(stream_id, cnghttp2.NGHTTP2_NO_ERROR)
|
||||
elif flag != DATA_OK:
|
||||
return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
|
||||
|
||||
|
@ -175,6 +175,51 @@ namespace {
|
||||
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr ev_tstamp RELEASE_FD_TIMEOUT = 2.;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr ev_tstamp FILE_ENTRY_MAX_AGE = 10.;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr size_t FILE_ENTRY_EVICT_THRES = 2048;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
bool need_validation_file_entry(const FileEntry *ent, ev_tstamp now) {
|
||||
return ent->last_valid + FILE_ENTRY_MAX_AGE < now;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
bool validate_file_entry(FileEntry *ent, ev_tstamp now) {
|
||||
struct stat stbuf;
|
||||
int rv;
|
||||
|
||||
rv = fstat(ent->fd, &stbuf);
|
||||
if (rv != 0) {
|
||||
ent->stale = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stbuf.st_nlink == 0 || ent->mtime != stbuf.st_mtime) {
|
||||
ent->stale = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ent->mtime = stbuf.st_mtime;
|
||||
ent->last_valid = now;
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class Sessions {
|
||||
public:
|
||||
Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
|
||||
@ -185,15 +230,24 @@ public:
|
||||
nghttp2_session_callbacks_new(&callbacks_);
|
||||
|
||||
fill_callback(callbacks_, config_);
|
||||
|
||||
ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT);
|
||||
release_fd_timer_.data = this;
|
||||
}
|
||||
~Sessions() {
|
||||
ev_timer_stop(loop_, &release_fd_timer_);
|
||||
for (auto handler : handlers_) {
|
||||
delete handler;
|
||||
}
|
||||
nghttp2_session_callbacks_del(callbacks_);
|
||||
}
|
||||
void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
|
||||
void remove_handler(Http2Handler *handler) { handlers_.erase(handler); }
|
||||
void remove_handler(Http2Handler *handler) {
|
||||
handlers_.erase(handler);
|
||||
if (handlers_.empty() && !fd_cache_.empty()) {
|
||||
ev_timer_again(loop_, &release_fd_timer_);
|
||||
}
|
||||
}
|
||||
SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }
|
||||
SSL *ssl_session_new(int fd) {
|
||||
SSL *ssl = SSL_new(ssl_ctx_);
|
||||
@ -252,50 +306,122 @@ public:
|
||||
return cached_date_;
|
||||
}
|
||||
FileEntry *get_cached_fd(const std::string &path) {
|
||||
auto i = fd_cache_.find(path);
|
||||
if (i == std::end(fd_cache_)) {
|
||||
auto range = fd_cache_.equal_range(path);
|
||||
if (range.first == range.second) {
|
||||
return nullptr;
|
||||
}
|
||||
auto &ent = (*i).second;
|
||||
++ent.usecount;
|
||||
return &ent;
|
||||
|
||||
auto now = ev_now(loop_);
|
||||
|
||||
for (auto it = range.first; it != range.second;) {
|
||||
auto &ent = (*it).second;
|
||||
if (ent->stale) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (need_validation_file_entry(ent.get(), now) &&
|
||||
!validate_file_entry(ent.get(), now)) {
|
||||
if (ent->usecount == 0) {
|
||||
fd_cache_lru_.remove(ent.get());
|
||||
close(ent->fd);
|
||||
it = fd_cache_.erase(it);
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
fd_cache_lru_.remove(ent.get());
|
||||
fd_cache_lru_.append(ent.get());
|
||||
|
||||
++ent->usecount;
|
||||
return ent.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
FileEntry *cache_fd(const std::string &path, const FileEntry &ent) {
|
||||
#ifdef HAVE_STD_MAP_EMPLACE
|
||||
auto rv = fd_cache_.emplace(path, ent);
|
||||
auto rv = fd_cache_.emplace(path, make_unique<FileEntry>(ent));
|
||||
#else // !HAVE_STD_MAP_EMPLACE
|
||||
// for gcc-4.7
|
||||
auto rv = fd_cache_.insert(std::make_pair(path, ent));
|
||||
auto rv =
|
||||
fd_cache_.insert(std::make_pair(path, make_unique<FileEntry>(ent)));
|
||||
#endif // !HAVE_STD_MAP_EMPLACE
|
||||
return &(*rv.first).second;
|
||||
auto &res = (*rv).second;
|
||||
res->it = rv;
|
||||
fd_cache_lru_.append(res.get());
|
||||
|
||||
while (fd_cache_.size() > FILE_ENTRY_EVICT_THRES) {
|
||||
auto ent = fd_cache_lru_.head;
|
||||
if (ent->usecount) {
|
||||
break;
|
||||
}
|
||||
fd_cache_lru_.remove(ent);
|
||||
close(ent->fd);
|
||||
fd_cache_.erase(ent->it);
|
||||
}
|
||||
|
||||
return res.get();
|
||||
}
|
||||
void release_fd(const std::string &path) {
|
||||
auto i = fd_cache_.find(path);
|
||||
if (i == std::end(fd_cache_)) {
|
||||
void release_fd(FileEntry *target) {
|
||||
--target->usecount;
|
||||
|
||||
if (target->usecount == 0 && target->stale) {
|
||||
fd_cache_lru_.remove(target);
|
||||
close(target->fd);
|
||||
fd_cache_.erase(target->it);
|
||||
return;
|
||||
}
|
||||
auto &ent = (*i).second;
|
||||
if (--ent.usecount == 0) {
|
||||
close(ent.fd);
|
||||
fd_cache_.erase(i);
|
||||
|
||||
// We use timer to close file descriptor and delete the entry from
|
||||
// cache. The timer will be started when there is no handler.
|
||||
}
|
||||
void release_unused_fd() {
|
||||
for (auto i = std::begin(fd_cache_); i != std::end(fd_cache_);) {
|
||||
auto &ent = (*i).second;
|
||||
if (ent->usecount != 0) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
fd_cache_lru_.remove(ent.get());
|
||||
close(ent->fd);
|
||||
i = fd_cache_.erase(i);
|
||||
}
|
||||
}
|
||||
const HttpServer *get_server() const { return sv_; }
|
||||
bool handlers_empty() const { return handlers_.empty(); }
|
||||
|
||||
private:
|
||||
std::set<Http2Handler *> handlers_;
|
||||
// cache for file descriptors to read file.
|
||||
std::map<std::string, FileEntry> fd_cache_;
|
||||
std::multimap<std::string, std::unique_ptr<FileEntry>> fd_cache_;
|
||||
DList<FileEntry> fd_cache_lru_;
|
||||
HttpServer *sv_;
|
||||
struct ev_loop *loop_;
|
||||
const Config *config_;
|
||||
SSL_CTX *ssl_ctx_;
|
||||
nghttp2_session_callbacks *callbacks_;
|
||||
ev_timer release_fd_timer_;
|
||||
int64_t next_session_id_;
|
||||
ev_tstamp tstamp_cached_;
|
||||
std::string cached_date_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto sessions = static_cast<Sessions *>(w->data);
|
||||
|
||||
ev_timer_stop(loop, w);
|
||||
|
||||
if (!sessions->handlers_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sessions->release_unused_fd();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
||||
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
|
||||
stream_id(stream_id), echo_upload(false) {
|
||||
@ -313,7 +439,7 @@ Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
||||
Stream::~Stream() {
|
||||
if (file_ent != nullptr) {
|
||||
auto sessions = handler->get_sessions();
|
||||
sessions->release_fd(file_ent->path);
|
||||
sessions->release_fd(file_ent);
|
||||
}
|
||||
|
||||
auto loop = handler->get_loop();
|
||||
@ -979,8 +1105,9 @@ bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) {
|
||||
unlink(tempfn);
|
||||
// Ordinary request never start with "echo:". The length is 0 for
|
||||
// now. We will update it when we get whole request body.
|
||||
stream->file_ent = sessions->cache_fd(std::string("echo:") + tempfn,
|
||||
FileEntry(tempfn, 0, 0, fd, nullptr));
|
||||
auto path = std::string("echo:") + tempfn;
|
||||
stream->file_ent =
|
||||
sessions->cache_fd(path, FileEntry(path, 0, 0, fd, nullptr, 0, true));
|
||||
stream->echo_upload = true;
|
||||
return true;
|
||||
}
|
||||
@ -1016,8 +1143,13 @@ namespace {
|
||||
void prepare_response(Stream *stream, Http2Handler *hd,
|
||||
bool allow_push = true) {
|
||||
int rv;
|
||||
auto reqpath =
|
||||
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value;
|
||||
auto pathhdr =
|
||||
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers);
|
||||
if (!pathhdr) {
|
||||
prepare_status_response(stream, hd, 405);
|
||||
return;
|
||||
}
|
||||
auto reqpath = pathhdr->value;
|
||||
auto ims =
|
||||
get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers);
|
||||
|
||||
@ -1042,10 +1174,10 @@ void prepare_response(Stream *stream, Http2Handler *hd,
|
||||
|
||||
auto sessions = hd->get_sessions();
|
||||
|
||||
url = util::percentDecode(std::begin(url), std::end(url));
|
||||
url = util::percent_decode(std::begin(url), std::end(url));
|
||||
if (!util::check_path(url)) {
|
||||
if (stream->file_ent) {
|
||||
sessions->release_fd(stream->file_ent->path);
|
||||
sessions->release_fd(stream->file_ent);
|
||||
stream->file_ent = nullptr;
|
||||
}
|
||||
prepare_status_response(stream, hd, 404);
|
||||
@ -1096,7 +1228,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
|
||||
close(file);
|
||||
|
||||
if (query_pos == std::string::npos) {
|
||||
reqpath += "/";
|
||||
reqpath += '/';
|
||||
} else {
|
||||
reqpath.insert(query_pos, "/");
|
||||
}
|
||||
@ -1127,7 +1259,8 @@ void prepare_response(Stream *stream, Http2Handler *hd,
|
||||
}
|
||||
|
||||
file_ent = sessions->cache_fd(
|
||||
path, FileEntry(path, buf.st_size, buf.st_mtime, file, content_type));
|
||||
path, FileEntry(path, buf.st_size, buf.st_mtime, file, content_type,
|
||||
ev_now(sessions->get_loop())));
|
||||
}
|
||||
|
||||
stream->file_ent = file_ent;
|
||||
@ -1651,7 +1784,7 @@ FileEntry make_status_body(int status, uint16_t port) {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
return FileEntry(util::utos(status), nwrite, 0, fd, nullptr);
|
||||
return FileEntry(util::utos(status), nwrite, 0, fd, nullptr, 0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -1661,6 +1794,7 @@ enum {
|
||||
IDX_301,
|
||||
IDX_400,
|
||||
IDX_404,
|
||||
IDX_405,
|
||||
};
|
||||
|
||||
HttpServer::HttpServer(const Config *config) : config_(config) {
|
||||
@ -1669,6 +1803,7 @@ HttpServer::HttpServer(const Config *config) : config_(config) {
|
||||
{"301", make_status_body(301, config_->port)},
|
||||
{"400", make_status_body(400, config_->port)},
|
||||
{"404", make_status_body(404, config_->port)},
|
||||
{"405", make_status_body(405, config_->port)},
|
||||
};
|
||||
}
|
||||
|
||||
@ -1921,6 +2056,8 @@ const StatusPage *HttpServer::get_status_page(int status) const {
|
||||
return &status_pages_[IDX_400];
|
||||
case 404:
|
||||
return &status_pages_[IDX_404];
|
||||
case 405:
|
||||
return &status_pages_[IDX_405];
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
@ -84,16 +84,21 @@ class Http2Handler;
|
||||
|
||||
struct FileEntry {
|
||||
FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
|
||||
const std::string *content_type)
|
||||
: path(std::move(path)), length(length), mtime(mtime), dlprev(nullptr),
|
||||
dlnext(nullptr), content_type(content_type), fd(fd), usecount(1) {}
|
||||
const std::string *content_type, ev_tstamp last_valid,
|
||||
bool stale = false)
|
||||
: path(std::move(path)), length(length), mtime(mtime),
|
||||
last_valid(last_valid), content_type(content_type), dlnext(nullptr),
|
||||
dlprev(nullptr), fd(fd), usecount(1), stale(stale) {}
|
||||
std::string path;
|
||||
std::multimap<std::string, std::unique_ptr<FileEntry>>::iterator it;
|
||||
int64_t length;
|
||||
int64_t mtime;
|
||||
FileEntry *dlprev, *dlnext;
|
||||
ev_tstamp last_valid;
|
||||
const std::string *content_type;
|
||||
FileEntry *dlnext, *dlprev;
|
||||
int fd;
|
||||
int usecount;
|
||||
bool stale;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
|
@ -420,10 +420,10 @@ const request *session_impl::submit(boost::system::error_code &ec,
|
||||
|
||||
if (util::ipv6_numeric_addr(uref.host.c_str())) {
|
||||
uref.host = "[" + uref.host;
|
||||
uref.host += "]";
|
||||
uref.host += ']';
|
||||
}
|
||||
if (u.field_set & (1 << UF_PORT)) {
|
||||
uref.host += ":";
|
||||
uref.host += ':';
|
||||
uref.host += util::utos(u.port);
|
||||
}
|
||||
|
||||
@ -435,7 +435,7 @@ const request *session_impl::submit(boost::system::error_code &ec,
|
||||
|
||||
auto path = uref.raw_path;
|
||||
if (u.field_set & (1 << UF_QUERY)) {
|
||||
path += "?";
|
||||
path += '?';
|
||||
path += uref.raw_query;
|
||||
}
|
||||
|
||||
@ -525,7 +525,7 @@ void session_impl::do_read() {
|
||||
read_socket([this](const boost::system::error_code &ec,
|
||||
std::size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
if (ec.value() == boost::asio::error::operation_aborted) {
|
||||
if (!should_stop()) {
|
||||
call_error_cb(ec);
|
||||
shutdown_socket();
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ generator_cb file_generator_from_fd(int fd) {
|
||||
bool check_path(const std::string &path) { return util::check_path(path); }
|
||||
|
||||
std::string percent_decode(const std::string &s) {
|
||||
return util::percentDecode(std::begin(s), std::end(s));
|
||||
return util::percent_decode(std::begin(s), std::end(s));
|
||||
}
|
||||
|
||||
std::string http_date(int64_t t) { return util::http_date(t); }
|
||||
@ -177,9 +177,11 @@ bool tls_h2_negotiated(ssl_socket &socket) {
|
||||
unsigned int next_proto_len = 0;
|
||||
|
||||
SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
if (next_proto == nullptr) {
|
||||
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
|
||||
}
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
|
||||
if (next_proto == nullptr) {
|
||||
return false;
|
||||
|
@ -55,7 +55,7 @@ void split_path(uri_ref &dst, InputIt first, InputIt last) {
|
||||
} else {
|
||||
query_first = path_last + 1;
|
||||
}
|
||||
dst.path = util::percentDecode(first, path_last);
|
||||
dst.path = util::percent_decode(first, path_last);
|
||||
dst.raw_path.assign(first, path_last);
|
||||
dst.raw_query.assign(query_first, last);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ request_cb serve_mux::handler(request_impl &req) const {
|
||||
auto new_uri = util::percent_encode_path(clean_path);
|
||||
auto &uref = req.uri();
|
||||
if (!uref.raw_query.empty()) {
|
||||
new_uri += "?";
|
||||
new_uri += '?';
|
||||
new_uri += uref.raw_query;
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ bool path_match(const std::string &pattern, const std::string &path) {
|
||||
if (pattern.back() != '/') {
|
||||
return pattern == path;
|
||||
}
|
||||
return util::startsWith(path, pattern);
|
||||
return util::starts_with(path, pattern);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
264
src/h2load.cc
264
src/h2load.cc
@ -71,6 +71,12 @@ using namespace nghttp2;
|
||||
|
||||
namespace h2load {
|
||||
|
||||
namespace {
|
||||
bool recorded(const std::chrono::steady_clock::time_point &t) {
|
||||
return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Config::Config()
|
||||
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
|
||||
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
|
||||
@ -92,11 +98,11 @@ Config config;
|
||||
|
||||
RequestStat::RequestStat() : data_offset(0), completed(false) {}
|
||||
|
||||
Stats::Stats(size_t req_todo)
|
||||
Stats::Stats(size_t req_todo, size_t nclients)
|
||||
: req_todo(0), req_started(0), req_done(0), req_success(0),
|
||||
req_status_success(0), req_failed(0), req_error(0), req_timedout(0),
|
||||
bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
|
||||
status(), req_stats(req_todo) {}
|
||||
status(), req_stats(req_todo), client_stats(nclients) {}
|
||||
|
||||
Stream::Stream() : status_success(-1) {}
|
||||
|
||||
@ -149,7 +155,8 @@ void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
++req_todo;
|
||||
--worker->nreqs_rem;
|
||||
}
|
||||
worker->clients.push_back(make_unique<Client>(worker, req_todo));
|
||||
worker->clients.push_back(
|
||||
make_unique<Client>(worker->next_client_id++, worker, req_todo));
|
||||
auto &client = worker->clients.back();
|
||||
if (client->connect() != 0) {
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
@ -232,11 +239,11 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Client::Client(Worker *worker, size_t req_todo)
|
||||
Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
||||
: worker(worker), ssl(nullptr), next_addr(config.addrs),
|
||||
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE),
|
||||
first_byte_received(false), req_todo(req_todo), req_started(0),
|
||||
req_done(0), fd(-1), new_connection_requested(false) {
|
||||
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
|
||||
req_started(0), req_done(0), id(id), fd(-1),
|
||||
new_connection_requested(false) {
|
||||
ev_io_init(&wev, writecb, 0, EV_WRITE);
|
||||
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||
|
||||
@ -302,7 +309,9 @@ int Client::make_socket(addrinfo *addr) {
|
||||
int Client::connect() {
|
||||
int rv;
|
||||
|
||||
record_start_time(&worker->stats);
|
||||
record_client_start_time();
|
||||
clear_connect_times();
|
||||
record_connect_start_time();
|
||||
|
||||
if (worker->config->conn_inactivity_timeout > 0.) {
|
||||
ev_timer_again(worker->loop, &conn_inactivity_watcher);
|
||||
@ -360,23 +369,37 @@ void Client::fail() {
|
||||
|
||||
if (new_connection_requested) {
|
||||
new_connection_requested = false;
|
||||
if (req_started < req_todo) {
|
||||
// At the moment, we don't have a facility to re-start request
|
||||
// already in in-flight. Make them fail.
|
||||
auto req_abandoned = req_started - req_done;
|
||||
|
||||
// Keep using current address
|
||||
if (connect() == 0) {
|
||||
return;
|
||||
worker->stats.req_failed += req_abandoned;
|
||||
worker->stats.req_error += req_abandoned;
|
||||
worker->stats.req_done += req_abandoned;
|
||||
|
||||
req_done = req_started;
|
||||
|
||||
// Keep using current address
|
||||
if (connect() == 0) {
|
||||
return;
|
||||
}
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
}
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
}
|
||||
|
||||
process_abandoned_streams();
|
||||
}
|
||||
|
||||
void Client::disconnect() {
|
||||
record_client_end_time();
|
||||
|
||||
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
|
||||
ev_timer_stop(worker->loop, &conn_active_watcher);
|
||||
ev_timer_stop(worker->loop, &request_timeout_watcher);
|
||||
streams.clear();
|
||||
session.reset();
|
||||
wb.reset();
|
||||
state = CLIENT_IDLE;
|
||||
ev_io_stop(worker->loop, &wev);
|
||||
ev_io_stop(worker->loop, &rev);
|
||||
@ -590,6 +613,8 @@ void Client::on_stream_close(int32_t stream_id, bool success,
|
||||
if (success) {
|
||||
req_stat->completed = true;
|
||||
++worker->stats.req_success;
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
++cstat.req_success;
|
||||
}
|
||||
++worker->stats.req_done;
|
||||
++req_done;
|
||||
@ -720,7 +745,7 @@ int Client::connection_made() {
|
||||
|
||||
session->on_connect();
|
||||
|
||||
record_connect_time(&worker->stats);
|
||||
record_connect_time();
|
||||
|
||||
if (!config.timing_script) {
|
||||
auto nreq =
|
||||
@ -742,10 +767,19 @@ int Client::connection_made() {
|
||||
break;
|
||||
}
|
||||
duration = config.timings[reqidx];
|
||||
if (reqidx == 0) {
|
||||
// if reqidx wraps around back to 0, we uses up all lines and
|
||||
// should break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
request_timeout_watcher.repeat = duration;
|
||||
ev_timer_again(worker->loop, &request_timeout_watcher);
|
||||
if (duration >= 1e-9) {
|
||||
// double check since we may have break due to reqidx wraps
|
||||
// around back to 0
|
||||
request_timeout_watcher.repeat = duration;
|
||||
ev_timer_again(worker->loop, &request_timeout_watcher);
|
||||
}
|
||||
}
|
||||
signal_write();
|
||||
|
||||
@ -950,21 +984,51 @@ void Client::record_request_time(RequestStat *req_stat) {
|
||||
req_stat->request_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_start_time(Stats *stat) {
|
||||
stat->start_times.push_back(std::chrono::steady_clock::now());
|
||||
void Client::record_connect_start_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
cstat.connect_start_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_connect_time(Stats *stat) {
|
||||
stat->connect_times.push_back(std::chrono::steady_clock::now());
|
||||
void Client::record_connect_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
cstat.connect_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_ttfb() {
|
||||
if (first_byte_received) {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
if (recorded(cstat.ttfb)) {
|
||||
return;
|
||||
}
|
||||
first_byte_received = true;
|
||||
|
||||
worker->stats.ttfbs.push_back(std::chrono::steady_clock::now());
|
||||
cstat.ttfb = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::clear_connect_times() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
cstat.connect_start_time = std::chrono::steady_clock::time_point();
|
||||
cstat.connect_time = std::chrono::steady_clock::time_point();
|
||||
cstat.ttfb = std::chrono::steady_clock::time_point();
|
||||
}
|
||||
|
||||
void Client::record_client_start_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
// Record start time only once at the very first connection is going
|
||||
// to be made.
|
||||
if (recorded(cstat.client_start_time)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cstat.client_start_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_client_end_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
// Unlike client_start_time, we overwrite client_end_time. This
|
||||
// handles multiple connect/disconnect for HTTP/1.1 benchmark.
|
||||
cstat.client_end_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||
@ -973,10 +1037,11 @@ void Client::try_new_connection() { new_connection_requested = true; }
|
||||
|
||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||
size_t rate, Config *config)
|
||||
: stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
|
||||
id(id), tls_info_report_done(false), app_info_report_done(false),
|
||||
nconns_made(0), nclients(nclients), nreqs_per_client(req_todo / nclients),
|
||||
nreqs_rem(req_todo % nclients), rate(rate) {
|
||||
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
|
||||
config(config), id(id), tls_info_report_done(false),
|
||||
app_info_report_done(false), nconns_made(0), nclients(nclients),
|
||||
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
|
||||
rate(rate), next_client_id(0) {
|
||||
stats.req_todo = req_todo;
|
||||
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
||||
|
||||
@ -992,7 +1057,7 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||
++req_todo;
|
||||
--nreqs_rem;
|
||||
}
|
||||
clients.push_back(make_unique<Client>(this, req_todo));
|
||||
clients.push_back(make_unique<Client>(next_client_id++, this, req_todo));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1025,9 +1090,7 @@ void Worker::run() {
|
||||
|
||||
namespace {
|
||||
// Returns percentage of number of samples within mean +/- sd.
|
||||
template <typename Duration>
|
||||
double within_sd(const std::vector<Duration> &samples, const Duration &mean,
|
||||
const Duration &sd) {
|
||||
double within_sd(const std::vector<double> &samples, double mean, double sd) {
|
||||
if (samples.size() == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
@ -1035,7 +1098,7 @@ double within_sd(const std::vector<Duration> &samples, const Duration &mean,
|
||||
auto upper = mean + sd;
|
||||
auto m = std::count_if(
|
||||
std::begin(samples), std::end(samples),
|
||||
[&lower, &upper](const Duration &t) { return lower <= t && t <= upper; });
|
||||
[&lower, &upper](double t) { return lower <= t && t <= upper; });
|
||||
return (m / static_cast<double>(samples.size())) * 100;
|
||||
}
|
||||
} // namespace
|
||||
@ -1043,32 +1106,31 @@ double within_sd(const std::vector<Duration> &samples, const Duration &mean,
|
||||
namespace {
|
||||
// Computes statistics using |samples|. The min, max, mean, sd, and
|
||||
// percentage of number of samples within mean +/- sd are computed.
|
||||
template <typename Duration>
|
||||
TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
|
||||
SDStat compute_time_stat(const std::vector<double> &samples) {
|
||||
if (samples.empty()) {
|
||||
return {Duration::zero(), Duration::zero(), Duration::zero(),
|
||||
Duration::zero(), 0.0};
|
||||
return {0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
}
|
||||
// standard deviation calculated using Rapid calculation method:
|
||||
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
double a = 0, q = 0;
|
||||
size_t n = 0;
|
||||
int64_t sum = 0;
|
||||
auto res = TimeStat<Duration>{Duration::max(), Duration::min()};
|
||||
double sum = 0;
|
||||
auto res = SDStat{std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::min()};
|
||||
for (const auto &t : samples) {
|
||||
++n;
|
||||
res.min = std::min(res.min, t);
|
||||
res.max = std::max(res.max, t);
|
||||
sum += t.count();
|
||||
sum += t;
|
||||
|
||||
auto na = a + (t.count() - a) / n;
|
||||
q += (t.count() - a) * (t.count() - na);
|
||||
auto na = a + (t - a) / n;
|
||||
q += (t - a) * (t - na);
|
||||
a = na;
|
||||
}
|
||||
|
||||
assert(n > 0);
|
||||
res.mean = Duration(sum / n);
|
||||
res.sd = Duration(static_cast<typename Duration::rep>(sqrt(q / n)));
|
||||
res.mean = sum / n;
|
||||
res.sd = sqrt(q / n);
|
||||
res.within_sd = within_sd(samples, res.mean, res.sd);
|
||||
|
||||
return res;
|
||||
@ -1076,19 +1138,20 @@ TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
TimeStats
|
||||
SDStats
|
||||
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
||||
size_t nrequest_times = 0, nttfb_times = 0;
|
||||
size_t nrequest_times = 0;
|
||||
for (const auto &w : workers) {
|
||||
nrequest_times += w->stats.req_stats.size();
|
||||
nttfb_times += w->stats.ttfbs.size();
|
||||
}
|
||||
|
||||
std::vector<std::chrono::microseconds> request_times;
|
||||
std::vector<double> request_times;
|
||||
request_times.reserve(nrequest_times);
|
||||
std::vector<std::chrono::microseconds> connect_times, ttfb_times;
|
||||
connect_times.reserve(nttfb_times);
|
||||
ttfb_times.reserve(nttfb_times);
|
||||
|
||||
std::vector<double> connect_times, ttfb_times, rps_values;
|
||||
connect_times.reserve(config.nclients);
|
||||
ttfb_times.reserve(config.nclients);
|
||||
rps_values.reserve(config.nclients);
|
||||
|
||||
for (const auto &w : workers) {
|
||||
for (const auto &req_stat : w->stats.req_stats) {
|
||||
@ -1096,28 +1159,44 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
||||
continue;
|
||||
}
|
||||
request_times.push_back(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
req_stat.stream_close_time - req_stat.request_time));
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
req_stat.stream_close_time - req_stat.request_time).count());
|
||||
}
|
||||
|
||||
const auto &stat = w->stats;
|
||||
// rule out cases where we started but didn't connect or get the
|
||||
// first byte (errors). We will get connect event before FFTB.
|
||||
assert(stat.start_times.size() >= stat.ttfbs.size());
|
||||
assert(stat.connect_times.size() >= stat.ttfbs.size());
|
||||
for (size_t i = 0; i < stat.ttfbs.size(); ++i) {
|
||||
|
||||
for (const auto &cstat : stat.client_stats) {
|
||||
if (recorded(cstat.client_start_time) &&
|
||||
recorded(cstat.client_end_time)) {
|
||||
auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
cstat.client_end_time - cstat.client_start_time).count();
|
||||
if (t > 1e-9) {
|
||||
rps_values.push_back(cstat.req_success / t);
|
||||
}
|
||||
}
|
||||
|
||||
// We will get connect event before FFTB.
|
||||
if (!recorded(cstat.connect_start_time) ||
|
||||
!recorded(cstat.connect_time)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connect_times.push_back(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
stat.connect_times[i] - stat.start_times[i]));
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
cstat.connect_time - cstat.connect_start_time).count());
|
||||
|
||||
if (!recorded(cstat.ttfb)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ttfb_times.push_back(
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
stat.ttfbs[i] - stat.start_times[i]));
|
||||
std::chrono::duration_cast<std::chrono::duration<double>>(
|
||||
cstat.ttfb - cstat.connect_start_time).count());
|
||||
}
|
||||
}
|
||||
|
||||
return {compute_time_stat(request_times), compute_time_stat(connect_times),
|
||||
compute_time_stat(ttfb_times)};
|
||||
compute_time_stat(ttfb_times), compute_time_stat(rps_values)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -1156,7 +1235,7 @@ std::string get_reqline(const char *uri, const http_parser_url &u) {
|
||||
}
|
||||
|
||||
if (util::has_uri_field(u, UF_QUERY)) {
|
||||
reqline += "?";
|
||||
reqline += '?';
|
||||
reqline += util::get_uri_field(uri, u, UF_QUERY);
|
||||
}
|
||||
|
||||
@ -1469,6 +1548,9 @@ Options:
|
||||
only and any white spaces are treated as a part of
|
||||
protocol string.
|
||||
Default: )" << DEFAULT_NPN_LIST << R"(
|
||||
--h1 Short hand for --npn-list=http/1.1
|
||||
--no-tls-proto=http/1.1, which effectively force
|
||||
http/1.1 for both http and https URI.
|
||||
-v, --verbose
|
||||
Output debug information.
|
||||
--version Display version information and exit.
|
||||
@ -1516,6 +1598,7 @@ int main(int argc, char **argv) {
|
||||
{"base-uri", required_argument, nullptr, 'B'},
|
||||
{"npn-list", required_argument, &flag, 4},
|
||||
{"rate-period", required_argument, &flag, 5},
|
||||
{"h1", no_argument, &flag, 6},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
int option_index = 0;
|
||||
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:T:N:B:",
|
||||
@ -1682,6 +1765,11 @@ int main(int argc, char **argv) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
// --h1
|
||||
config.npn_list = util::parse_config_str_list("http/1.1");
|
||||
config.no_tls_proto = Config::PROTO_HTTP1_1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -1893,8 +1981,8 @@ int main(int argc, char **argv) {
|
||||
shared_nva.emplace_back("user-agent", user_agent);
|
||||
|
||||
// list overridalbe headers
|
||||
auto override_hdrs =
|
||||
make_array<std::string>(":authority", ":host", ":method", ":scheme");
|
||||
auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
|
||||
":scheme", "user-agent");
|
||||
|
||||
for (auto &kv : config.custom_headers) {
|
||||
if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
|
||||
@ -1912,20 +2000,44 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
auto method_it =
|
||||
std::find_if(std::begin(shared_nva), std::end(shared_nva),
|
||||
[](const Header &nv) { return nv.name == ":method"; });
|
||||
assert(method_it != std::end(shared_nva));
|
||||
|
||||
config.h1reqs.reserve(reqlines.size());
|
||||
config.nva.reserve(reqlines.size());
|
||||
config.nv.reserve(reqlines.size());
|
||||
|
||||
for (auto &req : reqlines) {
|
||||
// For HTTP/1.1
|
||||
std::string h1req;
|
||||
h1req = config.data_fd == -1 ? "GET" : "POST";
|
||||
h1req += " " + req;
|
||||
auto h1req = (*method_it).value;
|
||||
h1req += ' ';
|
||||
h1req += req;
|
||||
h1req += " HTTP/1.1\r\n";
|
||||
h1req += "Host: " + config.host + "\r\n";
|
||||
h1req += "User-Agent: " + user_agent + "\r\n";
|
||||
h1req += "Accept: */*\r\n";
|
||||
for (auto &nv : shared_nva) {
|
||||
if (nv.name == ":authority") {
|
||||
h1req += "Host: ";
|
||||
h1req += nv.value;
|
||||
h1req += "\r\n";
|
||||
continue;
|
||||
}
|
||||
if (nv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
h1req += nv.name;
|
||||
h1req += ": ";
|
||||
h1req += nv.value;
|
||||
h1req += "\r\n";
|
||||
}
|
||||
h1req += "\r\n";
|
||||
config.h1reqs.push_back(h1req);
|
||||
|
||||
config.h1reqs.push_back(std::move(h1req));
|
||||
|
||||
// For nghttp2
|
||||
std::vector<nghttp2_nv> nva;
|
||||
// 1 for :path
|
||||
nva.reserve(1 + shared_nva.size());
|
||||
|
||||
nva.push_back(http2::make_nv_ls(":path", req));
|
||||
|
||||
@ -1937,6 +2049,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
// For spdylay
|
||||
std::vector<const char *> cva;
|
||||
// 2 for :path and :version, 1 for terminal nullptr
|
||||
cva.reserve(2 * (2 + shared_nva.size()) + 1);
|
||||
|
||||
cva.push_back(":path");
|
||||
cva.push_back(req.c_str());
|
||||
@ -2058,7 +2172,7 @@ int main(int argc, char **argv) {
|
||||
auto duration =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
|
||||
Stats stats(0);
|
||||
Stats stats(0, 0);
|
||||
for (const auto &w : workers) {
|
||||
const auto &s = w->stats;
|
||||
|
||||
@ -2138,7 +2252,11 @@ time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
|
||||
<< util::format_duration(ts.ttfb.max) << " " << std::setw(10)
|
||||
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
|
||||
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
|
||||
<< util::dtos(ts.ttfb.within_sd) << "%" << std::endl;
|
||||
<< util::dtos(ts.ttfb.within_sd) << "%"
|
||||
<< "\nreq/s (client) : " << std::setw(10) << ts.rps.min << " "
|
||||
<< std::setw(10) << ts.rps.max << " " << std::setw(10)
|
||||
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
||||
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
||||
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
|
||||
|
61
src/h2load.h
61
src/h2load.h
@ -124,26 +124,47 @@ struct RequestStat {
|
||||
bool completed;
|
||||
};
|
||||
|
||||
template <typename Duration> struct TimeStat {
|
||||
struct ClientStat {
|
||||
// time client started (i.e., first connect starts)
|
||||
std::chrono::steady_clock::time_point client_start_time;
|
||||
// time client end (i.e., client somehow processed all requests it
|
||||
// is responsible for, and disconnected)
|
||||
std::chrono::steady_clock::time_point client_end_time;
|
||||
// The number of requests completed successfull, but not necessarily
|
||||
// means successful HTTP status code.
|
||||
size_t req_success;
|
||||
|
||||
// The following 3 numbers are overwritten each time when connection
|
||||
// is made.
|
||||
|
||||
// time connect starts
|
||||
std::chrono::steady_clock::time_point connect_start_time;
|
||||
// time to connect
|
||||
std::chrono::steady_clock::time_point connect_time;
|
||||
// time to first byte (TTFB)
|
||||
std::chrono::steady_clock::time_point ttfb;
|
||||
};
|
||||
|
||||
struct SDStat {
|
||||
// min, max, mean and sd (standard deviation)
|
||||
Duration min, max, mean, sd;
|
||||
double min, max, mean, sd;
|
||||
// percentage of samples inside mean -/+ sd
|
||||
double within_sd;
|
||||
};
|
||||
|
||||
struct TimeStats {
|
||||
struct SDStats {
|
||||
// time for request
|
||||
TimeStat<std::chrono::microseconds> request;
|
||||
SDStat request;
|
||||
// time for connect
|
||||
TimeStat<std::chrono::microseconds> connect;
|
||||
SDStat connect;
|
||||
// time to first byte (TTFB)
|
||||
TimeStat<std::chrono::microseconds> ttfb;
|
||||
SDStat ttfb;
|
||||
// request per second for each client
|
||||
SDStat rps;
|
||||
};
|
||||
|
||||
enum TimeStatType { STAT_REQUEST, STAT_CONNECT, STAT_FIRST_BYTE };
|
||||
|
||||
struct Stats {
|
||||
Stats(size_t req_todo);
|
||||
Stats(size_t req_todo, size_t nclients);
|
||||
// The total number of requests
|
||||
size_t req_todo;
|
||||
// The number of requests issued so far
|
||||
@ -179,12 +200,8 @@ struct Stats {
|
||||
std::array<size_t, 6> status;
|
||||
// The statistics per request
|
||||
std::vector<RequestStat> req_stats;
|
||||
// time connect starts
|
||||
std::vector<std::chrono::steady_clock::time_point> start_times;
|
||||
// time to connect
|
||||
std::vector<std::chrono::steady_clock::time_point> connect_times;
|
||||
// time to first byte (TTFB)
|
||||
std::vector<std::chrono::steady_clock::time_point> ttfbs;
|
||||
// THe statistics per client
|
||||
std::vector<ClientStat> client_stats;
|
||||
};
|
||||
|
||||
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
||||
@ -210,6 +227,8 @@ struct Worker {
|
||||
size_t nreqs_rem;
|
||||
size_t rate;
|
||||
ev_timer timeout_watcher;
|
||||
// The next client ID this worker assigns
|
||||
uint32_t next_client_id;
|
||||
|
||||
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
|
||||
size_t rate, Config *config);
|
||||
@ -240,13 +259,14 @@ struct Client {
|
||||
addrinfo *current_addr;
|
||||
size_t reqidx;
|
||||
ClientState state;
|
||||
bool first_byte_received;
|
||||
// The number of requests this client has to issue.
|
||||
size_t req_todo;
|
||||
// The number of requests this client has issued so far.
|
||||
size_t req_started;
|
||||
// The number of requests this client has done so far.
|
||||
size_t req_done;
|
||||
// The client id per worker
|
||||
uint32_t id;
|
||||
int fd;
|
||||
Buffer<64_k> wb;
|
||||
ev_timer conn_active_watcher;
|
||||
@ -256,7 +276,7 @@ struct Client {
|
||||
|
||||
enum { ERR_CONNECT_FAIL = -100 };
|
||||
|
||||
Client(Worker *worker, size_t req_todo);
|
||||
Client(uint32_t id, Worker *worker, size_t req_todo);
|
||||
~Client();
|
||||
int make_socket(addrinfo *addr);
|
||||
int connect();
|
||||
@ -302,9 +322,12 @@ struct Client {
|
||||
bool final = false);
|
||||
|
||||
void record_request_time(RequestStat *req_stat);
|
||||
void record_start_time(Stats *stat);
|
||||
void record_connect_time(Stats *stat);
|
||||
void record_connect_start_time();
|
||||
void record_connect_time();
|
||||
void record_ttfb();
|
||||
void clear_connect_times();
|
||||
void record_client_start_time();
|
||||
void record_client_end_time();
|
||||
|
||||
void signal_write();
|
||||
};
|
||||
|
@ -51,7 +51,15 @@ Http1Session::~Http1Session() {}
|
||||
|
||||
namespace {
|
||||
// HTTP response message begin
|
||||
int htp_msg_begincb(http_parser *htp) { return 0; }
|
||||
int htp_msg_begincb(http_parser *htp) {
|
||||
auto session = static_cast<Http1Session *>(htp->data);
|
||||
|
||||
if (session->stream_resp_counter_ >= session->stream_req_counter_) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
@ -144,7 +152,7 @@ void Http1Session::on_connect() { client_->signal_write(); }
|
||||
|
||||
int Http1Session::submit_request(RequestStat *req_stat) {
|
||||
auto config = client_->worker->config;
|
||||
auto req = config->h1reqs[client_->reqidx];
|
||||
const auto &req = config->h1reqs[client_->reqidx];
|
||||
client_->reqidx++;
|
||||
|
||||
if (client_->reqidx == config->h1reqs.size()) {
|
||||
|
16
src/http2.cc
16
src/http2.cc
@ -454,8 +454,8 @@ std::string rewrite_location_uri(const std::string &uri,
|
||||
return "";
|
||||
}
|
||||
auto field = &u.field_data[UF_HOST];
|
||||
if (!util::startsWith(std::begin(match_host), std::end(match_host),
|
||||
&uri[field->off], &uri[field->off] + field->len) ||
|
||||
if (!util::starts_with(std::begin(match_host), std::end(match_host),
|
||||
&uri[field->off], &uri[field->off] + field->len) ||
|
||||
(match_host.size() != field->len && match_host[field->len] != ':')) {
|
||||
return "";
|
||||
}
|
||||
@ -471,12 +471,12 @@ std::string rewrite_location_uri(const std::string &uri,
|
||||
}
|
||||
if (u.field_set & (1 << UF_QUERY)) {
|
||||
field = &u.field_data[UF_QUERY];
|
||||
res += "?";
|
||||
res += '?';
|
||||
res.append(&uri[field->off], field->len);
|
||||
}
|
||||
if (u.field_set & (1 << UF_FRAGMENT)) {
|
||||
field = &u.field_data[UF_FRAGMENT];
|
||||
res += "#";
|
||||
res += '#';
|
||||
res.append(&uri[field->off], field->len);
|
||||
}
|
||||
return res;
|
||||
@ -1185,12 +1185,12 @@ std::string path_join(const char *base_path, size_t base_pathlen,
|
||||
}
|
||||
if (rel_querylen == 0) {
|
||||
if (base_querylen) {
|
||||
res += "?";
|
||||
res += '?';
|
||||
res.append(base_query, base_querylen);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
res += "?";
|
||||
res += '?';
|
||||
res.append(rel_query, rel_querylen);
|
||||
return res;
|
||||
}
|
||||
@ -1242,7 +1242,7 @@ std::string path_join(const char *base_path, size_t base_pathlen,
|
||||
;
|
||||
}
|
||||
if (rel_querylen) {
|
||||
res += "?";
|
||||
res += '?';
|
||||
res.append(rel_query, rel_querylen);
|
||||
}
|
||||
return res;
|
||||
@ -1500,7 +1500,7 @@ int construct_push_component(std::string &scheme, std::string &authority,
|
||||
if (u.field_set & (1 << UF_HOST)) {
|
||||
http2::copy_url_component(authority, &u, UF_HOST, uri);
|
||||
if (u.field_set & (1 << UF_PORT)) {
|
||||
authority += ":";
|
||||
authority += ':';
|
||||
authority += util::utos(u.port);
|
||||
}
|
||||
}
|
||||
|
@ -340,10 +340,11 @@ std::string normalize_path(InputIt first, InputIt last) {
|
||||
} else {
|
||||
for (; first < last - 2;) {
|
||||
if (*first == '%') {
|
||||
if (util::isHexDigit(*(first + 1)) && util::isHexDigit(*(first + 2))) {
|
||||
if (util::is_hex_digit(*(first + 1)) &&
|
||||
util::is_hex_digit(*(first + 2))) {
|
||||
auto c = (util::hex_to_uint(*(first + 1)) << 4) +
|
||||
util::hex_to_uint(*(first + 2));
|
||||
if (util::inRFC3986UnreservedChars(c)) {
|
||||
if (util::in_rfc3986_unreserved_chars(c)) {
|
||||
result += c;
|
||||
first += 3;
|
||||
continue;
|
||||
|
@ -95,8 +95,7 @@ constexpr auto anchors = std::array<Anchor, 5>{{
|
||||
Config::Config()
|
||||
: header_table_size(-1),
|
||||
min_header_table_size(std::numeric_limits<uint32_t>::max()), padding(0),
|
||||
max_concurrent_streams(100),
|
||||
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
|
||||
max_concurrent_streams(100), peer_max_concurrent_streams(100),
|
||||
weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), timeout(0.), window_bits(-1),
|
||||
connection_window_bits(-1), verbose(0), null_out(false),
|
||||
remote_name(false), get_assets(false), stat(false), upgrade(false),
|
||||
@ -168,7 +167,7 @@ std::string Request::make_reqpath() const {
|
||||
? util::get_uri_field(uri.c_str(), u, UF_PATH)
|
||||
: "/";
|
||||
if (util::has_uri_field(u, UF_QUERY)) {
|
||||
path += "?";
|
||||
path += '?';
|
||||
path.append(uri.c_str() + u.field_data[UF_QUERY].off,
|
||||
u.field_data[UF_QUERY].len);
|
||||
}
|
||||
@ -199,7 +198,7 @@ std::string decode_host(std::string host) {
|
||||
auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
|
||||
? zone_start + 3
|
||||
: zone_start + 1;
|
||||
auto zone_id = util::percentDecode(zone_id_src, std::end(host));
|
||||
auto zone_id = util::percent_decode(zone_id_src, std::end(host));
|
||||
host.erase(zone_start + 1, std::end(host));
|
||||
host += zone_id;
|
||||
return host;
|
||||
@ -828,7 +827,7 @@ int HttpClient::on_upgrade_connect() {
|
||||
reqvec[0]->method = "GET";
|
||||
} else {
|
||||
req = (*meth).value;
|
||||
req += " ";
|
||||
req += ' ';
|
||||
reqvec[0]->method = (*meth).value;
|
||||
}
|
||||
req += reqvec[0]->make_reqpath();
|
||||
@ -2449,7 +2448,7 @@ Options:
|
||||
-M, --peer-max-concurrent-streams=<N>
|
||||
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
||||
remote endpoint as if it is received in SETTINGS frame.
|
||||
The default is large enough as it is seen as unlimited.
|
||||
Default: 100
|
||||
-c, --header-table-size=<SIZE>
|
||||
Specify decoder header table size. If this option is
|
||||
used multiple times, and the minimum value among the
|
||||
|
21
src/shrpx.cc
21
src/shrpx.cc
@ -325,11 +325,11 @@ void exec_binary(SignalServer *ssv) {
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < envlen; ++i) {
|
||||
if (util::startsWith(environ[i], ENV_LISTENER4_FD) ||
|
||||
util::startsWith(environ[i], ENV_LISTENER6_FD) ||
|
||||
util::startsWith(environ[i], ENV_PORT) ||
|
||||
util::startsWith(environ[i], ENV_UNIX_FD) ||
|
||||
util::startsWith(environ[i], ENV_UNIX_PATH)) {
|
||||
if (util::starts_with(environ[i], ENV_LISTENER4_FD) ||
|
||||
util::starts_with(environ[i], ENV_LISTENER6_FD) ||
|
||||
util::starts_with(environ[i], ENV_PORT) ||
|
||||
util::starts_with(environ[i], ENV_UNIX_FD) ||
|
||||
util::starts_with(environ[i], ENV_UNIX_PATH)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1480,9 +1480,14 @@ HTTP/2 and SPDY:
|
||||
meant for debugging purpose and not intended to enhance
|
||||
protocol security.
|
||||
--no-server-push
|
||||
Disable HTTP/2 server push. Server push is only
|
||||
supported by default mode and HTTP/2 frontend. SPDY
|
||||
frontend does not support server push.
|
||||
Disable HTTP/2 server push. Server push is supported by
|
||||
default mode and HTTP/2 frontend via Link header field.
|
||||
It is also supported if both frontend and backend are
|
||||
HTTP/2 (which implies --http2-bridge or --client mode).
|
||||
In this case, server push from backend session is
|
||||
relayed to frontend, and server push via Link header
|
||||
field is also supported. HTTP SPDY frontend does not
|
||||
support server push.
|
||||
|
||||
Mode:
|
||||
(default mode)
|
||||
|
@ -859,13 +859,13 @@ ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
|
||||
}
|
||||
|
||||
if (*p == '0') {
|
||||
if (p + 1 != last && util::isDigit(*(p + 1))) {
|
||||
if (p + 1 != last && util::is_digit(*(p + 1))) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (; p != last && util::isDigit(*p); ++p) {
|
||||
for (; p != last && util::is_digit(*p); ++p) {
|
||||
port *= 10;
|
||||
port += *p - '0';
|
||||
|
||||
|
@ -471,7 +471,7 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
|
||||
|
||||
namespace {
|
||||
bool var_token(char c) {
|
||||
return util::isAlpha(c) || util::isDigit(c) || c == '_';
|
||||
return util::is_alpha(c) || util::is_digit(c) || c == '_';
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -519,7 +519,7 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
|
||||
auto type = log_var_lookup_token(var_name, var_namelen);
|
||||
|
||||
if (type == SHRPX_LOGF_NONE) {
|
||||
if (util::istartsWith(var_name, var_namelen, "http_")) {
|
||||
if (util::istarts_with(var_name, var_namelen, "http_")) {
|
||||
if (util::streq("host", var_name + str_size("http_"),
|
||||
var_namelen - str_size("http_"))) {
|
||||
// Special handling of host header field. We will use
|
||||
@ -596,7 +596,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
||||
// This effectively makes empty pattern to "/".
|
||||
pattern.assign(raw_pattern.first, raw_pattern.second);
|
||||
util::inp_strlower(pattern);
|
||||
pattern += "/";
|
||||
pattern += '/';
|
||||
} else {
|
||||
pattern.assign(raw_pattern.first, slash);
|
||||
util::inp_strlower(pattern);
|
||||
@ -1342,7 +1342,7 @@ int parse_config(const char *opt, const char *optarg,
|
||||
pat_delim = optarg + optarglen;
|
||||
}
|
||||
DownstreamAddr addr;
|
||||
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
|
||||
addr.host = strcopy(path, pat_delim);
|
||||
addr.host_unix = true;
|
||||
@ -1368,7 +1368,7 @@ int parse_config(const char *opt, const char *optarg,
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_FRONTEND: {
|
||||
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
|
||||
mod_config()->host = strcopy(path);
|
||||
mod_config()->port = 0;
|
||||
@ -1664,7 +1664,7 @@ int parse_config(const char *opt, const char *optarg,
|
||||
// Surprisingly, u.field_set & UF_USERINFO is nonzero even if
|
||||
// userinfo component is empty string.
|
||||
if (!val.empty()) {
|
||||
val = util::percentDecode(val.begin(), val.end());
|
||||
val = util::percent_decode(std::begin(val), std::end(val));
|
||||
mod_config()->downstream_http_proxy_userinfo = strcopy(val);
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,8 @@ bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream) {
|
||||
Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
|
||||
bool next_blocked) {
|
||||
// Delete downstream when this function returns.
|
||||
auto delptr = std::unique_ptr<Downstream>(downstream);
|
||||
|
||||
@ -144,7 +145,7 @@ Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ent.num_active >= conn_max_per_host_) {
|
||||
if (!next_blocked || ent.num_active >= conn_max_per_host_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -79,10 +79,12 @@ public:
|
||||
// |host|.
|
||||
bool can_activate(const std::string &host) const;
|
||||
// Removes and frees |downstream| object. If |downstream| is in
|
||||
// Downstream::DISPATCH_ACTIVE, this function may return Downstream
|
||||
// object with the same target host in Downstream::DISPATCH_BLOCKED
|
||||
// if its connection is now not blocked by conn_max_per_host_ limit.
|
||||
Downstream *remove_and_get_blocked(Downstream *downstream);
|
||||
// Downstream::DISPATCH_ACTIVE, and |next_blocked| is true, this
|
||||
// function may return Downstream object with the same target host
|
||||
// in Downstream::DISPATCH_BLOCKED if its connection is now not
|
||||
// blocked by conn_max_per_host_ limit.
|
||||
Downstream *remove_and_get_blocked(Downstream *downstream,
|
||||
bool next_blocked = true);
|
||||
Downstream *get_downstreams() const;
|
||||
HostEntry &find_host_entry(const std::string &host);
|
||||
const std::string &make_host_key(const std::string &host) const;
|
||||
|
@ -55,7 +55,7 @@ std::string create_via_header_value(int major, int minor) {
|
||||
std::string hdrs;
|
||||
hdrs += static_cast<char>(major + '0');
|
||||
if (major < 2) {
|
||||
hdrs += ".";
|
||||
hdrs += '.';
|
||||
hdrs += static_cast<char>(minor + '0');
|
||||
}
|
||||
hdrs += " nghttpx";
|
||||
|
@ -67,14 +67,8 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||
error_code = NGHTTP2_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (downstream_->get_downstream_stream_id() != -1) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
|
||||
<< ", stream_id="
|
||||
<< downstream_->get_downstream_stream_id()
|
||||
<< ", error_code=" << error_code;
|
||||
}
|
||||
|
||||
if (http2session_->get_state() == Http2Session::CONNECTED &&
|
||||
downstream_->get_downstream_stream_id() != -1) {
|
||||
submit_rst_stream(downstream_, error_code);
|
||||
|
||||
http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||
@ -143,7 +137,8 @@ int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream,
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream
|
||||
<< ", stream_id="
|
||||
<< downstream->get_downstream_stream_id();
|
||||
<< downstream->get_downstream_stream_id()
|
||||
<< ", error_code=" << error_code;
|
||||
}
|
||||
rv = http2session_->submit_rst_stream(
|
||||
downstream->get_downstream_stream_id(), error_code);
|
||||
|
@ -520,7 +520,7 @@ int Http2Session::downstream_connect_proxy() {
|
||||
std::string req = "CONNECT ";
|
||||
req += downstream_addr.hostport.get();
|
||||
if (downstream_addr.port == 80 || downstream_addr.port == 443) {
|
||||
req += ":";
|
||||
req += ':';
|
||||
req += util::utos(downstream_addr.port);
|
||||
}
|
||||
req += " HTTP/1.1\r\nHost: ";
|
||||
@ -682,32 +682,43 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||
if (dconn) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
if (downstream && downstream->get_downstream_stream_id() == stream_id) {
|
||||
auto upstream = downstream->get_upstream();
|
||||
|
||||
if (downstream->get_upgraded() &&
|
||||
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||
// For tunneled connection, we have to submit RST_STREAM to
|
||||
// upstream *after* whole response body is sent. We just set
|
||||
// MSG_COMPLETE here. Upstream will take care of that.
|
||||
downstream->get_upstream()->on_downstream_body_complete(downstream);
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
} else if (error_code == NGHTTP2_NO_ERROR) {
|
||||
switch (downstream->get_response_state()) {
|
||||
case Downstream::MSG_COMPLETE:
|
||||
case Downstream::MSG_BAD_HEADER:
|
||||
break;
|
||||
default:
|
||||
if (downstream->get_downstream_stream_id() % 2 == 0 &&
|
||||
downstream->get_request_state() == Downstream::INITIAL) {
|
||||
// Downstream is canceled in backend before it is submitted in
|
||||
// frontend session.
|
||||
|
||||
// This will avoid to send RST_STREAM to backend
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
upstream->cancel_premature_downstream(downstream);
|
||||
} else {
|
||||
if (downstream->get_upgraded() &&
|
||||
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||
// For tunneled connection, we have to submit RST_STREAM to
|
||||
// upstream *after* whole response body is sent. We just set
|
||||
// MSG_COMPLETE here. Upstream will take care of that.
|
||||
downstream->get_upstream()->on_downstream_body_complete(downstream);
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
} else if (error_code == NGHTTP2_NO_ERROR) {
|
||||
switch (downstream->get_response_state()) {
|
||||
case Downstream::MSG_COMPLETE:
|
||||
case Downstream::MSG_BAD_HEADER:
|
||||
break;
|
||||
default:
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
}
|
||||
} else if (downstream->get_response_state() !=
|
||||
Downstream::MSG_BAD_HEADER) {
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
}
|
||||
} else if (downstream->get_response_state() !=
|
||||
Downstream::MSG_BAD_HEADER) {
|
||||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
if (downstream->get_response_state() == Downstream::MSG_RESET &&
|
||||
downstream->get_response_rst_stream_error_code() ==
|
||||
NGHTTP2_NO_ERROR) {
|
||||
downstream->set_response_rst_stream_error_code(error_code);
|
||||
}
|
||||
call_downstream_readcb(http2session, downstream);
|
||||
}
|
||||
if (downstream->get_response_state() == Downstream::MSG_RESET &&
|
||||
downstream->get_response_rst_stream_error_code() ==
|
||||
NGHTTP2_NO_ERROR) {
|
||||
downstream->set_response_rst_stream_error_code(error_code);
|
||||
}
|
||||
call_downstream_readcb(http2session, downstream);
|
||||
// dconn may be deleted
|
||||
}
|
||||
}
|
||||
@ -730,6 +741,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, uint8_t flags,
|
||||
void *user_data) {
|
||||
auto http2session = static_cast<Http2Session *>(user_data);
|
||||
auto sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
if (!sd || !sd->dconn) {
|
||||
@ -740,44 +752,80 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (frame->hd.type != NGHTTP2_HEADERS) {
|
||||
return 0;
|
||||
}
|
||||
switch (frame->hd.type) {
|
||||
case NGHTTP2_HEADERS: {
|
||||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||
!downstream->get_expect_final_response();
|
||||
|
||||
auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
||||
!downstream->get_expect_final_response();
|
||||
if (downstream->get_response_headers_sum() + namelen + valuelen >
|
||||
get_config()->header_field_buffer ||
|
||||
downstream->get_response_headers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream)
|
||||
<< "Too large or many header field size="
|
||||
<< downstream->get_response_headers_sum() + namelen + valuelen
|
||||
<< ", num=" << downstream->get_response_headers().size() + 1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_headers_sum() + namelen + valuelen >
|
||||
get_config()->header_field_buffer ||
|
||||
downstream->get_response_headers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream)
|
||||
<< "Too large or many header field size="
|
||||
<< downstream->get_response_headers_sum() + namelen + valuelen
|
||||
<< ", num=" << downstream->get_response_headers().size() + 1;
|
||||
if (trailer) {
|
||||
// we don't care trailer part exceeds header size limit; just
|
||||
// discard it.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
if (trailer) {
|
||||
// we don't care trailer part exceeds header size limit; just
|
||||
// discard it.
|
||||
// just store header fields for trailer part
|
||||
downstream->add_response_trailer(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
|
||||
if (trailer) {
|
||||
// just store header fields for trailer part
|
||||
downstream->add_response_trailer(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
|
||||
downstream->add_response_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
}
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
||||
auto promised_sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, promised_stream_id));
|
||||
if (!promised_sd || !promised_sd->dconn) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
auto promised_downstream = promised_sd->dconn->get_downstream();
|
||||
|
||||
assert(promised_downstream);
|
||||
|
||||
if (promised_downstream->get_request_headers_sum() + namelen + valuelen >
|
||||
get_config()->header_field_buffer ||
|
||||
promised_downstream->get_request_headers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, promised_downstream)
|
||||
<< "Too large or many header field size="
|
||||
<< promised_downstream->get_request_headers_sum() + namelen +
|
||||
valuelen << ", num="
|
||||
<< promised_downstream->get_request_headers().size() + 1;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
promised_downstream->add_request_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
||||
token);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
downstream->add_response_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@ -786,24 +834,52 @@ namespace {
|
||||
int on_begin_headers_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data) {
|
||||
auto http2session = static_cast<Http2Session *>(user_data);
|
||||
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
|
||||
|
||||
switch (frame->hd.type) {
|
||||
case NGHTTP2_HEADERS: {
|
||||
if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
||||
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) {
|
||||
return 0;
|
||||
}
|
||||
auto sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
if (!sd || !sd->dconn) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
auto downstream = sd->dconn->get_downstream();
|
||||
if (!downstream ||
|
||||
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
auto sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
if (!sd || !sd->dconn) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_INTERNAL_ERROR);
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
||||
auto sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
if (!sd || !sd->dconn) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto downstream = sd->dconn->get_downstream();
|
||||
|
||||
assert(downstream);
|
||||
assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
|
||||
|
||||
if (http2session->handle_downstream_push_promise(downstream,
|
||||
promised_stream_id) != 0) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
auto downstream = sd->dconn->get_downstream();
|
||||
if (!downstream ||
|
||||
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
||||
http2session->submit_rst_stream(frame->hd.stream_id,
|
||||
NGHTTP2_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
@ -976,7 +1052,8 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
|
||||
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
|
||||
frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
|
||||
rv = on_response_headers(http2session, downstream, session, frame);
|
||||
|
||||
if (rv != 0) {
|
||||
@ -1044,17 +1121,47 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
http2session->connection_alive();
|
||||
}
|
||||
return 0;
|
||||
case NGHTTP2_PUSH_PROMISE:
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, http2session)
|
||||
<< "Received downstream PUSH_PROMISE stream_id="
|
||||
<< frame->hd.stream_id
|
||||
<< ", promised_stream_id=" << frame->push_promise.promised_stream_id;
|
||||
<< ", promised_stream_id=" << promised_stream_id;
|
||||
}
|
||||
// We just respond with RST_STREAM.
|
||||
http2session->submit_rst_stream(frame->push_promise.promised_stream_id,
|
||||
NGHTTP2_REFUSED_STREAM);
|
||||
|
||||
auto sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
if (!sd || !sd->dconn) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto downstream = sd->dconn->get_downstream();
|
||||
|
||||
assert(downstream);
|
||||
assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
|
||||
|
||||
auto promised_sd = static_cast<StreamData *>(
|
||||
nghttp2_session_get_stream_user_data(session, promised_stream_id));
|
||||
if (!promised_sd || !promised_sd->dconn) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto promised_downstream = promised_sd->dconn->get_downstream();
|
||||
|
||||
assert(promised_downstream);
|
||||
|
||||
if (http2session->handle_downstream_push_promise_complete(
|
||||
downstream, promised_downstream) != 0) {
|
||||
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -1279,16 +1386,22 @@ int Http2Session::connection_made() {
|
||||
flow_control_ = true;
|
||||
|
||||
std::array<nghttp2_settings_entry, 3> entry;
|
||||
entry[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
entry[0].value = 0;
|
||||
entry[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
entry[1].value = get_config()->http2_max_concurrent_streams;
|
||||
size_t nentry = 2;
|
||||
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
entry[0].value = get_config()->http2_max_concurrent_streams;
|
||||
|
||||
entry[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
entry[2].value = (1 << get_config()->http2_downstream_window_bits) - 1;
|
||||
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
entry[1].value = (1 << get_config()->http2_downstream_window_bits) - 1;
|
||||
|
||||
if (get_config()->no_server_push || get_config()->http2_proxy ||
|
||||
get_config()->client_proxy) {
|
||||
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
entry[nentry].value = 0;
|
||||
++nentry;
|
||||
}
|
||||
|
||||
rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
|
||||
entry.size());
|
||||
nentry);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
@ -1771,4 +1884,96 @@ size_t Http2Session::get_group() const { return group_; }
|
||||
|
||||
size_t Http2Session::get_index() const { return index_; }
|
||||
|
||||
int Http2Session::handle_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id) {
|
||||
auto upstream = downstream->get_upstream();
|
||||
if (!upstream->push_enabled()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto promised_downstream =
|
||||
upstream->on_downstream_push_promise(downstream, promised_stream_id);
|
||||
if (!promised_downstream) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now we have Downstream object for pushed stream.
|
||||
// promised_downstream->get_stream() still returns 0.
|
||||
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto worker = handler->get_worker();
|
||||
|
||||
auto promised_dconn =
|
||||
make_unique<Http2DownstreamConnection>(worker->get_dconn_pool(), this);
|
||||
promised_dconn->set_client_handler(handler);
|
||||
|
||||
auto ptr = promised_dconn.get();
|
||||
|
||||
if (promised_downstream->attach_downstream_connection(
|
||||
std::move(promised_dconn)) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto promised_sd = make_unique<StreamData>();
|
||||
|
||||
nghttp2_session_set_stream_user_data(session_, promised_stream_id,
|
||||
promised_sd.get());
|
||||
|
||||
ptr->attach_stream_data(promised_sd.get());
|
||||
streams_.append(promised_sd.release());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Session::handle_downstream_push_promise_complete(
|
||||
Downstream *downstream, Downstream *promised_downstream) {
|
||||
auto authority =
|
||||
promised_downstream->get_request_header(http2::HD__AUTHORITY);
|
||||
auto path = promised_downstream->get_request_header(http2::HD__PATH);
|
||||
auto method = promised_downstream->get_request_header(http2::HD__METHOD);
|
||||
auto scheme = promised_downstream->get_request_header(http2::HD__SCHEME);
|
||||
|
||||
if (!authority) {
|
||||
authority = promised_downstream->get_request_header(http2::HD_HOST);
|
||||
}
|
||||
|
||||
auto method_token = http2::lookup_method_token(method->value);
|
||||
if (method_token == -1) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "Unrecognized method: " << method->value;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO Rewrite authority if we enabled rewrite host. But we
|
||||
// really don't know how to rewrite host. Should we use the same
|
||||
// host in associated stream?
|
||||
promised_downstream->set_request_http2_authority(
|
||||
http2::value_to_str(authority));
|
||||
promised_downstream->set_request_method(method_token);
|
||||
// libnghttp2 ensures that we don't have CONNECT method in
|
||||
// PUSH_PROMISE, and guarantees that :scheme exists.
|
||||
promised_downstream->set_request_http2_scheme(http2::value_to_str(scheme));
|
||||
|
||||
// For server-wide OPTIONS request, path is empty.
|
||||
if (method_token != HTTP_OPTIONS || path->value != "*") {
|
||||
promised_downstream->set_request_path(http2::rewrite_clean_path(
|
||||
std::begin(path->value), std::end(path->value)));
|
||||
}
|
||||
|
||||
promised_downstream->inspect_http2_request();
|
||||
|
||||
auto upstream = promised_downstream->get_upstream();
|
||||
|
||||
promised_downstream->set_request_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
if (upstream->on_downstream_push_promise_complete(downstream,
|
||||
promised_downstream) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
@ -156,6 +156,11 @@ public:
|
||||
|
||||
size_t get_index() const;
|
||||
|
||||
int handle_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id);
|
||||
int handle_downstream_push_promise_complete(Downstream *downstream,
|
||||
Downstream *promised_downstream);
|
||||
|
||||
enum {
|
||||
// Disconnected
|
||||
DISCONNECTED,
|
||||
|
@ -543,6 +543,13 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
return 0;
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
||||
|
||||
if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) {
|
||||
// In case of push from backend, downstream object was already
|
||||
// created.
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto downstream = make_unique<Downstream>(upstream, handler->get_mcpool(),
|
||||
promised_stream_id, 0);
|
||||
|
||||
@ -1712,6 +1719,7 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
|
||||
}
|
||||
|
||||
if (!downstream->request_submission_ready()) {
|
||||
// pushed stream is handled here
|
||||
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||
downstream->pop_downstream_connection();
|
||||
continue;
|
||||
@ -1798,7 +1806,6 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
|
||||
const std::string &authority,
|
||||
const std::string &path,
|
||||
Downstream *downstream) {
|
||||
int rv;
|
||||
std::vector<nghttp2_nv> nva;
|
||||
// 4 for :method, :scheme, :path and :authority
|
||||
nva.reserve(4 + downstream->get_request_headers().size());
|
||||
@ -1827,16 +1834,16 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
|
||||
}
|
||||
}
|
||||
|
||||
rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
|
||||
downstream->get_stream_id(), nva.data(),
|
||||
nva.size(), nullptr);
|
||||
auto promised_stream_id = nghttp2_submit_push_promise(
|
||||
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
|
||||
nva.size(), nullptr);
|
||||
|
||||
if (rv < 0) {
|
||||
if (promised_stream_id < 0) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
|
||||
<< nghttp2_strerror(rv);
|
||||
<< nghttp2_strerror(promised_stream_id);
|
||||
}
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
if (nghttp2_is_fatal(promised_stream_id)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@ -1847,22 +1854,25 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
|
||||
for (auto &nv : nva) {
|
||||
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
|
||||
}
|
||||
ULOG(INFO, this) << "HTTP push request headers. promised_stream_id=" << rv
|
||||
<< "\n" << ss.str();
|
||||
ULOG(INFO, this) << "HTTP push request headers. promised_stream_id="
|
||||
<< promised_stream_id << "\n" << ss.str();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Http2Upstream::push_enabled() const {
|
||||
return !(get_config()->no_server_push ||
|
||||
nghttp2_session_get_remote_settings(
|
||||
session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
|
||||
get_config()->http2_proxy || get_config()->client_proxy);
|
||||
}
|
||||
|
||||
int Http2Upstream::initiate_push(Downstream *downstream, const char *uri,
|
||||
size_t len) {
|
||||
int rv;
|
||||
|
||||
if (len == 0 || get_config()->no_server_push ||
|
||||
nghttp2_session_get_remote_settings(session_,
|
||||
NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
|
||||
get_config()->http2_proxy || get_config()->client_proxy ||
|
||||
(downstream->get_stream_id() % 2) == 0) {
|
||||
if (len == 0 || !push_enabled() || (downstream->get_stream_id() % 2)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1917,4 +1927,58 @@ bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; }
|
||||
|
||||
Http2Upstream::WriteBuffer *Http2Upstream::get_response_buf() { return &wb_; }
|
||||
|
||||
Downstream *
|
||||
Http2Upstream::on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id) {
|
||||
// promised_stream_id is for backend HTTP/2 session, not for
|
||||
// frontend.
|
||||
auto promised_downstream =
|
||||
make_unique<Downstream>(this, handler_->get_mcpool(), 0, 0);
|
||||
promised_downstream->set_downstream_stream_id(promised_stream_id);
|
||||
|
||||
promised_downstream->disable_upstream_rtimer();
|
||||
|
||||
promised_downstream->set_request_major(2);
|
||||
promised_downstream->set_request_minor(0);
|
||||
|
||||
auto ptr = promised_downstream.get();
|
||||
add_pending_downstream(std::move(promised_downstream));
|
||||
downstream_queue_.mark_active(ptr);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int Http2Upstream::on_downstream_push_promise_complete(
|
||||
Downstream *downstream, Downstream *promised_downstream) {
|
||||
std::vector<nghttp2_nv> nva;
|
||||
|
||||
auto &headers = promised_downstream->get_request_headers();
|
||||
|
||||
nva.reserve(headers.size());
|
||||
|
||||
for (auto &kv : headers) {
|
||||
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
|
||||
}
|
||||
|
||||
auto promised_stream_id = nghttp2_submit_push_promise(
|
||||
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
|
||||
nva.size(), promised_downstream);
|
||||
if (promised_stream_id < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
promised_downstream->set_stream_id(promised_stream_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Http2Upstream::cancel_premature_downstream(
|
||||
Downstream *promised_downstream) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, this) << "Remove premature promised stream "
|
||||
<< promised_downstream;
|
||||
}
|
||||
downstream_queue_.remove_and_get_blocked(promised_downstream, false);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
@ -87,6 +87,14 @@ public:
|
||||
virtual void response_drain(size_t n);
|
||||
virtual bool response_empty() const;
|
||||
|
||||
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id);
|
||||
virtual int
|
||||
on_downstream_push_promise_complete(Downstream *downstream,
|
||||
Downstream *promised_downstream);
|
||||
virtual bool push_enabled() const;
|
||||
virtual void cancel_premature_downstream(Downstream *promised_downstream);
|
||||
|
||||
bool get_flow_control() const;
|
||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||
// takes ownership of the |upstream|. This function returns 0 if it
|
||||
|
@ -1191,4 +1191,20 @@ bool HttpsUpstream::response_empty() const {
|
||||
return buf->rleft() == 0;
|
||||
}
|
||||
|
||||
Downstream *
|
||||
HttpsUpstream::on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int HttpsUpstream::on_downstream_push_promise_complete(
|
||||
Downstream *downstream, Downstream *promised_downstream) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool HttpsUpstream::push_enabled() const { return false; }
|
||||
|
||||
void HttpsUpstream::cancel_premature_downstream(
|
||||
Downstream *promised_downstream) {}
|
||||
|
||||
} // namespace shrpx
|
||||
|
@ -82,6 +82,14 @@ public:
|
||||
virtual void response_drain(size_t n);
|
||||
virtual bool response_empty() const;
|
||||
|
||||
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id);
|
||||
virtual int
|
||||
on_downstream_push_promise_complete(Downstream *downstream,
|
||||
Downstream *promised_downstream);
|
||||
virtual bool push_enabled() const;
|
||||
virtual void cancel_premature_downstream(Downstream *promised_downstream);
|
||||
|
||||
void reset_current_header_length();
|
||||
void log_response_headers(DefaultMemchunks *buf) const;
|
||||
|
||||
|
@ -297,7 +297,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
||||
if (frac.size() < 3) {
|
||||
frac = std::string(3 - frac.size(), '0') + frac;
|
||||
}
|
||||
sec += ".";
|
||||
sec += '.';
|
||||
sec += frac;
|
||||
|
||||
std::tie(p, avail) = copy(sec, avail, p);
|
||||
@ -415,12 +415,12 @@ void log_chld(pid_t pid, int rstatus, const char *msg) {
|
||||
auto s = strsignal(sig);
|
||||
if (s) {
|
||||
signalstr += s;
|
||||
signalstr += "(";
|
||||
signalstr += '(';
|
||||
} else {
|
||||
signalstr += "UNKNOWN(";
|
||||
}
|
||||
signalstr += util::utos(sig);
|
||||
signalstr += ")";
|
||||
signalstr += ')';
|
||||
}
|
||||
|
||||
LOG(NOTICE) << msg << ": [" << pid << "] exited "
|
||||
|
@ -1230,4 +1230,20 @@ bool SpdyUpstream::response_empty() const { return wb_.rleft() == 0; }
|
||||
|
||||
SpdyUpstream::WriteBuffer *SpdyUpstream::get_response_buf() { return &wb_; }
|
||||
|
||||
Downstream *
|
||||
SpdyUpstream::on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int SpdyUpstream::on_downstream_push_promise_complete(
|
||||
Downstream *downstream, Downstream *promised_downstream) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool SpdyUpstream::push_enabled() const { return false; }
|
||||
|
||||
void SpdyUpstream::cancel_premature_downstream(
|
||||
Downstream *promised_downstream) {}
|
||||
|
||||
} // namespace shrpx
|
||||
|
@ -82,6 +82,14 @@ public:
|
||||
virtual void response_drain(size_t n);
|
||||
virtual bool response_empty() const;
|
||||
|
||||
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id);
|
||||
virtual int
|
||||
on_downstream_push_promise_complete(Downstream *downstream,
|
||||
Downstream *promised_downstream);
|
||||
virtual bool push_enabled() const;
|
||||
virtual void cancel_premature_downstream(Downstream *promised_downstream);
|
||||
|
||||
bool get_flow_control() const;
|
||||
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
|
@ -792,7 +792,7 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
|
||||
// Don't attempt to match a presented identifier where the wildcard
|
||||
// character is embedded within an A-label.
|
||||
if (ptLeftLabelEnd == 0 || strchr(ptLeftLabelEnd + 1, '.') == 0 ||
|
||||
ptLeftLabelEnd < ptWildcard || util::istartsWith(pattern, "xn--")) {
|
||||
ptLeftLabelEnd < ptWildcard || util::istarts_with(pattern, "xn--")) {
|
||||
wildcardEnabled = false;
|
||||
}
|
||||
if (!wildcardEnabled) {
|
||||
@ -807,9 +807,9 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
|
||||
if (hnLeftLabelEnd - hostname < ptLeftLabelEnd - pattern) {
|
||||
return false;
|
||||
}
|
||||
return util::istartsWith(hostname, hnLeftLabelEnd, pattern, ptWildcard) &&
|
||||
util::iendsWith(hostname, hnLeftLabelEnd, ptWildcard + 1,
|
||||
ptLeftLabelEnd);
|
||||
return util::istarts_with(hostname, hnLeftLabelEnd, pattern, ptWildcard) &&
|
||||
util::iends_with(hostname, hnLeftLabelEnd, ptWildcard + 1,
|
||||
ptLeftLabelEnd);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -76,6 +76,28 @@ public:
|
||||
virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0;
|
||||
virtual void response_drain(size_t n) = 0;
|
||||
virtual bool response_empty() const = 0;
|
||||
|
||||
// Called when PUSH_PROMISE was started in downstream. The
|
||||
// associated downstream is given as |downstream|. The promised
|
||||
// stream ID is given as |promised_stream_id|. If upstream supports
|
||||
// server push for the corresponding upstream connection, it should
|
||||
// return Downstream object for pushed stream. Otherwise, returns
|
||||
// nullptr.
|
||||
virtual Downstream *
|
||||
on_downstream_push_promise(Downstream *downstream,
|
||||
int32_t promised_stream_id) = 0;
|
||||
// Called when PUSH_PROMISE frame was completely received in
|
||||
// downstream. The associated downstream is given as |downstream|.
|
||||
// This function returns 0 if it succeeds, or -1.
|
||||
virtual int
|
||||
on_downstream_push_promise_complete(Downstream *downstream,
|
||||
Downstream *promised_downstream) = 0;
|
||||
// Returns true if server push is enabled in upstream connection.
|
||||
virtual bool push_enabled() const = 0;
|
||||
// Cancels promised downstream. This function is called when
|
||||
// PUSH_PROMISE for |promised_downstream| is not submitted to
|
||||
// upstream session.
|
||||
virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
88
src/util.cc
88
src/util.cc
@ -63,32 +63,31 @@ namespace nghttp2 {
|
||||
|
||||
namespace util {
|
||||
|
||||
const char DEFAULT_STRIP_CHARSET[] = "\r\n\t ";
|
||||
|
||||
const char UPPER_XDIGITS[] = "0123456789ABCDEF";
|
||||
|
||||
bool inRFC3986UnreservedChars(const char c) {
|
||||
static const char unreserved[] = {'-', '.', '_', '~'};
|
||||
return isAlpha(c) || isDigit(c) ||
|
||||
std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4];
|
||||
bool in_rfc3986_unreserved_chars(const char c) {
|
||||
static constexpr const char unreserved[] = {'-', '.', '_', '~'};
|
||||
return is_alpha(c) || is_digit(c) ||
|
||||
std::find(std::begin(unreserved), std::end(unreserved), c) !=
|
||||
std::end(unreserved);
|
||||
}
|
||||
|
||||
bool in_rfc3986_sub_delims(const char c) {
|
||||
static const char sub_delims[] = {'!', '$', '&', '\'', '(', ')',
|
||||
'*', '+', ',', ';', '='};
|
||||
static constexpr const char sub_delims[] = {'!', '$', '&', '\'', '(', ')',
|
||||
'*', '+', ',', ';', '='};
|
||||
return std::find(std::begin(sub_delims), std::end(sub_delims), c) !=
|
||||
std::end(sub_delims);
|
||||
}
|
||||
|
||||
std::string percentEncode(const unsigned char *target, size_t len) {
|
||||
std::string percent_encode(const unsigned char *target, size_t len) {
|
||||
std::string dest;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
unsigned char c = target[i];
|
||||
|
||||
if (inRFC3986UnreservedChars(c)) {
|
||||
if (in_rfc3986_unreserved_chars(c)) {
|
||||
dest += c;
|
||||
} else {
|
||||
dest += "%";
|
||||
dest += '%';
|
||||
dest += UPPER_XDIGITS[c >> 4];
|
||||
dest += UPPER_XDIGITS[(c & 0x0f)];
|
||||
}
|
||||
@ -96,20 +95,21 @@ std::string percentEncode(const unsigned char *target, size_t len) {
|
||||
return dest;
|
||||
}
|
||||
|
||||
std::string percentEncode(const std::string &target) {
|
||||
return percentEncode(reinterpret_cast<const unsigned char *>(target.c_str()),
|
||||
target.size());
|
||||
std::string percent_encode(const std::string &target) {
|
||||
return percent_encode(reinterpret_cast<const unsigned char *>(target.c_str()),
|
||||
target.size());
|
||||
}
|
||||
|
||||
std::string percent_encode_path(const std::string &s) {
|
||||
std::string dest;
|
||||
for (auto c : s) {
|
||||
if (inRFC3986UnreservedChars(c) || in_rfc3986_sub_delims(c) || c == '/') {
|
||||
if (in_rfc3986_unreserved_chars(c) || in_rfc3986_sub_delims(c) ||
|
||||
c == '/') {
|
||||
dest += c;
|
||||
continue;
|
||||
}
|
||||
|
||||
dest += "%";
|
||||
dest += '%';
|
||||
dest += UPPER_XDIGITS[(c >> 4) & 0x0f];
|
||||
dest += UPPER_XDIGITS[(c & 0x0f)];
|
||||
}
|
||||
@ -117,18 +117,17 @@ std::string percent_encode_path(const std::string &s) {
|
||||
}
|
||||
|
||||
bool in_token(char c) {
|
||||
static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+',
|
||||
'-', '.', '^', '_', '`', '|', '~'};
|
||||
|
||||
return isAlpha(c) || isDigit(c) ||
|
||||
std::find(&extra[0], &extra[sizeof(extra)], c) !=
|
||||
&extra[sizeof(extra)];
|
||||
static constexpr const char extra[] = {'!', '#', '$', '%', '&',
|
||||
'\'', '*', '+', '-', '.',
|
||||
'^', '_', '`', '|', '~'};
|
||||
return is_alpha(c) || is_digit(c) ||
|
||||
std::find(std::begin(extra), std::end(extra), c) != std::end(extra);
|
||||
}
|
||||
|
||||
bool in_attr_char(char c) {
|
||||
static const char bad[] = {'*', '\'', '%'};
|
||||
static constexpr const char bad[] = {'*', '\'', '%'};
|
||||
return util::in_token(c) &&
|
||||
std::find(std::begin(bad), std::end(bad) - 1, c) == std::end(bad) - 1;
|
||||
std::find(std::begin(bad), std::end(bad), c) == std::end(bad);
|
||||
}
|
||||
|
||||
std::string percent_encode_token(const std::string &target) {
|
||||
@ -141,7 +140,7 @@ std::string percent_encode_token(const std::string &target) {
|
||||
if (c != '%' && in_token(c)) {
|
||||
dest += c;
|
||||
} else {
|
||||
dest += "%";
|
||||
dest += '%';
|
||||
dest += UPPER_XDIGITS[c >> 4];
|
||||
dest += UPPER_XDIGITS[(c & 0x0f)];
|
||||
}
|
||||
@ -354,7 +353,7 @@ void streq_advance(const char **ap, const char **bp) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool istartsWith(const char *a, const char *b) {
|
||||
bool istarts_with(const char *a, const char *b) {
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
@ -510,8 +509,8 @@ void show_candidates(const char *unkopt, option *options) {
|
||||
for (size_t i = 0; options[i].name != nullptr; ++i) {
|
||||
auto optnamelen = strlen(options[i].name);
|
||||
// Use cost 0 for prefix match
|
||||
if (istartsWith(options[i].name, options[i].name + optnamelen, unkopt,
|
||||
unkopt + unkoptlen)) {
|
||||
if (istarts_with(options[i].name, options[i].name + optnamelen, unkopt,
|
||||
unkopt + unkoptlen)) {
|
||||
if (optnamelen == static_cast<size_t>(unkoptlen)) {
|
||||
// Exact match, then we don't show any condidates.
|
||||
return;
|
||||
@ -522,8 +521,8 @@ void show_candidates(const char *unkopt, option *options) {
|
||||
}
|
||||
// Use cost 0 for suffix match, but match at least 3 characters
|
||||
if (unkoptlen >= 3 &&
|
||||
iendsWith(options[i].name, options[i].name + optnamelen, unkopt,
|
||||
unkopt + unkoptlen)) {
|
||||
iends_with(options[i].name, options[i].name + optnamelen, unkopt,
|
||||
unkopt + unkoptlen)) {
|
||||
cands.emplace_back(0, options[i].name);
|
||||
continue;
|
||||
}
|
||||
@ -712,7 +711,7 @@ std::string ascii_dump(const uint8_t *data, size_t len) {
|
||||
if (c >= 0x20 && c < 0x7f) {
|
||||
res += c;
|
||||
} else {
|
||||
res += ".";
|
||||
res += '.';
|
||||
}
|
||||
}
|
||||
|
||||
@ -755,7 +754,7 @@ bool check_path(const std::string &path) {
|
||||
path.find('\\') == std::string::npos &&
|
||||
path.find("/../") == std::string::npos &&
|
||||
path.find("/./") == std::string::npos &&
|
||||
!util::endsWith(path, "/..") && !util::endsWith(path, "/.");
|
||||
!util::ends_with(path, "/..") && !util::ends_with(path, "/.");
|
||||
}
|
||||
|
||||
int64_t to_time64(const timeval &tv) {
|
||||
@ -1093,6 +1092,20 @@ std::string format_duration(const std::chrono::microseconds &u) {
|
||||
return dtos(static_cast<double>(t) / d) + unit;
|
||||
}
|
||||
|
||||
std::string format_duration(double t) {
|
||||
const char *unit = "us";
|
||||
if (t >= 1.) {
|
||||
unit = "s";
|
||||
} else if (t >= 0.001) {
|
||||
t *= 1000.;
|
||||
unit = "ms";
|
||||
} else {
|
||||
t *= 1000000.;
|
||||
return utos(static_cast<int64_t>(t)) + unit;
|
||||
}
|
||||
return dtos(t) + unit;
|
||||
}
|
||||
|
||||
std::string dtos(double n) {
|
||||
auto f = utos(static_cast<int64_t>(round(100. * n)) % 100);
|
||||
return utos(static_cast<int64_t>(n)) + "." + (f.size() == 1 ? "0" : "") + f;
|
||||
@ -1103,17 +1116,17 @@ std::string make_hostport(const char *host, uint16_t port) {
|
||||
std::string hostport;
|
||||
|
||||
if (ipv6) {
|
||||
hostport += "[";
|
||||
hostport += '[';
|
||||
}
|
||||
|
||||
hostport += host;
|
||||
|
||||
if (ipv6) {
|
||||
hostport += "]";
|
||||
hostport += ']';
|
||||
}
|
||||
|
||||
if (port != 80 && port != 443) {
|
||||
hostport += ":";
|
||||
hostport += ':';
|
||||
hostport += utos(port);
|
||||
}
|
||||
|
||||
@ -1245,8 +1258,13 @@ int read_mime_types(std::map<std::string, std::string> &res,
|
||||
break;
|
||||
}
|
||||
ext_end = std::find_if(ext_start, std::end(line), delim_pred);
|
||||
#ifdef HAVE_STD_MAP_EMPLACE
|
||||
res.emplace(std::string(ext_start, ext_end),
|
||||
std::string(std::begin(line), type_end));
|
||||
#else // !HAVE_STD_MAP_EMPLACE
|
||||
res.insert(std::make_pair(std::string(ext_start, ext_end),
|
||||
std::string(std::begin(line), type_end)));
|
||||
#endif // !HAVE_STD_MAP_EMPLACE
|
||||
}
|
||||
}
|
||||
|
||||
|
165
src/util.h
165
src/util.h
@ -64,115 +64,17 @@ constexpr const char NGHTTP2_H1_1[] = "http/1.1";
|
||||
|
||||
namespace util {
|
||||
|
||||
extern const char DEFAULT_STRIP_CHARSET[];
|
||||
|
||||
template <typename InputIterator>
|
||||
std::pair<InputIterator, InputIterator>
|
||||
stripIter(InputIterator first, InputIterator last,
|
||||
const char *chars = DEFAULT_STRIP_CHARSET) {
|
||||
for (; first != last && strchr(chars, *first) != 0; ++first)
|
||||
;
|
||||
if (first == last) {
|
||||
return std::make_pair(first, last);
|
||||
}
|
||||
InputIterator left = last - 1;
|
||||
for (; left != first && strchr(chars, *left) != 0; --left)
|
||||
;
|
||||
return std::make_pair(first, left + 1);
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator splitIter(InputIterator first, InputIterator last,
|
||||
OutputIterator out, char delim, bool doStrip = false,
|
||||
bool allowEmpty = false) {
|
||||
for (InputIterator i = first; i != last;) {
|
||||
InputIterator j = std::find(i, last, delim);
|
||||
std::pair<InputIterator, InputIterator> p(i, j);
|
||||
if (doStrip) {
|
||||
p = stripIter(i, j);
|
||||
}
|
||||
if (allowEmpty || p.first != p.second) {
|
||||
*out++ = p;
|
||||
}
|
||||
i = j;
|
||||
if (j != last) {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (allowEmpty && (first == last || *(last - 1) == delim)) {
|
||||
*out++ = std::make_pair(last, last);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename OutputIterator>
|
||||
OutputIterator split(InputIterator first, InputIterator last,
|
||||
OutputIterator out, char delim, bool doStrip = false,
|
||||
bool allowEmpty = false) {
|
||||
for (InputIterator i = first; i != last;) {
|
||||
InputIterator j = std::find(i, last, delim);
|
||||
std::pair<InputIterator, InputIterator> p(i, j);
|
||||
if (doStrip) {
|
||||
p = stripIter(i, j);
|
||||
}
|
||||
if (allowEmpty || p.first != p.second) {
|
||||
*out++ = std::string(p.first, p.second);
|
||||
}
|
||||
i = j;
|
||||
if (j != last) {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (allowEmpty && (first == last || *(last - 1) == delim)) {
|
||||
*out++ = std::string(last, last);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename InputIterator, typename DelimiterType>
|
||||
std::string strjoin(InputIterator first, InputIterator last,
|
||||
const DelimiterType &delim) {
|
||||
std::string result;
|
||||
if (first == last) {
|
||||
return result;
|
||||
}
|
||||
InputIterator beforeLast = last - 1;
|
||||
for (; first != beforeLast; ++first) {
|
||||
result += *first;
|
||||
result += delim;
|
||||
}
|
||||
result += *beforeLast;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
std::string joinPath(InputIterator first, InputIterator last) {
|
||||
std::vector<std::string> elements;
|
||||
for (; first != last; ++first) {
|
||||
if (*first == "..") {
|
||||
if (!elements.empty()) {
|
||||
elements.pop_back();
|
||||
}
|
||||
} else if (*first == ".") {
|
||||
// do nothing
|
||||
} else {
|
||||
elements.push_back(*first);
|
||||
}
|
||||
}
|
||||
return strjoin(elements.begin(), elements.end(), "/");
|
||||
}
|
||||
|
||||
inline bool isAlpha(const char c) {
|
||||
inline bool is_alpha(const char c) {
|
||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||
}
|
||||
|
||||
inline bool isDigit(const char c) { return '0' <= c && c <= '9'; }
|
||||
inline bool is_digit(const char c) { return '0' <= c && c <= '9'; }
|
||||
|
||||
inline bool isHexDigit(const char c) {
|
||||
return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
|
||||
inline bool is_hex_digit(const char c) {
|
||||
return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
|
||||
}
|
||||
|
||||
bool inRFC3986UnreservedChars(const char c);
|
||||
bool in_rfc3986_unreserved_chars(const char c);
|
||||
|
||||
bool in_rfc3986_sub_delims(const char c);
|
||||
|
||||
@ -182,23 +84,23 @@ bool in_token(char c);
|
||||
bool in_attr_char(char c);
|
||||
|
||||
// Returns integer corresponding to hex notation |c|. It is undefined
|
||||
// if isHexDigit(c) is false.
|
||||
// if is_hex_digit(c) is false.
|
||||
uint32_t hex_to_uint(char c);
|
||||
|
||||
std::string percentEncode(const unsigned char *target, size_t len);
|
||||
std::string percent_encode(const unsigned char *target, size_t len);
|
||||
|
||||
std::string percentEncode(const std::string &target);
|
||||
std::string percent_encode(const std::string &target);
|
||||
|
||||
// percent-encode path component of URI |s|.
|
||||
std::string percent_encode_path(const std::string &s);
|
||||
|
||||
template <typename InputIt>
|
||||
std::string percentDecode(InputIt first, InputIt last) {
|
||||
std::string percent_decode(InputIt first, InputIt last) {
|
||||
std::string result;
|
||||
for (; first != last; ++first) {
|
||||
if (*first == '%') {
|
||||
if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
|
||||
isHexDigit(*(first + 2))) {
|
||||
if (first + 1 != last && first + 2 != last &&
|
||||
is_hex_digit(*(first + 1)) && is_hex_digit(*(first + 2))) {
|
||||
result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
|
||||
first += 2;
|
||||
continue;
|
||||
@ -267,20 +169,20 @@ inline char lowcase(char c) {
|
||||
}
|
||||
|
||||
template <typename InputIterator1, typename InputIterator2>
|
||||
bool startsWith(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
bool starts_with(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
if (last1 - first1 < last2 - first2) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(first2, last2, first1);
|
||||
}
|
||||
|
||||
inline bool startsWith(const std::string &a, const std::string &b) {
|
||||
return startsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
inline bool starts_with(const std::string &a, const std::string &b) {
|
||||
return starts_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
}
|
||||
|
||||
inline bool startsWith(const char *a, const char *b) {
|
||||
return startsWith(a, a + strlen(a), b, b + strlen(b));
|
||||
inline bool starts_with(const char *a, const char *b) {
|
||||
return starts_with(a, a + strlen(a), b, b + strlen(b));
|
||||
}
|
||||
|
||||
struct CaseCmp {
|
||||
@ -290,49 +192,49 @@ struct CaseCmp {
|
||||
};
|
||||
|
||||
template <typename InputIterator1, typename InputIterator2>
|
||||
bool istartsWith(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
bool istarts_with(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
if (last1 - first1 < last2 - first2) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(first2, last2, first1, CaseCmp());
|
||||
}
|
||||
|
||||
inline bool istartsWith(const std::string &a, const std::string &b) {
|
||||
return istartsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
inline bool istarts_with(const std::string &a, const std::string &b) {
|
||||
return istarts_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
}
|
||||
|
||||
template <typename InputIt>
|
||||
bool istartsWith(InputIt a, size_t an, const char *b) {
|
||||
return istartsWith(a, a + an, b, b + strlen(b));
|
||||
bool istarts_with(InputIt a, size_t an, const char *b) {
|
||||
return istarts_with(a, a + an, b, b + strlen(b));
|
||||
}
|
||||
|
||||
bool istartsWith(const char *a, const char *b);
|
||||
bool istarts_with(const char *a, const char *b);
|
||||
|
||||
template <typename InputIterator1, typename InputIterator2>
|
||||
bool endsWith(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
bool ends_with(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
if (last1 - first1 < last2 - first2) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(first2, last2, last1 - (last2 - first2));
|
||||
}
|
||||
|
||||
inline bool endsWith(const std::string &a, const std::string &b) {
|
||||
return endsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
inline bool ends_with(const std::string &a, const std::string &b) {
|
||||
return ends_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
}
|
||||
|
||||
template <typename InputIterator1, typename InputIterator2>
|
||||
bool iendsWith(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
bool iends_with(InputIterator1 first1, InputIterator1 last1,
|
||||
InputIterator2 first2, InputIterator2 last2) {
|
||||
if (last1 - first1 < last2 - first2) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp());
|
||||
}
|
||||
|
||||
inline bool iendsWith(const std::string &a, const std::string &b) {
|
||||
return iendsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
inline bool iends_with(const std::string &a, const std::string &b) {
|
||||
return iends_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
|
||||
}
|
||||
|
||||
int strcompare(const char *a, const uint8_t *b, size_t n);
|
||||
@ -683,6 +585,9 @@ std::string duration_str(double t);
|
||||
// fractional digits follow.
|
||||
std::string format_duration(const std::chrono::microseconds &u);
|
||||
|
||||
// Just like above, but this takes |t| as seconds.
|
||||
std::string format_duration(double t);
|
||||
|
||||
// Creates "host:port" string using given |host| and |port|. If
|
||||
// |host| is numeric IPv6 address (e.g., ::1), it is enclosed by "["
|
||||
// and "]". If |port| is 80 or 443, port part is omitted.
|
||||
|
@ -147,15 +147,15 @@ void test_util_percent_encode_path(void) {
|
||||
void test_util_percent_decode(void) {
|
||||
{
|
||||
std::string s = "%66%6F%6f%62%61%72";
|
||||
CU_ASSERT("foobar" == util::percentDecode(std::begin(s), std::end(s)));
|
||||
CU_ASSERT("foobar" == util::percent_decode(std::begin(s), std::end(s)));
|
||||
}
|
||||
{
|
||||
std::string s = "%66%6";
|
||||
CU_ASSERT("f%6" == util::percentDecode(std::begin(s), std::end(s)));
|
||||
CU_ASSERT("f%6" == util::percent_decode(std::begin(s), std::end(s)));
|
||||
}
|
||||
{
|
||||
std::string s = "%66%";
|
||||
CU_ASSERT("f%" == util::percentDecode(std::begin(s), std::end(s)));
|
||||
CU_ASSERT("f%" == util::percent_decode(std::begin(s), std::end(s)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,30 +341,39 @@ void test_util_format_duration(void) {
|
||||
util::format_duration(std::chrono::microseconds(1000000)));
|
||||
CU_ASSERT("1.05s" ==
|
||||
util::format_duration(std::chrono::microseconds(1050000)));
|
||||
|
||||
CU_ASSERT("0us" == util::format_duration(0.));
|
||||
CU_ASSERT("999us" == util::format_duration(0.000999));
|
||||
CU_ASSERT("1.00ms" == util::format_duration(0.001));
|
||||
CU_ASSERT("1.09ms" == util::format_duration(0.00109));
|
||||
CU_ASSERT("1.01ms" == util::format_duration(0.001009));
|
||||
CU_ASSERT("999.99ms" == util::format_duration(0.99999));
|
||||
CU_ASSERT("1.00s" == util::format_duration(1.));
|
||||
CU_ASSERT("1.05s" == util::format_duration(1.05));
|
||||
}
|
||||
|
||||
void test_util_starts_with(void) {
|
||||
CU_ASSERT(util::startsWith("foo", "foo"));
|
||||
CU_ASSERT(util::startsWith("fooo", "foo"));
|
||||
CU_ASSERT(util::startsWith("ofoo", ""));
|
||||
CU_ASSERT(!util::startsWith("ofoo", "foo"));
|
||||
CU_ASSERT(util::starts_with("foo", "foo"));
|
||||
CU_ASSERT(util::starts_with("fooo", "foo"));
|
||||
CU_ASSERT(util::starts_with("ofoo", ""));
|
||||
CU_ASSERT(!util::starts_with("ofoo", "foo"));
|
||||
|
||||
CU_ASSERT(util::istartsWith("FOO", "fOO"));
|
||||
CU_ASSERT(util::startsWith("ofoo", ""));
|
||||
CU_ASSERT(util::istartsWith("fOOo", "Foo"));
|
||||
CU_ASSERT(!util::istartsWith("ofoo", "foo"));
|
||||
CU_ASSERT(util::istarts_with("FOO", "fOO"));
|
||||
CU_ASSERT(util::starts_with("ofoo", ""));
|
||||
CU_ASSERT(util::istarts_with("fOOo", "Foo"));
|
||||
CU_ASSERT(!util::istarts_with("ofoo", "foo"));
|
||||
}
|
||||
|
||||
void test_util_ends_with(void) {
|
||||
CU_ASSERT(util::endsWith("foo", "foo"));
|
||||
CU_ASSERT(util::endsWith("foo", ""));
|
||||
CU_ASSERT(util::endsWith("ofoo", "foo"));
|
||||
CU_ASSERT(!util::endsWith("ofoo", "fo"));
|
||||
CU_ASSERT(util::ends_with("foo", "foo"));
|
||||
CU_ASSERT(util::ends_with("foo", ""));
|
||||
CU_ASSERT(util::ends_with("ofoo", "foo"));
|
||||
CU_ASSERT(!util::ends_with("ofoo", "fo"));
|
||||
|
||||
CU_ASSERT(util::iendsWith("fOo", "Foo"));
|
||||
CU_ASSERT(util::iendsWith("foo", ""));
|
||||
CU_ASSERT(util::iendsWith("oFoo", "fOO"));
|
||||
CU_ASSERT(!util::iendsWith("ofoo", "fo"));
|
||||
CU_ASSERT(util::iends_with("fOo", "Foo"));
|
||||
CU_ASSERT(util::iends_with("foo", ""));
|
||||
CU_ASSERT(util::iends_with("oFoo", "fOO"));
|
||||
CU_ASSERT(!util::iends_with("ofoo", "fo"));
|
||||
}
|
||||
|
||||
void test_util_parse_http_date(void) {
|
||||
|
@ -87,6 +87,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||
test_nghttp2_session_recv_continuation) ||
|
||||
!CU_add_test(pSuite, "session_recv_headers_with_priority",
|
||||
test_nghttp2_session_recv_headers_with_priority) ||
|
||||
!CU_add_test(pSuite, "session_recv_headers_early_response",
|
||||
test_nghttp2_session_recv_headers_early_response) ||
|
||||
!CU_add_test(pSuite, "session_recv_premature_headers",
|
||||
test_nghttp2_session_recv_premature_headers) ||
|
||||
!CU_add_test(pSuite, "session_recv_unknown_frame",
|
||||
@ -161,6 +163,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||
test_nghttp2_submit_response_with_data) ||
|
||||
!CU_add_test(pSuite, "submit_response_without_data",
|
||||
test_nghttp2_submit_response_without_data) ||
|
||||
!CU_add_test(pSuite, "Submit_response_push_response",
|
||||
test_nghttp2_submit_response_push_response) ||
|
||||
!CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) ||
|
||||
!CU_add_test(pSuite, "submit_headers_start_stream",
|
||||
test_nghttp2_submit_headers_start_stream) ||
|
||||
@ -252,6 +256,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||
test_nghttp2_session_stream_get_state) ||
|
||||
!CU_add_test(pSuite, "session_stream_get_something",
|
||||
test_nghttp2_session_stream_get_something) ||
|
||||
!CU_add_test(pSuite, "session_find_stream",
|
||||
test_nghttp2_session_find_stream) ||
|
||||
!CU_add_test(pSuite, "session_keep_closed_stream",
|
||||
test_nghttp2_session_keep_closed_stream) ||
|
||||
!CU_add_test(pSuite, "session_keep_idle_stream",
|
||||
@ -283,6 +289,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||
!CU_add_test(pSuite, "session_detach_item_from_closed_stream",
|
||||
test_nghttp2_session_detach_item_from_closed_stream) ||
|
||||
!CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
|
||||
!CU_add_test(pSuite, "session_change_stream_priority",
|
||||
test_nghttp2_session_change_stream_priority) ||
|
||||
!CU_add_test(pSuite, "http_mandatory_headers",
|
||||
test_nghttp2_http_mandatory_headers) ||
|
||||
!CU_add_test(pSuite, "http_content_length",
|
||||
|
@ -1408,6 +1408,85 @@ void test_nghttp2_session_recv_headers_with_priority(void) {
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
static int response_on_begin_frame_callback(nghttp2_session *session,
|
||||
const nghttp2_frame_hd *hd,
|
||||
void *user_data _U_) {
|
||||
int rv;
|
||||
|
||||
if (hd->type != NGHTTP2_HEADERS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv = nghttp2_submit_response(session, hd->stream_id, resnv, ARRLEN(resnv),
|
||||
NULL);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_nghttp2_session_recv_headers_early_response(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_bufs bufs;
|
||||
nghttp2_buf *buf;
|
||||
nghttp2_hd_deflater deflater;
|
||||
nghttp2_mem *mem;
|
||||
nghttp2_nv *nva;
|
||||
size_t nvlen;
|
||||
nghttp2_frame frame;
|
||||
ssize_t rv;
|
||||
nghttp2_stream *stream;
|
||||
|
||||
mem = nghttp2_mem_default();
|
||||
frame_pack_bufs_init(&bufs);
|
||||
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
callbacks.on_begin_frame_callback = response_on_begin_frame_callback;
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
nghttp2_hd_deflate_init(&deflater, mem);
|
||||
|
||||
nvlen = ARRLEN(reqnv);
|
||||
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
|
||||
nghttp2_frame_headers_init(&frame.headers,
|
||||
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
|
||||
1, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
|
||||
|
||||
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||
|
||||
buf = &bufs.head->buf;
|
||||
|
||||
/* Only receive 9 bytes headers, and invoke
|
||||
on_begin_frame_callback */
|
||||
rv = nghttp2_session_mem_recv(session, buf->pos, 9);
|
||||
|
||||
CU_ASSERT(9 == rv);
|
||||
|
||||
rv = nghttp2_session_send(session);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
rv =
|
||||
nghttp2_session_mem_recv(session, buf->pos + 9, nghttp2_buf_len(buf) - 9);
|
||||
|
||||
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - 9 == rv);
|
||||
|
||||
stream = nghttp2_session_get_stream_raw(session, 1);
|
||||
|
||||
CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_CLOSED);
|
||||
|
||||
nghttp2_hd_deflate_free(&deflater);
|
||||
nghttp2_session_del(session);
|
||||
nghttp2_bufs_free(&bufs);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_recv_premature_headers(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
@ -2765,7 +2844,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
||||
CU_ASSERT(6 == item->frame.hd.stream_id);
|
||||
CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
|
||||
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
|
||||
/* Attempt to PUSH_PROMISE against non-existent stream */
|
||||
@ -2939,7 +3018,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
|
||||
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
||||
CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
|
||||
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
|
||||
|
||||
nghttp2_frame_push_promise_free(&frame.push_promise, mem);
|
||||
nghttp2_session_del(session);
|
||||
@ -3580,6 +3659,15 @@ void test_nghttp2_session_reprioritize_stream(void) {
|
||||
CU_ASSERT(128 == stream->weight);
|
||||
CU_ASSERT(dep_stream == stream->dep_prev);
|
||||
|
||||
/* Change weight again to test short-path case */
|
||||
pri_spec.weight = 100;
|
||||
|
||||
nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
|
||||
|
||||
CU_ASSERT(100 == stream->weight);
|
||||
CU_ASSERT(dep_stream == stream->dep_prev);
|
||||
CU_ASSERT(100 == dep_stream->sum_dep_weight);
|
||||
|
||||
/* Test circular dependency; stream 1 is first removed and becomes
|
||||
root. Then stream 3 depends on it. */
|
||||
nghttp2_priority_spec_init(&pri_spec, 1, 1, 0);
|
||||
@ -4012,6 +4100,33 @@ void test_nghttp2_submit_response_without_data(void) {
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_submit_response_push_response(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
my_user_data ud;
|
||||
|
||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, &ud);
|
||||
|
||||
nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE, &pri_spec_default,
|
||||
NGHTTP2_STREAM_RESERVED, NULL);
|
||||
|
||||
session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
|
||||
|
||||
CU_ASSERT(0 ==
|
||||
nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL));
|
||||
|
||||
ud.frame_not_send_cb_called = 0;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(1 == ud.frame_not_send_cb_called);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_submit_trailer(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
@ -4572,28 +4687,28 @@ void test_nghttp2_submit_push_promise(void) {
|
||||
CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
|
||||
reqnv, ARRLEN(reqnv), &ud));
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 2);
|
||||
|
||||
CU_ASSERT(NULL != stream);
|
||||
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
|
||||
CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
|
||||
|
||||
ud.frame_send_cb_called = 0;
|
||||
ud.sent_frame_type = 0;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(1 == ud.frame_send_cb_called);
|
||||
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type);
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 2);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
|
||||
CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
|
||||
|
||||
/* submit PUSH_PROMISE while associated stream is not opened */
|
||||
CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3,
|
||||
reqnv, ARRLEN(reqnv), &ud));
|
||||
|
||||
ud.frame_not_send_cb_called = 0;
|
||||
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
CU_ASSERT(1 == ud.frame_not_send_cb_called);
|
||||
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type);
|
||||
|
||||
stream = nghttp2_session_get_stream(session, 4);
|
||||
|
||||
CU_ASSERT(NULL == stream);
|
||||
CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED ==
|
||||
nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv,
|
||||
ARRLEN(reqnv), &ud));
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
@ -4965,7 +5080,7 @@ void test_nghttp2_session_open_stream(void) {
|
||||
NGHTTP2_STREAM_RESERVED, NULL);
|
||||
CU_ASSERT(0 == session->num_incoming_streams);
|
||||
CU_ASSERT(0 == session->num_outgoing_streams);
|
||||
CU_ASSERT(NULL == stream->dep_prev);
|
||||
CU_ASSERT(&session->root == stream->dep_prev);
|
||||
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
|
||||
CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);
|
||||
|
||||
@ -7457,7 +7572,8 @@ void test_nghttp2_session_stream_get_state(void) {
|
||||
|
||||
stream = nghttp2_session_find_stream(session, 2);
|
||||
|
||||
CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream));
|
||||
/* At server, pushed stream object is not retained after closed */
|
||||
CU_ASSERT(NULL == stream);
|
||||
|
||||
/* Push stream 4 associated to stream 5 */
|
||||
rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv,
|
||||
@ -7597,6 +7713,34 @@ void test_nghttp2_session_stream_get_something(void) {
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_find_stream(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_stream *stream;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
open_stream(session, 1);
|
||||
|
||||
stream = nghttp2_session_find_stream(session, 1);
|
||||
|
||||
CU_ASSERT(NULL != stream);
|
||||
CU_ASSERT(1 == stream->stream_id);
|
||||
|
||||
stream = nghttp2_session_find_stream(session, 0);
|
||||
|
||||
CU_ASSERT(&session->root == stream);
|
||||
CU_ASSERT(0 == stream->stream_id);
|
||||
|
||||
stream = nghttp2_session_find_stream(session, 2);
|
||||
|
||||
CU_ASSERT(NULL == stream);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_keep_closed_stream(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
@ -7650,6 +7794,22 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
||||
CU_ASSERT(NULL == session->closed_stream_tail);
|
||||
CU_ASSERT(NULL == session->closed_stream_head);
|
||||
|
||||
nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);
|
||||
|
||||
CU_ASSERT(1 == session->num_closed_streams);
|
||||
CU_ASSERT(3 == session->closed_stream_head->stream_id);
|
||||
|
||||
/* server initiated stream is not counted to max concurrent limit */
|
||||
open_stream(session, 2);
|
||||
|
||||
CU_ASSERT(1 == session->num_closed_streams);
|
||||
CU_ASSERT(3 == session->closed_stream_head->stream_id);
|
||||
|
||||
nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR);
|
||||
|
||||
CU_ASSERT(1 == session->num_closed_streams);
|
||||
CU_ASSERT(3 == session->closed_stream_head->stream_id);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
@ -7787,13 +7947,12 @@ void test_nghttp2_session_large_dep_tree(void) {
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
stream_id = 1;
|
||||
for (i = 0; i < 250; ++i) {
|
||||
for (i = 0; i < 250; ++i, stream_id += 2) {
|
||||
dep_stream = open_stream_with_dep(session, stream_id, dep_stream);
|
||||
stream_id += 2;
|
||||
}
|
||||
|
||||
stream_id = 1;
|
||||
for (i = 0; i < 250; ++i) {
|
||||
for (i = 0; i < 250; ++i, stream_id += 2) {
|
||||
stream = nghttp2_session_get_stream(session, stream_id);
|
||||
CU_ASSERT(nghttp2_stream_dep_find_ancestor(stream, &session->root));
|
||||
CU_ASSERT(nghttp2_stream_in_dep_tree(stream));
|
||||
@ -8477,6 +8636,116 @@ void test_nghttp2_session_flooding(void) {
|
||||
nghttp2_bufs_free(&bufs);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_change_stream_priority(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_stream *stream1, *stream2, *stream3;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int rv;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
stream1 = open_stream(session, 1);
|
||||
stream3 = open_stream_with_dep_weight(session, 3, 199, stream1);
|
||||
stream2 = open_stream_with_dep_weight(session, 2, 101, stream3);
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, 1, 256, 0);
|
||||
|
||||
rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
CU_ASSERT(stream1 == stream2->dep_prev);
|
||||
CU_ASSERT(256 == stream2->weight);
|
||||
|
||||
/* Cannot change stream which does not exist */
|
||||
rv = nghttp2_session_change_stream_priority(session, 5, &pri_spec);
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
/* It is an error to depend on itself */
|
||||
rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
/* It is an error to change priority of root stream (0) */
|
||||
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
void test_nghttp2_session_create_idle_stream(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_stream *stream2, *stream4, *stream8, *stream10;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int rv;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
|
||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||
|
||||
stream2 = open_stream(session, 2);
|
||||
|
||||
nghttp2_priority_spec_init(&pri_spec, 2, 111, 1);
|
||||
|
||||
rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
stream4 = nghttp2_session_get_stream_raw(session, 4);
|
||||
|
||||
CU_ASSERT(4 == stream4->stream_id);
|
||||
CU_ASSERT(111 == stream4->weight);
|
||||
CU_ASSERT(stream2 == stream4->dep_prev);
|
||||
CU_ASSERT(stream4 == stream2->dep_next);
|
||||
|
||||
/* If pri_spec->stream_id does not exist, and it is idle stream, it
|
||||
is created too */
|
||||
nghttp2_priority_spec_init(&pri_spec, 8, 109, 0);
|
||||
|
||||
rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
stream8 = nghttp2_session_get_stream_raw(session, 8);
|
||||
stream10 = nghttp2_session_get_stream_raw(session, 10);
|
||||
|
||||
CU_ASSERT(8 == stream8->stream_id);
|
||||
CU_ASSERT(109 == stream8->weight);
|
||||
CU_ASSERT(10 == stream10->stream_id);
|
||||
CU_ASSERT(16 == stream10->weight);
|
||||
CU_ASSERT(stream10 == stream8->dep_prev);
|
||||
CU_ASSERT(&session->root == stream10->dep_prev);
|
||||
|
||||
/* It is an error to attempt to create already existing idle
|
||||
stream */
|
||||
rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
|
||||
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
/* It is an error to depend on itself */
|
||||
pri_spec.stream_id = 6;
|
||||
|
||||
rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec);
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
/* It is an error to create root stream (0) as idle stream */
|
||||
rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec);
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
/* It is an error to create non-idle stream */
|
||||
session->next_stream_id = 20;
|
||||
pri_spec.stream_id = 2;
|
||||
|
||||
rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec);
|
||||
|
||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
||||
static void check_nghttp2_http_recv_headers_fail(
|
||||
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
|
||||
int stream_state, const nghttp2_nv *nva, size_t nvlen) {
|
||||
|
@ -33,6 +33,7 @@ void test_nghttp2_session_recv_data(void);
|
||||
void test_nghttp2_session_recv_data_no_auto_flow_control(void);
|
||||
void test_nghttp2_session_recv_continuation(void);
|
||||
void test_nghttp2_session_recv_headers_with_priority(void);
|
||||
void test_nghttp2_session_recv_headers_early_response(void);
|
||||
void test_nghttp2_session_recv_premature_headers(void);
|
||||
void test_nghttp2_session_recv_unknown_frame(void);
|
||||
void test_nghttp2_session_recv_unexpected_continuation(void);
|
||||
@ -71,6 +72,7 @@ void test_nghttp2_submit_request_with_data(void);
|
||||
void test_nghttp2_submit_request_without_data(void);
|
||||
void test_nghttp2_submit_response_with_data(void);
|
||||
void test_nghttp2_submit_response_without_data(void);
|
||||
void test_nghttp2_submit_response_push_response(void);
|
||||
void test_nghttp2_submit_trailer(void);
|
||||
void test_nghttp2_submit_headers_start_stream(void);
|
||||
void test_nghttp2_submit_headers_reply(void);
|
||||
@ -118,6 +120,7 @@ void test_nghttp2_session_stream_attach_item(void);
|
||||
void test_nghttp2_session_stream_attach_item_subtree(void);
|
||||
void test_nghttp2_session_stream_get_state(void);
|
||||
void test_nghttp2_session_stream_get_something(void);
|
||||
void test_nghttp2_session_find_stream(void);
|
||||
void test_nghttp2_session_keep_closed_stream(void);
|
||||
void test_nghttp2_session_keep_idle_stream(void);
|
||||
void test_nghttp2_session_detach_idle_stream(void);
|
||||
@ -134,6 +137,8 @@ void test_nghttp2_session_on_begin_headers_temporal_failure(void);
|
||||
void test_nghttp2_session_defer_then_close(void);
|
||||
void test_nghttp2_session_detach_item_from_closed_stream(void);
|
||||
void test_nghttp2_session_flooding(void);
|
||||
void test_nghttp2_session_change_stream_priority(void);
|
||||
void test_nghttp2_session_create_idle_stream(void);
|
||||
void test_nghttp2_http_mandatory_headers(void);
|
||||
void test_nghttp2_http_content_length(void);
|
||||
void test_nghttp2_http_content_length_mismatch(void);
|
||||
|
2
third-party/mruby
vendored
2
third-party/mruby
vendored
@ -1 +1 @@
|
||||
Subproject commit 83922d0bbb7de894380fa6b33e7d75061114aa59
|
||||
Subproject commit 22464fe5a0a10f2b077eaba109ce1e912e4a77de
|
Loading…
Reference in New Issue
Block a user