Merge branch 'master' into simple-extensions
This commit is contained in:
commit
0caefe20ef
2
COPYING
2
COPYING
|
@ -1,6 +1,6 @@
|
||||||
The MIT License
|
The MIT License
|
||||||
|
|
||||||
Copyright (c) 2012, 2014, 2015 Tatsuhiro Tsujikawa
|
Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|
|
@ -274,7 +274,7 @@ for this 24 bytes byte string and updated API.
|
||||||
``NGHTTP2_CLIENT_MAGIC_LEN``.
|
``NGHTTP2_CLIENT_MAGIC_LEN``.
|
||||||
* ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC``
|
* ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC``
|
||||||
|
|
||||||
The alreay deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and
|
The already deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and
|
||||||
``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed.
|
``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed.
|
||||||
|
|
||||||
If application uses these macros, just replace old ones with new ones.
|
If application uses these macros, just replace old ones with new ones.
|
||||||
|
|
27
configure.ac
27
configure.ac
|
@ -25,7 +25,7 @@ dnl Do not change user variables!
|
||||||
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
|
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
|
||||||
|
|
||||||
AC_PREREQ(2.61)
|
AC_PREREQ(2.61)
|
||||||
AC_INIT([nghttp2], [1.5.1-DEV], [t-tujikawa@users.sourceforge.net])
|
AC_INIT([nghttp2], [1.6.1-DEV], [t-tujikawa@users.sourceforge.net])
|
||||||
AC_CONFIG_AUX_DIR([.])
|
AC_CONFIG_AUX_DIR([.])
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
AC_CONFIG_HEADERS([config.h])
|
AC_CONFIG_HEADERS([config.h])
|
||||||
|
@ -46,9 +46,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||||
|
|
||||||
dnl See versioning rule:
|
dnl See versioning rule:
|
||||||
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||||
AC_SUBST(LT_CURRENT, 17)
|
AC_SUBST(LT_CURRENT, 18)
|
||||||
AC_SUBST(LT_REVISION, 0)
|
AC_SUBST(LT_REVISION, 0)
|
||||||
AC_SUBST(LT_AGE, 3)
|
AC_SUBST(LT_AGE, 4)
|
||||||
|
|
||||||
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
|
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"`
|
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
|
||||||
|
@ -104,6 +104,11 @@ AC_ARG_ENABLE([failmalloc],
|
||||||
[Do not build failmalloc test program])],
|
[Do not build failmalloc test program])],
|
||||||
[request_failmalloc=$enableval], [request_failmalloc=yes])
|
[request_failmalloc=$enableval], [request_failmalloc=yes])
|
||||||
|
|
||||||
|
AC_ARG_ENABLE([lib-only],
|
||||||
|
[AS_HELP_STRING([--enable-lib-only],
|
||||||
|
[Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools --disable-python-bindings])],
|
||||||
|
[request_lib_only=$enableval], [request_lib_only=no])
|
||||||
|
|
||||||
AC_ARG_WITH([libxml2],
|
AC_ARG_WITH([libxml2],
|
||||||
[AS_HELP_STRING([--with-libxml2],
|
[AS_HELP_STRING([--with-libxml2],
|
||||||
[Use libxml2 [default=check]])],
|
[Use libxml2 [default=check]])],
|
||||||
|
@ -150,6 +155,13 @@ PKG_PROG_PKG_CONFIG([0.20])
|
||||||
|
|
||||||
AM_PATH_PYTHON([2.7],, [:])
|
AM_PATH_PYTHON([2.7],, [:])
|
||||||
|
|
||||||
|
if [test "x$request_lib_only" = "xyes"]; then
|
||||||
|
request_app=no
|
||||||
|
request_hpack_tools=no
|
||||||
|
request_examples=no
|
||||||
|
request_python_bindings=no
|
||||||
|
fi
|
||||||
|
|
||||||
if [test "x$request_python_bindings" != "xno"]; then
|
if [test "x$request_python_bindings" != "xno"]; then
|
||||||
AX_PYTHON_DEVEL([>= '2.7'])
|
AX_PYTHON_DEVEL([>= '2.7'])
|
||||||
fi
|
fi
|
||||||
|
@ -243,6 +255,11 @@ if test "x${have_zlib}" = "xno"; then
|
||||||
AC_MSG_NOTICE($ZLIB_PKG_ERRORS)
|
AC_MSG_NOTICE($ZLIB_PKG_ERRORS)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# dl: openssl requires libdl when it is statically linked.
|
||||||
|
LIBS_OLD=$LIBS
|
||||||
|
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
|
||||||
|
LIBS=$LIBS_OLD
|
||||||
|
|
||||||
# cunit
|
# cunit
|
||||||
PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
|
PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
|
||||||
# If pkg-config does not find cunit, check it using AC_CHECK_LIB. We
|
# If pkg-config does not find cunit, check it using AC_CHECK_LIB. We
|
||||||
|
@ -613,6 +630,10 @@ AC_CHECK_FUNCS([ \
|
||||||
AC_CHECK_FUNC([timerfd_create],
|
AC_CHECK_FUNC([timerfd_create],
|
||||||
[have_timerfd_create=yes], [have_timerfd_create=no])
|
[have_timerfd_create=yes], [have_timerfd_create=no])
|
||||||
|
|
||||||
|
# For cygwin: we can link initgroups, so AC_CHECK_FUNCS succeeds, but
|
||||||
|
# cygwin disables initgroups due to feature test macro magic with our
|
||||||
|
# configuration.
|
||||||
|
AC_CHECK_DECLS([initgroups], [], [], [[#include <grp.h>]])
|
||||||
|
|
||||||
# Checks for epoll availability, primarily for examples/tiny-nghttpd
|
# Checks for epoll availability, primarily for examples/tiny-nghttpd
|
||||||
AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])
|
AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])
|
||||||
|
|
|
@ -66,7 +66,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'nghttp2'
|
project = u'nghttp2'
|
||||||
copyright = u'2012, 2015, Tatsuhiro Tsujikawa'
|
copyright = u'2012, 2015, 2016, Tatsuhiro Tsujikawa'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|
24
doc/h2load.1
24
doc/h2load.1
|
@ -1,6 +1,6 @@
|
||||||
.\" Man page generated from reStructuredText.
|
.\" Man page generated from reStructuredText.
|
||||||
.
|
.
|
||||||
.TH "H2LOAD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
.TH "H2LOAD" "1" "December 23, 2015" "1.6.0" "nghttp2"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
h2load \- HTTP/2 benchmarking tool
|
h2load \- HTTP/2 benchmarking tool
|
||||||
.
|
.
|
||||||
|
@ -374,6 +374,28 @@ The fraction of the number of connections within standard
|
||||||
deviation range (mean +/\- sd) against total number of successful
|
deviation range (mean +/\- sd) against total number of successful
|
||||||
connections.
|
connections.
|
||||||
.UNINDENT
|
.UNINDENT
|
||||||
|
.TP
|
||||||
|
.B req/s
|
||||||
|
.INDENT 7.0
|
||||||
|
.TP
|
||||||
|
.B min
|
||||||
|
The minimum request per second among all clients.
|
||||||
|
.TP
|
||||||
|
.B max
|
||||||
|
The maximum request per second among all clients.
|
||||||
|
.TP
|
||||||
|
.B mean
|
||||||
|
The mean request per second among all clients.
|
||||||
|
.TP
|
||||||
|
.B sd
|
||||||
|
The standard deviation of request per second among all clients.
|
||||||
|
server.
|
||||||
|
.TP
|
||||||
|
.B +/\- sd
|
||||||
|
The fraction of the number of connections within standard
|
||||||
|
deviation range (mean +/\- sd) against total number of successful
|
||||||
|
connections.
|
||||||
|
.UNINDENT
|
||||||
.UNINDENT
|
.UNINDENT
|
||||||
.SH FLOW CONTROL
|
.SH FLOW CONTROL
|
||||||
.sp
|
.sp
|
||||||
|
|
|
@ -213,6 +213,8 @@ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
|
||||||
(hours, minutes, seconds and milliseconds, respectively). If a unit
|
(hours, minutes, seconds and milliseconds, respectively). If a unit
|
||||||
is omitted, a second is used as unit.
|
is omitted, a second is used as unit.
|
||||||
|
|
||||||
|
.. _h2load-1-output:
|
||||||
|
|
||||||
OUTPUT
|
OUTPUT
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -301,6 +303,21 @@ time for 1st byte (of (decrypted in case of TLS) application data)
|
||||||
deviation range (mean +/- sd) against total number of successful
|
deviation range (mean +/- sd) against total number of successful
|
||||||
connections.
|
connections.
|
||||||
|
|
||||||
|
req/s
|
||||||
|
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
|
FLOW CONTROL
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _h2load-1-output:
|
||||||
|
|
||||||
OUTPUT
|
OUTPUT
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -86,7 +88,7 @@ time for 1st byte (of (decrypted in case of TLS) application data)
|
||||||
deviation range (mean +/- sd) against total number of successful
|
deviation range (mean +/- sd) against total number of successful
|
||||||
connections.
|
connections.
|
||||||
|
|
||||||
req/s (client)
|
req/s
|
||||||
min
|
min
|
||||||
The minimum request per second among all clients.
|
The minimum request per second among all clients.
|
||||||
max
|
max
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.\" Man page generated from reStructuredText.
|
.\" Man page generated from reStructuredText.
|
||||||
.
|
.
|
||||||
.TH "NGHTTP" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
.TH "NGHTTP" "1" "December 23, 2015" "1.6.0" "nghttp2"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
nghttp \- HTTP/2 client
|
nghttp \- HTTP/2 client
|
||||||
.
|
.
|
||||||
|
@ -152,7 +152,8 @@ Default: \fB16\fP
|
||||||
.B \-M, \-\-peer\-max\-concurrent\-streams=<N>
|
.B \-M, \-\-peer\-max\-concurrent\-streams=<N>
|
||||||
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
||||||
remote endpoint as if it is received in SETTINGS frame.
|
remote endpoint as if it is received in SETTINGS frame.
|
||||||
The default is large enough as it is seen as unlimited.
|
.sp
|
||||||
|
Default: \fB100\fP
|
||||||
.UNINDENT
|
.UNINDENT
|
||||||
.INDENT 0.0
|
.INDENT 0.0
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -116,7 +116,8 @@ OPTIONS
|
||||||
|
|
||||||
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
||||||
remote endpoint as if it is received in SETTINGS frame.
|
remote endpoint as if it is received in SETTINGS frame.
|
||||||
The default is large enough as it is seen as unlimited.
|
|
||||||
|
Default: ``100``
|
||||||
|
|
||||||
.. option:: -c, --header-table-size=<SIZE>
|
.. option:: -c, --header-table-size=<SIZE>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.\" Man page generated from reStructuredText.
|
.\" Man page generated from reStructuredText.
|
||||||
.
|
.
|
||||||
.TH "NGHTTPD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
.TH "NGHTTPD" "1" "December 23, 2015" "1.6.0" "nghttp2"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
nghttpd \- HTTP/2 server
|
nghttpd \- HTTP/2 server
|
||||||
.
|
.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.\" Man page generated from reStructuredText.
|
.\" Man page generated from reStructuredText.
|
||||||
.
|
.
|
||||||
.TH "NGHTTPX" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
.TH "NGHTTPX" "1" "December 23, 2015" "1.6.0" "nghttp2"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
nghttpx \- HTTP/2 proxy
|
nghttpx \- HTTP/2 proxy
|
||||||
.
|
.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
h2load - HTTP/2 benchmarking tool - HOW-TO
|
h2load - HTTP/2 benchmarking tool - HOW-TO
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
h2load is benchmarking tool for HTTP/2. If built with
|
h2load is benchmarking tool for HTTP/2 and HTTP/1.1. If built with
|
||||||
spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also
|
spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also
|
||||||
supports SPDY protocol. It supports SSL/TLS and clear text for both
|
supports SPDY protocol. It supports SSL/TLS and clear text for all
|
||||||
HTTP/2 and SPDY.
|
supported protocols.
|
||||||
|
|
||||||
Basic Usage
|
Basic Usage
|
||||||
-----------
|
-----------
|
||||||
|
@ -22,29 +22,37 @@ In order to set benchmark settings, specify following 3 options.
|
||||||
If ``auto`` is given, the number of given URIs is used.
|
If ``auto`` is given, the number of given URIs is used.
|
||||||
Default: ``auto``
|
Default: ``auto``
|
||||||
|
|
||||||
|
For SSL/TLS connection, the protocol will be negotiated via ALPN/NPN.
|
||||||
|
You can set specific protocols in ``--npn-list`` option. For
|
||||||
|
cleartext connection, the default protocol is HTTP/2. To change the
|
||||||
|
protocol in cleartext connection, use ``--no-tls-proto`` option. For
|
||||||
|
convenience, ``--h1`` option forces HTTP/1.1 for both cleartext and
|
||||||
|
SSL/TLS connections.
|
||||||
|
|
||||||
Here is a command-line to perform benchmark to URI \https://localhost
|
Here is a command-line to perform benchmark to URI \https://localhost
|
||||||
using total 100000 requests, 100 concurrent clients and 10 max
|
using total 100000 requests, 100 concurrent clients and 10 max
|
||||||
concurrent streams::
|
concurrent streams:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
$ h2load -n100000 -c100 -m10 https://localhost
|
$ h2load -n100000 -c100 -m10 https://localhost
|
||||||
|
|
||||||
The benchmarking result looks like this::
|
The benchmarking result looks like this:
|
||||||
|
|
||||||
finished in 0 sec, 385 millisec and 851 microsec, 2591 req/s, 1689 kbytes/s
|
.. code-block:: text
|
||||||
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored
|
|
||||||
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
|
|
||||||
traffic: 667500 bytes total, 28700 bytes headers, 612000 bytes data
|
|
||||||
|
|
||||||
The number of ``failed`` is the number of requests returned with non
|
finished in 7.08s, 141164.80 req/s, 555.33MB/s
|
||||||
2xx status. The number of ``error`` is the number of ``failed`` plus
|
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
|
||||||
the number of requests which failed with connection error.
|
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
|
||||||
|
traffic: 4125025824 bytes total, 11023424 bytes headers (space savings 93.07%), 4096000000 bytes data
|
||||||
|
min max mean sd +/- sd
|
||||||
|
time for request: 15.31ms 146.85ms 69.78ms 9.26ms 92.43%
|
||||||
|
time for connect: 1.08ms 25.04ms 10.71ms 9.80ms 64.00%
|
||||||
|
time to 1st byte: 25.36ms 184.96ms 79.11ms 53.97ms 78.00%
|
||||||
|
req/s (client) : 1412.04 1447.84 1426.52 10.57 63.00%
|
||||||
|
|
||||||
The number of ``total`` in ``traffic`` is the received application
|
See the h2load manual page :ref:`h2load-1-output` section for the
|
||||||
data. If SSL/TLS is used, this number is calculated after decryption.
|
explanation of the above numbers.
|
||||||
The number of ``headers`` is the sum of payload size of response
|
|
||||||
HEADERS (or SYN_REPLY for SPDY). This number comes before
|
|
||||||
decompressing header block. The number of ``data`` is the sum of
|
|
||||||
response body.
|
|
||||||
|
|
||||||
Flow Control
|
Flow Control
|
||||||
------------
|
------------
|
||||||
|
|
|
@ -3296,6 +3296,9 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
|
||||||
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
|
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
|
||||||
* No stream ID is available because maximum stream ID was
|
* No stream ID is available because maximum stream ID was
|
||||||
* reached.
|
* reached.
|
||||||
|
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||||
|
* Trying to depend on itself (new stream ID equals
|
||||||
|
* ``pri_spec->stream_id``).
|
||||||
*
|
*
|
||||||
* .. warning::
|
* .. warning::
|
||||||
*
|
*
|
||||||
|
@ -3407,7 +3410,7 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
|
||||||
* has already sent and if `nghttp2_submit_trailer()` is called before
|
* has already sent and if `nghttp2_submit_trailer()` is called before
|
||||||
* any response HEADERS submission (usually by
|
* any response HEADERS submission (usually by
|
||||||
* `nghttp2_submit_response()`), the content of |nva| will be sent as
|
* `nghttp2_submit_response()`), the content of |nva| will be sent as
|
||||||
* reponse headers, which will result in error.
|
* response headers, which will result in error.
|
||||||
*
|
*
|
||||||
* This function has the same effect with `nghttp2_submit_headers()`,
|
* This function has the same effect with `nghttp2_submit_headers()`,
|
||||||
* with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and
|
* with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and
|
||||||
|
@ -3506,7 +3509,8 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
|
||||||
* No stream ID is available because maximum stream ID was
|
* No stream ID is available because maximum stream ID was
|
||||||
* reached.
|
* reached.
|
||||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||||
* The |stream_id| is 0.
|
* The |stream_id| is 0; or trying to depend on itself (stream ID
|
||||||
|
* equals ``pri_spec->stream_id``).
|
||||||
* :enum:`NGHTTP2_ERR_DATA_EXIST`
|
* :enum:`NGHTTP2_ERR_DATA_EXIST`
|
||||||
* DATA or HEADERS has been already submitted and not fully
|
* DATA or HEADERS has been already submitted and not fully
|
||||||
* processed yet. This happens if stream denoted by |stream_id|
|
* processed yet. This happens if stream denoted by |stream_id|
|
||||||
|
@ -3549,7 +3553,7 @@ nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
|
||||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||||
* The |stream_id| is 0.
|
* The |stream_id| is 0.
|
||||||
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
|
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
|
||||||
* The stream was alreay closed; or the |stream_id| is invalid.
|
* The stream was already closed; or the |stream_id| is invalid.
|
||||||
*
|
*
|
||||||
* .. note::
|
* .. note::
|
||||||
*
|
*
|
||||||
|
@ -3718,7 +3722,7 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
|
||||||
* The |stream_id| is 0; The |stream_id| does not designate stream
|
* The |stream_id| is 0; The |stream_id| does not designate stream
|
||||||
* that peer initiated.
|
* that peer initiated.
|
||||||
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
|
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
|
||||||
* The stream was alreay closed; or the |stream_id| is invalid.
|
* The stream was already closed; or the |stream_id| is invalid.
|
||||||
*
|
*
|
||||||
* .. warning::
|
* .. warning::
|
||||||
*
|
*
|
||||||
|
@ -4336,7 +4340,7 @@ typedef enum {
|
||||||
* :enum:`NGHTTP2_ERR_HEADER_COMP`
|
* :enum:`NGHTTP2_ERR_HEADER_COMP`
|
||||||
* Inflation process has failed.
|
* Inflation process has failed.
|
||||||
* :enum:`NGHTTP2_ERR_BUFFER_ERROR`
|
* :enum:`NGHTTP2_ERR_BUFFER_ERROR`
|
||||||
* The heder field name or value is too large.
|
* The header field name or value is too large.
|
||||||
*
|
*
|
||||||
* Example follows::
|
* Example follows::
|
||||||
*
|
*
|
||||||
|
|
|
@ -43,9 +43,6 @@ typedef struct {
|
||||||
/* nonzero if request HEADERS is canceled. The error code is stored
|
/* nonzero if request HEADERS is canceled. The error code is stored
|
||||||
in |error_code|. */
|
in |error_code|. */
|
||||||
uint8_t canceled;
|
uint8_t canceled;
|
||||||
/* nonzero if this item should be attached to stream object to make
|
|
||||||
it under priority control */
|
|
||||||
uint8_t attach_stream;
|
|
||||||
} nghttp2_headers_aux_data;
|
} nghttp2_headers_aux_data;
|
||||||
|
|
||||||
/* struct used for DATA frame */
|
/* struct used for DATA frame */
|
||||||
|
|
|
@ -44,11 +44,13 @@ void nghttp2_pq_free(nghttp2_pq *pq) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void swap(nghttp2_pq *pq, size_t i, size_t j) {
|
static void swap(nghttp2_pq *pq, size_t i, size_t j) {
|
||||||
nghttp2_pq_entry *t = pq->q[i];
|
nghttp2_pq_entry *a = pq->q[i];
|
||||||
pq->q[i] = pq->q[j];
|
nghttp2_pq_entry *b = pq->q[j];
|
||||||
pq->q[i]->index = i;
|
|
||||||
pq->q[j] = t;
|
pq->q[i] = b;
|
||||||
pq->q[j]->index = j;
|
b->index = i;
|
||||||
|
pq->q[j] = a;
|
||||||
|
a->index = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bubble_up(nghttp2_pq *pq, size_t index) {
|
static void bubble_up(nghttp2_pq *pq, size_t index) {
|
||||||
|
|
|
@ -635,7 +635,7 @@ int nghttp2_session_reprioritize_stream(
|
||||||
if (pri_spec->stream_id != 0) {
|
if (pri_spec->stream_id != 0) {
|
||||||
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
|
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
|
||||||
|
|
||||||
if (session->server && !dep_stream &&
|
if (!dep_stream &&
|
||||||
session_detect_idle_stream(session, pri_spec->stream_id)) {
|
session_detect_idle_stream(session, pri_spec->stream_id)) {
|
||||||
|
|
||||||
nghttp2_priority_spec_default_init(&pri_spec_default);
|
nghttp2_priority_spec_default_init(&pri_spec_default);
|
||||||
|
@ -671,15 +671,9 @@ int nghttp2_session_reprioritize_stream(
|
||||||
assert(dep_stream);
|
assert(dep_stream);
|
||||||
|
|
||||||
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
|
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
|
||||||
/* This is minor optimization when just weight is changed.
|
/* This is minor optimization when just weight is changed. */
|
||||||
Currently, we don't reschedule stream in this case, since we
|
nghttp2_stream_change_weight(stream, pri_spec->weight);
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,20 +723,6 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream && item->aux_data.headers.attach_stream) {
|
|
||||||
if (stream->item) {
|
|
||||||
return NGHTTP2_ERR_DATA_EXIST;
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = nghttp2_stream_attach_item(stream, item);
|
|
||||||
|
|
||||||
if (rv != 0) {
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
||||||
item->queued = 1;
|
item->queued = 1;
|
||||||
break;
|
break;
|
||||||
|
@ -778,6 +758,10 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don't have to call nghttp2_session_adjust_closed_stream()
|
||||||
|
here, since stream->stream_id is local stream_id, and it does
|
||||||
|
not affect closed stream count. */
|
||||||
|
|
||||||
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
nghttp2_outbound_queue_push(&session->ob_reg, item);
|
||||||
item->queued = 1;
|
item->queued = 1;
|
||||||
|
|
||||||
|
@ -909,15 +893,6 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (session->server && initial_state != NGHTTP2_STREAM_IDLE &&
|
|
||||||
!nghttp2_session_is_my_stream_id(session, stream_id)) {
|
|
||||||
|
|
||||||
rv = nghttp2_session_adjust_closed_stream(session, 1);
|
|
||||||
if (rv != 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
|
stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
|
||||||
if (stream == NULL) {
|
if (stream == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -929,7 +904,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
||||||
if (pri_spec->stream_id != 0) {
|
if (pri_spec->stream_id != 0) {
|
||||||
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
|
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
|
||||||
|
|
||||||
if (session->server && !dep_stream &&
|
if (!dep_stream &&
|
||||||
session_detect_idle_stream(session, pri_spec->stream_id)) {
|
session_detect_idle_stream(session, pri_spec->stream_id)) {
|
||||||
/* Depends on idle stream, which does not exist in memory.
|
/* Depends on idle stream, which does not exist in memory.
|
||||||
Assign default priority for it. */
|
Assign default priority for it. */
|
||||||
|
@ -994,11 +969,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
||||||
case NGHTTP2_STREAM_IDLE:
|
case NGHTTP2_STREAM_IDLE:
|
||||||
/* Idle stream does not count toward the concurrent streams limit.
|
/* Idle stream does not count toward the concurrent streams limit.
|
||||||
This is used as anchor node in dependency tree. */
|
This is used as anchor node in dependency tree. */
|
||||||
assert(session->server);
|
nghttp2_session_keep_idle_stream(session, stream);
|
||||||
rv = nghttp2_session_keep_idle_stream(session, stream);
|
|
||||||
if (rv != 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
|
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
|
||||||
|
@ -1103,7 +1074,9 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
|
||||||
/* On server side, retain stream at most MAX_CONCURRENT_STREAMS
|
/* On server side, retain stream at most MAX_CONCURRENT_STREAMS
|
||||||
combined with the current active incoming streams to make
|
combined with the current active incoming streams to make
|
||||||
dependency tree work better. */
|
dependency tree work better. */
|
||||||
rv = nghttp2_session_keep_closed_stream(session, stream);
|
nghttp2_session_keep_closed_stream(session, stream);
|
||||||
|
|
||||||
|
rv = nghttp2_session_adjust_closed_stream(session);
|
||||||
} else {
|
} else {
|
||||||
rv = nghttp2_session_destroy_stream(session, stream);
|
rv = nghttp2_session_destroy_stream(session, stream);
|
||||||
}
|
}
|
||||||
|
@ -1138,10 +1111,8 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
void nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||||
nghttp2_stream *stream) {
|
nghttp2_stream *stream) {
|
||||||
int rv;
|
|
||||||
|
|
||||||
DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
|
DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
|
||||||
stream, stream->stream_id, stream->state));
|
stream, stream->stream_id, stream->state));
|
||||||
|
|
||||||
|
@ -1154,19 +1125,10 @@ int nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||||
session->closed_stream_tail = stream;
|
session->closed_stream_tail = stream;
|
||||||
|
|
||||||
++session->num_closed_streams;
|
++session->num_closed_streams;
|
||||||
|
|
||||||
rv = nghttp2_session_adjust_closed_stream(session, 0);
|
|
||||||
if (rv != 0) {
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
void nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||||
}
|
|
||||||
|
|
||||||
int nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
|
||||||
nghttp2_stream *stream) {
|
nghttp2_stream *stream) {
|
||||||
int rv;
|
|
||||||
|
|
||||||
DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream,
|
DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream,
|
||||||
stream->stream_id, stream->state));
|
stream->stream_id, stream->state));
|
||||||
|
|
||||||
|
@ -1179,13 +1141,6 @@ int nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||||
session->idle_stream_tail = stream;
|
session->idle_stream_tail = stream;
|
||||||
|
|
||||||
++session->num_idle_streams;
|
++session->num_idle_streams;
|
||||||
|
|
||||||
rv = nghttp2_session_adjust_idle_stream(session);
|
|
||||||
if (rv != 0) {
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
||||||
|
@ -1216,8 +1171,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
||||||
--session->num_idle_streams;
|
--session->num_idle_streams;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
int nghttp2_session_adjust_closed_stream(nghttp2_session *session) {
|
||||||
size_t offset) {
|
|
||||||
size_t num_stream_max;
|
size_t num_stream_max;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
|
@ -1231,7 +1185,7 @@ int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
||||||
num_stream_max));
|
num_stream_max));
|
||||||
|
|
||||||
while (session->num_closed_streams > 0 &&
|
while (session->num_closed_streams > 0 &&
|
||||||
session->num_closed_streams + session->num_incoming_streams + offset >
|
session->num_closed_streams + session->num_incoming_streams >
|
||||||
num_stream_max) {
|
num_stream_max) {
|
||||||
nghttp2_stream *head_stream;
|
nghttp2_stream *head_stream;
|
||||||
nghttp2_stream *next;
|
nghttp2_stream *next;
|
||||||
|
@ -1267,12 +1221,10 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
|
||||||
size_t max;
|
size_t max;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
/* Make minimum number of idle streams 2 so that allocating 2
|
/* Make minimum number of idle streams 16, which is arbitrary chosen
|
||||||
streams at once is easy. This happens when PRIORITY frame to
|
number. */
|
||||||
idle stream, which depends on idle stream which does not
|
max = nghttp2_max(16,
|
||||||
exist. */
|
nghttp2_min(session->local_settings.max_concurrent_streams,
|
||||||
max =
|
|
||||||
nghttp2_max(2, nghttp2_min(session->local_settings.max_concurrent_streams,
|
|
||||||
session->pending_local_max_concurrent_stream));
|
session->pending_local_max_concurrent_stream));
|
||||||
|
|
||||||
DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
|
DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
|
||||||
|
@ -1331,6 +1283,15 @@ int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns nonzero if local endpoint allows reception of new stream
|
||||||
|
* from remote.
|
||||||
|
*/
|
||||||
|
static int session_allow_incoming_new_stream(nghttp2_session *session) {
|
||||||
|
return (session->goaway_flags &
|
||||||
|
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_SENT)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This function returns nonzero if session is closing.
|
* This function returns nonzero if session is closing.
|
||||||
*/
|
*/
|
||||||
|
@ -1850,6 +1811,9 @@ static int session_prep_frame(nghttp2_session *session,
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don't call nghttp2_session_adjust_closed_stream() here,
|
||||||
|
since we don't keep closed stream in client side */
|
||||||
|
|
||||||
estimated_payloadlen = session_estimate_headers_payload(
|
estimated_payloadlen = session_estimate_headers_payload(
|
||||||
session, frame->headers.nva, frame->headers.nvlen,
|
session, frame->headers.nva, frame->headers.nvlen,
|
||||||
NGHTTP2_PRIORITY_SPECLEN);
|
NGHTTP2_PRIORITY_SPECLEN);
|
||||||
|
@ -1899,7 +1863,7 @@ static int session_prep_frame(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
// If stream was alreay closed, nghttp2_session_get_stream()
|
// If stream was already closed, nghttp2_session_get_stream()
|
||||||
// returns NULL, but item is still attached to the stream.
|
// returns NULL, but item is still attached to the stream.
|
||||||
// Search stream including closed again.
|
// Search stream including closed again.
|
||||||
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||||
|
@ -2061,7 +2025,7 @@ static int session_prep_frame(nghttp2_session *session,
|
||||||
|
|
||||||
rv = nghttp2_session_predicate_data_send(session, stream);
|
rv = nghttp2_session_predicate_data_send(session, stream);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
// If stream was alreay closed, nghttp2_session_get_stream()
|
// If stream was already closed, nghttp2_session_get_stream()
|
||||||
// returns NULL, but item is still attached to the stream.
|
// returns NULL, but item is still attached to the stream.
|
||||||
// Search stream including closed again.
|
// Search stream including closed again.
|
||||||
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||||
|
@ -2442,11 +2406,25 @@ static int session_after_frame_sent1(nghttp2_session *session) {
|
||||||
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||||
|
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
|
if (!session_detect_idle_stream(session, frame->hd.stream_id)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream = nghttp2_session_open_stream(
|
||||||
|
session, frame->hd.stream_id, NGHTTP2_FLAG_NONE,
|
||||||
|
&frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL);
|
||||||
|
if (!stream) {
|
||||||
|
return NGHTTP2_ERR_NOMEM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
rv = nghttp2_session_reprioritize_stream(session, stream,
|
rv = nghttp2_session_reprioritize_stream(session, stream,
|
||||||
&frame->priority.pri_spec);
|
&frame->priority.pri_spec);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = nghttp2_session_adjust_idle_stream(session);
|
||||||
|
|
||||||
if (nghttp2_is_fatal(rv)) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
|
@ -2612,6 +2590,8 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
||||||
nghttp2_bufs *framebufs = &aob->framebufs;
|
nghttp2_bufs *framebufs = &aob->framebufs;
|
||||||
nghttp2_frame *frame;
|
nghttp2_frame *frame;
|
||||||
nghttp2_mem *mem;
|
nghttp2_mem *mem;
|
||||||
|
nghttp2_stream *stream;
|
||||||
|
nghttp2_data_aux_data *aux_data;
|
||||||
|
|
||||||
mem = &session->mem;
|
mem = &session->mem;
|
||||||
frame = &item->frame;
|
frame = &item->frame;
|
||||||
|
@ -2634,9 +2614,9 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
||||||
active_outbound_item_reset(&session->aob, mem);
|
active_outbound_item_reset(&session->aob, mem);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
}
|
||||||
nghttp2_stream *stream;
|
|
||||||
nghttp2_data_aux_data *aux_data;
|
/* DATA frame */
|
||||||
|
|
||||||
aux_data = &item->aux_data.data;
|
aux_data = &item->aux_data.data;
|
||||||
|
|
||||||
|
@ -2676,10 +2656,6 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/* Unreachable */
|
|
||||||
assert(0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int session_call_send_data(nghttp2_session *session,
|
static int session_call_send_data(nghttp2_session *session,
|
||||||
nghttp2_outbound_item *item,
|
nghttp2_outbound_item *item,
|
||||||
|
@ -2722,6 +2698,14 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
|
||||||
aob = &session->aob;
|
aob = &session->aob;
|
||||||
framebufs = &aob->framebufs;
|
framebufs = &aob->framebufs;
|
||||||
|
|
||||||
|
/* We may have idle streams more than we expect (e.g.,
|
||||||
|
nghttp2_session_change_stream_priority() or
|
||||||
|
nghttp2_session_create_idle_stream()). Adjust them here. */
|
||||||
|
rv = nghttp2_session_adjust_idle_stream(session);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
*data_ptr = NULL;
|
*data_ptr = NULL;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
switch (aob->state) {
|
switch (aob->state) {
|
||||||
|
@ -3519,35 +3503,44 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(session->server);
|
||||||
|
|
||||||
if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
|
if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
|
||||||
/* The spec says if an endpoint receives a HEADERS with invalid
|
/* The spec says if an endpoint receives a HEADERS with invalid
|
||||||
stream ID, it MUST issue connection error with error code
|
stream ID, it MUST issue connection error with error code
|
||||||
PROTOCOL_ERROR. But we could get trailer HEADERS after we have
|
PROTOCOL_ERROR. But we could get trailer HEADERS after we have
|
||||||
sent RST_STREAM to this stream and peer have not received it.
|
sent RST_STREAM to this stream and peer have not received it.
|
||||||
Then connection error is too harsh. It means that we only use
|
Then connection error is too harsh. It means that we only use
|
||||||
connection error if stream ID refers idle stream. OTherwise we
|
connection error if stream ID refers idle stream. Therwise we
|
||||||
just ignore HEADERS for now. */
|
just ignore HEADERS for now. */
|
||||||
if (session_detect_idle_stream(session, frame->hd.stream_id)) {
|
if (frame->hd.stream_id == 0 ||
|
||||||
|
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO,
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
"request HEADERS: invalid stream_id");
|
"request HEADERS: invalid stream_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
|
||||||
|
if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
|
||||||
|
return session_inflate_handle_invalid_connection(
|
||||||
|
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
|
||||||
|
}
|
||||||
|
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
}
|
}
|
||||||
session->last_recv_stream_id = frame->hd.stream_id;
|
session->last_recv_stream_id = frame->hd.stream_id;
|
||||||
|
|
||||||
if (session->goaway_flags & NGHTTP2_GOAWAY_SENT) {
|
|
||||||
/* We just ignore stream after GOAWAY was queued */
|
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session_is_incoming_concurrent_streams_max(session)) {
|
if (session_is_incoming_concurrent_streams_max(session)) {
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO,
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
"request HEADERS: max concurrent streams exceeded");
|
"request HEADERS: max concurrent streams exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session_allow_incoming_new_stream(session)) {
|
||||||
|
/* We just ignore stream after GOAWAY was sent */
|
||||||
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) {
|
if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) {
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself");
|
session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself");
|
||||||
|
@ -3564,7 +3557,14 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rv = nghttp2_session_adjust_closed_stream(session);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
session->last_proc_stream_id = session->last_recv_stream_id;
|
session->last_proc_stream_id = session->last_recv_stream_id;
|
||||||
|
|
||||||
rv = session_call_on_begin_headers(session, frame);
|
rv = session_call_on_begin_headers(session, frame);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return rv;
|
return rv;
|
||||||
|
@ -3590,9 +3590,11 @@ int nghttp2_session_on_response_headers_received(nghttp2_session *session,
|
||||||
If an endpoint receives additional frames for a stream that is
|
If an endpoint receives additional frames for a stream that is
|
||||||
in this state it MUST respond with a stream error (Section
|
in this state it MUST respond with a stream error (Section
|
||||||
5.4.2) of type STREAM_CLOSED.
|
5.4.2) of type STREAM_CLOSED.
|
||||||
|
|
||||||
|
We go further, and make it connection error.
|
||||||
*/
|
*/
|
||||||
return session_inflate_handle_invalid_stream(session, frame,
|
return session_inflate_handle_invalid_connection(
|
||||||
NGHTTP2_ERR_STREAM_CLOSED);
|
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
|
||||||
}
|
}
|
||||||
stream->state = NGHTTP2_STREAM_OPENED;
|
stream->state = NGHTTP2_STREAM_OPENED;
|
||||||
rv = session_call_on_begin_headers(session, frame);
|
rv = session_call_on_begin_headers(session, frame);
|
||||||
|
@ -3612,9 +3614,11 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
|
||||||
session, frame, NGHTTP2_ERR_PROTO,
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
"push response HEADERS: stream_id == 0");
|
"push response HEADERS: stream_id == 0");
|
||||||
}
|
}
|
||||||
if (session->goaway_flags) {
|
|
||||||
/* We don't accept new stream after GOAWAY is sent or received. */
|
if (session->server) {
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
return session_inflate_handle_invalid_connection(
|
||||||
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
|
"HEADERS: no HEADERS allowed from client in reserved state");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session_is_incoming_concurrent_streams_max(session)) {
|
if (session_is_incoming_concurrent_streams_max(session)) {
|
||||||
|
@ -3622,6 +3626,12 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
|
||||||
session, frame, NGHTTP2_ERR_PROTO,
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
"push response HEADERS: max concurrent streams exceeded");
|
"push response HEADERS: max concurrent streams exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session_allow_incoming_new_stream(session)) {
|
||||||
|
/* We don't accept new stream after GOAWAY was sent. */
|
||||||
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
if (session_is_incoming_concurrent_streams_pending_max(session)) {
|
if (session_is_incoming_concurrent_streams_pending_max(session)) {
|
||||||
return session_inflate_handle_invalid_stream(session, frame,
|
return session_inflate_handle_invalid_stream(session, frame,
|
||||||
NGHTTP2_ERR_REFUSED_STREAM);
|
NGHTTP2_ERR_REFUSED_STREAM);
|
||||||
|
@ -3647,23 +3657,17 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0");
|
session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0");
|
||||||
}
|
}
|
||||||
if (stream->state == NGHTTP2_STREAM_RESERVED) {
|
|
||||||
/* reserved. The valid push response HEADERS is processed by
|
|
||||||
nghttp2_session_on_push_response_headers_received(). This
|
|
||||||
generic HEADERS is called invalid cases for HEADERS against
|
|
||||||
reserved state. */
|
|
||||||
return session_inflate_handle_invalid_connection(
|
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream in reserved");
|
|
||||||
}
|
|
||||||
if ((stream->shut_flags & NGHTTP2_SHUT_RD)) {
|
if ((stream->shut_flags & NGHTTP2_SHUT_RD)) {
|
||||||
/* half closed (remote): from the spec:
|
/* half closed (remote): from the spec:
|
||||||
|
|
||||||
If an endpoint receives additional frames for a stream that is
|
If an endpoint receives additional frames for a stream that is
|
||||||
in this state it MUST respond with a stream error (Section
|
in this state it MUST respond with a stream error (Section
|
||||||
5.4.2) of type STREAM_CLOSED.
|
5.4.2) of type STREAM_CLOSED.
|
||||||
|
|
||||||
|
we go further, and make it connection error.
|
||||||
*/
|
*/
|
||||||
return session_inflate_handle_invalid_stream(session, frame,
|
return session_inflate_handle_invalid_connection(
|
||||||
NGHTTP2_ERR_STREAM_CLOSED);
|
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
|
||||||
}
|
}
|
||||||
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||||
if (stream->state == NGHTTP2_STREAM_OPENED) {
|
if (stream->state == NGHTTP2_STREAM_OPENED) {
|
||||||
|
@ -3672,15 +3676,9 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
} else if (stream->state == NGHTTP2_STREAM_CLOSING) {
|
|
||||||
/* This is race condition. NGHTTP2_STREAM_CLOSING indicates
|
|
||||||
that we queued RST_STREAM but it has not been sent. It will
|
|
||||||
eventually sent, so we just ignore this frame. */
|
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
|
||||||
} else {
|
|
||||||
return session_inflate_handle_invalid_stream(session, frame,
|
|
||||||
NGHTTP2_ERR_PROTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
}
|
}
|
||||||
/* If this is remote peer initiated stream, it is OK unless it
|
/* If this is remote peer initiated stream, it is OK unless it
|
||||||
has sent END_STREAM frame already. But if stream is in
|
has sent END_STREAM frame already. But if stream is in
|
||||||
|
@ -3715,20 +3713,18 @@ static int session_process_headers_frame(nghttp2_session *session) {
|
||||||
return nghttp2_session_on_request_headers_received(session, frame);
|
return nghttp2_session_on_request_headers_received(session, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
|
||||||
if (stream->state == NGHTTP2_STREAM_OPENING) {
|
|
||||||
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
|
|
||||||
return nghttp2_session_on_response_headers_received(session, frame,
|
|
||||||
stream);
|
|
||||||
}
|
|
||||||
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
|
|
||||||
return nghttp2_session_on_headers_received(session, frame, stream);
|
|
||||||
}
|
|
||||||
if (stream->state == NGHTTP2_STREAM_RESERVED) {
|
if (stream->state == NGHTTP2_STREAM_RESERVED) {
|
||||||
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
||||||
return nghttp2_session_on_push_response_headers_received(session, frame,
|
return nghttp2_session_on_push_response_headers_received(session, frame,
|
||||||
stream);
|
stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stream->state == NGHTTP2_STREAM_OPENING &&
|
||||||
|
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||||
|
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
|
||||||
|
return nghttp2_session_on_response_headers_received(session, frame, stream);
|
||||||
|
}
|
||||||
|
|
||||||
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
|
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
|
||||||
return nghttp2_session_on_headers_received(session, frame, stream);
|
return nghttp2_session_on_headers_received(session, frame, stream);
|
||||||
}
|
}
|
||||||
|
@ -3769,6 +3765,11 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
|
||||||
if (stream == NULL) {
|
if (stream == NULL) {
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rv = nghttp2_session_adjust_idle_stream(session);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
rv = nghttp2_session_reprioritize_stream(session, stream,
|
rv = nghttp2_session_reprioritize_stream(session, stream,
|
||||||
&frame->priority.pri_spec);
|
&frame->priority.pri_spec);
|
||||||
|
@ -3776,6 +3777,11 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
|
||||||
if (nghttp2_is_fatal(rv)) {
|
if (nghttp2_is_fatal(rv)) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rv = nghttp2_session_adjust_idle_stream(session);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return session_call_on_frame_received(session, frame);
|
return session_call_on_frame_received(session, frame);
|
||||||
|
@ -3805,6 +3811,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
|
||||||
return session_handle_invalid_connection(
|
return session_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "RST_STREAM: stream in idle");
|
session, frame, NGHTTP2_ERR_PROTO, "RST_STREAM: stream in idle");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
/* We may use stream->shut_flags for strict error checking. */
|
||||||
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = session_call_on_frame_received(session, frame);
|
rv = session_call_on_frame_received(session, frame);
|
||||||
|
@ -4220,17 +4229,17 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled");
|
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled");
|
||||||
}
|
}
|
||||||
if (session->goaway_flags) {
|
|
||||||
/* We just dicard PUSH_PROMISE after GOAWAY is sent or
|
|
||||||
received. */
|
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id");
|
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!session_allow_incoming_new_stream(session)) {
|
||||||
|
/* We just discard PUSH_PROMISE after GOAWAY was sent */
|
||||||
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
if (!session_is_new_peer_stream_id(session,
|
if (!session_is_new_peer_stream_id(session,
|
||||||
frame->push_promise.promised_stream_id)) {
|
frame->push_promise.promised_stream_id)) {
|
||||||
/* The spec says if an endpoint receives a PUSH_PROMISE with
|
/* The spec says if an endpoint receives a PUSH_PROMISE with
|
||||||
|
@ -4251,7 +4260,11 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
||||||
return session_inflate_handle_invalid_connection(
|
return session_inflate_handle_invalid_connection(
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
|
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Currently, client does not retain closed stream, so we don't
|
||||||
|
check NGHTTP2_SHUT_RD condition here. */
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = nghttp2_session_add_rst_stream(
|
rv = nghttp2_session_add_rst_stream(
|
||||||
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
|
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
@ -4259,25 +4272,13 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream->shut_flags & NGHTTP2_SHUT_RD) {
|
if (stream->shut_flags & NGHTTP2_SHUT_RD) {
|
||||||
if (session->callbacks.on_invalid_frame_recv_callback) {
|
return session_inflate_handle_invalid_connection(
|
||||||
if (session->callbacks.on_invalid_frame_recv_callback(
|
session, frame, NGHTTP2_ERR_STREAM_CLOSED,
|
||||||
session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data) !=
|
"PUSH_PROMISE: stream closed");
|
||||||
0) {
|
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv = nghttp2_session_add_rst_stream(session,
|
|
||||||
frame->push_promise.promised_stream_id,
|
|
||||||
NGHTTP2_PROTOCOL_ERROR);
|
|
||||||
if (rv != 0) {
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO It is unclear reserved stream dpeneds on associated
|
|
||||||
stream with or without exclusive flag set */
|
|
||||||
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
|
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
|
||||||
NGHTTP2_DEFAULT_WEIGHT, 0);
|
NGHTTP2_DEFAULT_WEIGHT, 0);
|
||||||
|
|
||||||
|
@ -4289,6 +4290,9 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don't call nghttp2_session_adjust_closed_stream(), since we
|
||||||
|
don't keep closed stream in client side */
|
||||||
|
|
||||||
session->last_proc_stream_id = session->last_recv_stream_id;
|
session->last_proc_stream_id = session->last_recv_stream_id;
|
||||||
rv = session_call_on_begin_headers(session, frame);
|
rv = session_call_on_begin_headers(session, frame);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
@ -4392,8 +4396,9 @@ session_on_connection_window_update_received(nghttp2_session *session,
|
||||||
nghttp2_frame *frame) {
|
nghttp2_frame *frame) {
|
||||||
/* Handle connection-level flow control */
|
/* Handle connection-level flow control */
|
||||||
if (frame->window_update.window_size_increment == 0) {
|
if (frame->window_update.window_size_increment == 0) {
|
||||||
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
|
return session_handle_invalid_connection(
|
||||||
NULL);
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
|
"WINDOW_UPDATE: window_size_increment == 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
|
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
|
||||||
|
@ -4423,7 +4428,9 @@ static int session_on_stream_window_update_received(nghttp2_session *session,
|
||||||
session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream");
|
session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream");
|
||||||
}
|
}
|
||||||
if (frame->window_update.window_size_increment == 0) {
|
if (frame->window_update.window_size_increment == 0) {
|
||||||
return session_handle_invalid_stream(session, frame, NGHTTP2_ERR_PROTO);
|
return session_handle_invalid_connection(
|
||||||
|
session, frame, NGHTTP2_ERR_PROTO,
|
||||||
|
"WINDOW_UPDATE: window_size_increment == 0");
|
||||||
}
|
}
|
||||||
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
|
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
|
||||||
stream->remote_window_size) {
|
stream->remote_window_size) {
|
||||||
|
@ -4736,6 +4743,14 @@ static int session_on_data_received_fail_fast(nghttp2_session *session) {
|
||||||
error_code = NGHTTP2_PROTOCOL_ERROR;
|
error_code = NGHTTP2_PROTOCOL_ERROR;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream = nghttp2_session_get_stream_raw(session, stream_id);
|
||||||
|
if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
|
||||||
|
failure_reason = "DATA: stream closed";
|
||||||
|
error_code = NGHTTP2_STREAM_CLOSED;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
return NGHTTP2_ERR_IGN_PAYLOAD;
|
return NGHTTP2_ERR_IGN_PAYLOAD;
|
||||||
}
|
}
|
||||||
if (stream->shut_flags & NGHTTP2_SHUT_RD) {
|
if (stream->shut_flags & NGHTTP2_SHUT_RD) {
|
||||||
|
@ -4920,6 +4935,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
||||||
|
|
||||||
mem = &session->mem;
|
mem = &session->mem;
|
||||||
|
|
||||||
|
/* We may have idle streams more than we expect (e.g.,
|
||||||
|
nghttp2_session_change_stream_priority() or
|
||||||
|
nghttp2_session_create_idle_stream()). Adjust them here. */
|
||||||
|
rv = nghttp2_session_adjust_idle_stream(session);
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
switch (iframe->state) {
|
switch (iframe->state) {
|
||||||
case NGHTTP2_IB_READ_CLIENT_MAGIC:
|
case NGHTTP2_IB_READ_CLIENT_MAGIC:
|
||||||
|
@ -6671,6 +6694,10 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
|
||||||
if (stream == NULL) {
|
if (stream == NULL) {
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don't call nghttp2_session_adjust_closed_stream(), since this
|
||||||
|
should be the first stream open. */
|
||||||
|
|
||||||
if (session->server) {
|
if (session->server) {
|
||||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
session->last_recv_stream_id = 1;
|
session->last_recv_stream_id = 1;
|
||||||
|
@ -6885,6 +6912,7 @@ int nghttp2_session_check_server_session(nghttp2_session *session) {
|
||||||
int nghttp2_session_change_stream_priority(
|
int nghttp2_session_change_stream_priority(
|
||||||
nghttp2_session *session, int32_t stream_id,
|
nghttp2_session *session, int32_t stream_id,
|
||||||
const nghttp2_priority_spec *pri_spec) {
|
const nghttp2_priority_spec *pri_spec) {
|
||||||
|
int rv;
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
nghttp2_priority_spec pri_spec_copy;
|
nghttp2_priority_spec pri_spec_copy;
|
||||||
|
|
||||||
|
@ -6900,7 +6928,18 @@ int nghttp2_session_change_stream_priority(
|
||||||
pri_spec_copy = *pri_spec;
|
pri_spec_copy = *pri_spec;
|
||||||
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
|
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
|
||||||
|
|
||||||
return nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
|
rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
|
||||||
|
|
||||||
|
if (nghttp2_is_fatal(rv)) {
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We don't intentionally call nghttp2_session_adjust_idle_stream()
|
||||||
|
so that idle stream created by this function, and existing ones
|
||||||
|
are kept for application. We will adjust number of idle stream
|
||||||
|
in nghttp2_session_mem_send or nghttp2_session_mem_recv is
|
||||||
|
called. */
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_session_create_idle_stream(nghttp2_session *session,
|
int nghttp2_session_create_idle_stream(nghttp2_session *session,
|
||||||
|
@ -6929,5 +6968,10 @@ int nghttp2_session_create_idle_stream(nghttp2_session *session,
|
||||||
return NGHTTP2_ERR_NOMEM;
|
return NGHTTP2_ERR_NOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* We don't intentionally call nghttp2_session_adjust_idle_stream()
|
||||||
|
so that idle stream created by this function, and existing ones
|
||||||
|
are kept for application. We will adjust number of idle stream
|
||||||
|
in nghttp2_session_mem_send or nghttp2_session_mem_recv is
|
||||||
|
called. */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,10 @@ typedef struct {
|
||||||
/* The default maximum number of incoming reserved streams */
|
/* The default maximum number of incoming reserved streams */
|
||||||
#define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200
|
#define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200
|
||||||
|
|
||||||
|
/* Even if we have less SETTINGS_MAX_CONCURRENT_STREAMS than this
|
||||||
|
number, we keep NGHTTP2_MIN_IDLE_STREAMS streams in idle state */
|
||||||
|
#define NGHTTP2_MIN_IDLE_STREAMS 16
|
||||||
|
|
||||||
/* The maximum number of items in outbound queue, which is considered
|
/* The maximum number of items in outbound queue, which is considered
|
||||||
as flooding caused by peer. All frames are not considered here.
|
as flooding caused by peer. All frames are not considered here.
|
||||||
We only consider PING + ACK and SETTINGS + ACK. This is because
|
We only consider PING + ACK and SETTINGS + ACK. This is because
|
||||||
|
@ -450,6 +454,11 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
|
||||||
*
|
*
|
||||||
* This function returns a pointer to created new stream object, or
|
* This function returns a pointer to created new stream object, or
|
||||||
* NULL.
|
* NULL.
|
||||||
|
*
|
||||||
|
* This function adjusts neither the number of closed streams or idle
|
||||||
|
* streams. The caller should manually call
|
||||||
|
* nghttp2_session_adjust_closed_stream() or
|
||||||
|
* nghttp2_session_adjust_idle_stream() respectively.
|
||||||
*/
|
*/
|
||||||
nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
||||||
int32_t stream_id, uint8_t flags,
|
int32_t stream_id, uint8_t flags,
|
||||||
|
@ -498,28 +507,16 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
|
||||||
* limitation of maximum number of streams in memory, |stream| is not
|
* limitation of maximum number of streams in memory, |stream| is not
|
||||||
* closed and just deleted from memory (see
|
* closed and just deleted from memory (see
|
||||||
* nghttp2_session_destroy_stream).
|
* nghttp2_session_destroy_stream).
|
||||||
*
|
|
||||||
* This function returns 0 if it succeeds, or one the following
|
|
||||||
* negative error codes:
|
|
||||||
*
|
|
||||||
* NGHTTP2_ERR_NOMEM
|
|
||||||
* Out of memory
|
|
||||||
*/
|
*/
|
||||||
int nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
void nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||||
nghttp2_stream *stream);
|
nghttp2_stream *stream);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Appends |stream| to linked list |session->idle_stream_head|. We
|
* Appends |stream| to linked list |session->idle_stream_head|. We
|
||||||
* apply fixed limit for list size. To fit into that limit, one or
|
* apply fixed limit for list size. To fit into that limit, one or
|
||||||
* more oldest streams are removed from list as necessary.
|
* more oldest streams are removed from list as necessary.
|
||||||
*
|
|
||||||
* This function returns 0 if it succeeds, or one the following
|
|
||||||
* negative error codes:
|
|
||||||
*
|
|
||||||
* NGHTTP2_ERR_NOMEM
|
|
||||||
* Out of memory
|
|
||||||
*/
|
*/
|
||||||
int nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
void nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||||
nghttp2_stream *stream);
|
nghttp2_stream *stream);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -531,9 +528,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
||||||
/*
|
/*
|
||||||
* Deletes closed stream to ensure that number of incoming streams
|
* Deletes closed stream to ensure that number of incoming streams
|
||||||
* including active and closed is in the maximum number of allowed
|
* including active and closed is in the maximum number of allowed
|
||||||
* stream. If |offset| is nonzero, it is decreased from the maximum
|
* stream.
|
||||||
* number of allowed stream when comparing number of active and closed
|
|
||||||
* stream and the maximum number.
|
|
||||||
*
|
*
|
||||||
* This function returns 0 if it succeeds, or one the following
|
* This function returns 0 if it succeeds, or one the following
|
||||||
* negative error codes:
|
* negative error codes:
|
||||||
|
@ -541,8 +536,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
||||||
* NGHTTP2_ERR_NOMEM
|
* NGHTTP2_ERR_NOMEM
|
||||||
* Out of memory
|
* Out of memory
|
||||||
*/
|
*/
|
||||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
int nghttp2_session_adjust_closed_stream(nghttp2_session *session);
|
||||||
size_t offset);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deletes idle stream to ensure that number of idle streams is in
|
* Deletes idle stream to ensure that number of idle streams is in
|
||||||
|
@ -814,6 +808,9 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
|
||||||
* |pri_spec|. Caller must ensure that stream->hd.stream_id !=
|
* |pri_spec|. Caller must ensure that stream->hd.stream_id !=
|
||||||
* pri_spec->stream_id.
|
* pri_spec->stream_id.
|
||||||
*
|
*
|
||||||
|
* This function does not adjust the number of idle streams. The
|
||||||
|
* caller should call nghttp2_session_adjust_idle_stream() later.
|
||||||
|
*
|
||||||
* This function returns 0 if it succeeds, or one of the following
|
* This function returns 0 if it succeeds, or one of the following
|
||||||
* negative error codes:
|
* negative error codes:
|
||||||
*
|
*
|
||||||
|
|
|
@ -80,6 +80,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
||||||
stream->queued = 0;
|
stream->queued = 0;
|
||||||
stream->descendant_last_cycle = 0;
|
stream->descendant_last_cycle = 0;
|
||||||
stream->cycle = 0;
|
stream->cycle = 0;
|
||||||
|
stream->pending_penalty = 0;
|
||||||
stream->descendant_next_seq = 0;
|
stream->descendant_next_seq = 0;
|
||||||
stream->seq = 0;
|
stream->seq = 0;
|
||||||
stream->last_writelen = 0;
|
stream->last_writelen = 0;
|
||||||
|
@ -115,9 +116,14 @@ static int stream_subtree_active(nghttp2_stream *stream) {
|
||||||
/*
|
/*
|
||||||
* Returns next cycle for |stream|.
|
* Returns next cycle for |stream|.
|
||||||
*/
|
*/
|
||||||
static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
|
static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
|
||||||
return last_cycle +
|
size_t penalty;
|
||||||
stream->last_writelen * NGHTTP2_MAX_WEIGHT / (uint32_t)stream->weight;
|
|
||||||
|
penalty =
|
||||||
|
stream->last_writelen * NGHTTP2_MAX_WEIGHT + stream->pending_penalty;
|
||||||
|
|
||||||
|
stream->cycle = last_cycle + penalty / (uint32_t)stream->weight;
|
||||||
|
stream->pending_penalty = (uint32_t)(penalty % (uint32_t)stream->weight);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
||||||
|
@ -125,7 +131,6 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
||||||
|
|
||||||
for (; dep_stream && !stream->queued;
|
for (; dep_stream && !stream->queued;
|
||||||
stream = dep_stream, dep_stream = dep_stream->dep_prev) {
|
stream = dep_stream, dep_stream = dep_stream->dep_prev) {
|
||||||
stream->cycle =
|
|
||||||
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||||
stream->seq = dep_stream->descendant_next_seq++;
|
stream->seq = dep_stream->descendant_next_seq++;
|
||||||
|
|
||||||
|
@ -169,6 +174,7 @@ static void stream_obq_remove(nghttp2_stream *stream) {
|
||||||
|
|
||||||
stream->queued = 0;
|
stream->queued = 0;
|
||||||
stream->cycle = 0;
|
stream->cycle = 0;
|
||||||
|
stream->pending_penalty = 0;
|
||||||
stream->descendant_last_cycle = 0;
|
stream->descendant_last_cycle = 0;
|
||||||
stream->last_writelen = 0;
|
stream->last_writelen = 0;
|
||||||
|
|
||||||
|
@ -207,25 +213,12 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
|
||||||
dep_stream = stream->dep_prev;
|
dep_stream = stream->dep_prev;
|
||||||
|
|
||||||
for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) {
|
for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) {
|
||||||
if (nghttp2_pq_size(&dep_stream->obq) == 1) {
|
|
||||||
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);
|
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
|
||||||
|
|
||||||
stream->cycle =
|
|
||||||
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||||
stream->seq = dep_stream->descendant_next_seq++;
|
stream->seq = dep_stream->descendant_next_seq++;
|
||||||
|
|
||||||
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
|
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
|
||||||
}
|
|
||||||
|
|
||||||
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
|
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
|
||||||
stream->stream_id, stream->cycle));
|
stream->stream_id, stream->cycle));
|
||||||
|
@ -234,6 +227,61 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
|
||||||
|
nghttp2_stream *dep_stream;
|
||||||
|
uint64_t last_cycle;
|
||||||
|
int32_t old_weight;
|
||||||
|
size_t wlen_penalty;
|
||||||
|
|
||||||
|
if (stream->weight == weight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_weight = stream->weight;
|
||||||
|
stream->weight = weight;
|
||||||
|
|
||||||
|
dep_stream = stream->dep_prev;
|
||||||
|
|
||||||
|
if (!dep_stream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dep_stream->sum_dep_weight += weight - old_weight;
|
||||||
|
|
||||||
|
if (!stream->queued) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
|
||||||
|
|
||||||
|
wlen_penalty = stream->last_writelen * NGHTTP2_MAX_WEIGHT;
|
||||||
|
|
||||||
|
/* Compute old stream->pending_penalty we used to calculate
|
||||||
|
stream->cycle */
|
||||||
|
stream->pending_penalty =
|
||||||
|
(uint32_t)((stream->pending_penalty + (uint32_t)old_weight -
|
||||||
|
(wlen_penalty % (uint32_t)old_weight)) %
|
||||||
|
(uint32_t)old_weight);
|
||||||
|
|
||||||
|
last_cycle = stream->cycle -
|
||||||
|
(wlen_penalty + stream->pending_penalty) / (uint32_t)old_weight;
|
||||||
|
|
||||||
|
/* Now we have old stream->pending_penalty and new stream->weight in
|
||||||
|
place */
|
||||||
|
stream_next_cycle(stream, last_cycle);
|
||||||
|
|
||||||
|
if (stream->cycle < dep_stream->descendant_last_cycle) {
|
||||||
|
stream->cycle = dep_stream->descendant_last_cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue to use same stream->seq */
|
||||||
|
|
||||||
|
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
|
||||||
|
|
||||||
|
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
|
||||||
|
stream->stream_id, stream->cycle));
|
||||||
|
}
|
||||||
|
|
||||||
static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
|
static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
|
||||||
for (; stream->sib_next; stream = stream->sib_next)
|
for (; stream->sib_next; stream = stream->sib_next)
|
||||||
;
|
;
|
||||||
|
@ -861,9 +909,15 @@ int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) {
|
||||||
nghttp2_outbound_item *
|
nghttp2_outbound_item *
|
||||||
nghttp2_stream_next_outbound_item(nghttp2_stream *stream) {
|
nghttp2_stream_next_outbound_item(nghttp2_stream *stream) {
|
||||||
nghttp2_pq_entry *ent;
|
nghttp2_pq_entry *ent;
|
||||||
|
nghttp2_stream *si;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (stream_active(stream)) {
|
if (stream_active(stream)) {
|
||||||
|
/* Update ascendant's descendant_last_cycle here, so that we can
|
||||||
|
assure that new stream is scheduled based on it. */
|
||||||
|
for (si = stream; si->dep_prev; si = si->dep_prev) {
|
||||||
|
si->dep_prev->descendant_last_cycle = si->cycle;
|
||||||
|
}
|
||||||
return stream->item;
|
return stream->item;
|
||||||
}
|
}
|
||||||
ent = nghttp2_pq_top(&stream->obq);
|
ent = nghttp2_pq_top(&stream->obq);
|
||||||
|
|
|
@ -197,6 +197,8 @@ struct nghttp2_stream {
|
||||||
int32_t local_window_size;
|
int32_t local_window_size;
|
||||||
/* weight of this stream */
|
/* weight of this stream */
|
||||||
int32_t weight;
|
int32_t weight;
|
||||||
|
/* This is unpaid penalty (offset) when calculating cycle. */
|
||||||
|
uint32_t pending_penalty;
|
||||||
/* sum of weight of direct descendants */
|
/* sum of weight of direct descendants */
|
||||||
int32_t sum_dep_weight;
|
int32_t sum_dep_weight;
|
||||||
nghttp2_stream_state state;
|
nghttp2_stream_state state;
|
||||||
|
@ -419,7 +421,14 @@ int nghttp2_stream_in_dep_tree(nghttp2_stream *stream);
|
||||||
void nghttp2_stream_reschedule(nghttp2_stream *stream);
|
void nghttp2_stream_reschedule(nghttp2_stream *stream);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns a stream which has highest priority.
|
* Changes |stream|'s weight to |weight|. If |stream| is queued, it
|
||||||
|
* will be rescheduled based on new weight.
|
||||||
|
*/
|
||||||
|
void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a stream which has highest priority, updating
|
||||||
|
* descendant_last_cycle of selected stream's ancestors.
|
||||||
*/
|
*/
|
||||||
nghttp2_outbound_item *
|
nghttp2_outbound_item *
|
||||||
nghttp2_stream_next_outbound_item(nghttp2_stream *stream);
|
nghttp2_stream_next_outbound_item(nghttp2_stream *stream);
|
||||||
|
|
|
@ -40,8 +40,7 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
|
||||||
const nghttp2_priority_spec *pri_spec,
|
const nghttp2_priority_spec *pri_spec,
|
||||||
nghttp2_nv *nva_copy, size_t nvlen,
|
nghttp2_nv *nva_copy, size_t nvlen,
|
||||||
const nghttp2_data_provider *data_prd,
|
const nghttp2_data_provider *data_prd,
|
||||||
void *stream_user_data,
|
void *stream_user_data) {
|
||||||
uint8_t attach_stream) {
|
|
||||||
int rv;
|
int rv;
|
||||||
uint8_t flags_copy;
|
uint8_t flags_copy;
|
||||||
nghttp2_outbound_item *item = NULL;
|
nghttp2_outbound_item *item = NULL;
|
||||||
|
@ -56,6 +55,16 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stream_id == -1) {
|
||||||
|
if ((int32_t)session->next_stream_id == pri_spec->stream_id) {
|
||||||
|
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else if (stream_id == pri_spec->stream_id) {
|
||||||
|
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
|
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
|
||||||
if (item == NULL) {
|
if (item == NULL) {
|
||||||
rv = NGHTTP2_ERR_NOMEM;
|
rv = NGHTTP2_ERR_NOMEM;
|
||||||
|
@ -69,7 +78,6 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
item->aux_data.headers.stream_user_data = stream_user_data;
|
item->aux_data.headers.stream_user_data = stream_user_data;
|
||||||
item->aux_data.headers.attach_stream = attach_stream;
|
|
||||||
|
|
||||||
flags_copy =
|
flags_copy =
|
||||||
(uint8_t)((flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
|
(uint8_t)((flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
|
||||||
|
@ -122,8 +130,7 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
|
||||||
const nghttp2_priority_spec *pri_spec,
|
const nghttp2_priority_spec *pri_spec,
|
||||||
const nghttp2_nv *nva, size_t nvlen,
|
const nghttp2_nv *nva, size_t nvlen,
|
||||||
const nghttp2_data_provider *data_prd,
|
const nghttp2_data_provider *data_prd,
|
||||||
void *stream_user_data,
|
void *stream_user_data) {
|
||||||
uint8_t attach_stream) {
|
|
||||||
int rv;
|
int rv;
|
||||||
nghttp2_nv *nva_copy;
|
nghttp2_nv *nva_copy;
|
||||||
nghttp2_priority_spec copy_pri_spec;
|
nghttp2_priority_spec copy_pri_spec;
|
||||||
|
@ -144,15 +151,14 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
|
|
||||||
return submit_headers_shared(session, flags, stream_id, ©_pri_spec,
|
return submit_headers_shared(session, flags, stream_id, ©_pri_spec,
|
||||||
nva_copy, nvlen, data_prd, stream_user_data,
|
nva_copy, nvlen, data_prd, stream_user_data);
|
||||||
attach_stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
|
int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
|
||||||
const nghttp2_nv *nva, size_t nvlen) {
|
const nghttp2_nv *nva, size_t nvlen) {
|
||||||
return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
|
return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
|
||||||
stream_id, NULL, nva, nvlen, NULL, NULL,
|
stream_id, NULL, nva, nvlen, NULL,
|
||||||
0);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
|
int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
|
||||||
|
@ -169,7 +175,7 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva,
|
return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva,
|
||||||
nvlen, NULL, stream_user_data, 0);
|
nvlen, NULL, stream_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_,
|
int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_,
|
||||||
|
@ -398,7 +404,7 @@ int32_t nghttp2_submit_request(nghttp2_session *session,
|
||||||
flags = set_request_flags(pri_spec, data_prd);
|
flags = set_request_flags(pri_spec, data_prd);
|
||||||
|
|
||||||
return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen,
|
return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen,
|
||||||
data_prd, stream_user_data, 0);
|
data_prd, stream_user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) {
|
static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) {
|
||||||
|
@ -414,7 +420,7 @@ int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
|
||||||
const nghttp2_data_provider *data_prd) {
|
const nghttp2_data_provider *data_prd) {
|
||||||
uint8_t flags = set_response_flags(data_prd);
|
uint8_t flags = set_response_flags(data_prd);
|
||||||
return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
|
return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
|
||||||
data_prd, NULL, 1);
|
data_prd, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
|
int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/dh.h>
|
||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ Config::Config()
|
||||||
max_concurrent_streams(100), header_table_size(-1), port(0),
|
max_concurrent_streams(100), header_table_size(-1), port(0),
|
||||||
verbose(false), daemon(false), verify_client(false), no_tls(false),
|
verbose(false), daemon(false), verify_client(false), no_tls(false),
|
||||||
error_gzip(false), early_response(false), hexdump(false),
|
error_gzip(false), early_response(false), hexdump(false),
|
||||||
echo_upload(false) {}
|
echo_upload(false), no_content_length(false) {}
|
||||||
|
|
||||||
Config::~Config() {}
|
Config::~Config() {}
|
||||||
|
|
||||||
|
@ -872,12 +873,14 @@ int Http2Handler::submit_file_response(const std::string &status,
|
||||||
std::string last_modified_str;
|
std::string last_modified_str;
|
||||||
auto nva = make_array(http2::make_nv_ls(":status", status),
|
auto nva = make_array(http2::make_nv_ls(":status", status),
|
||||||
http2::make_nv_ll("server", NGHTTPD_SERVER),
|
http2::make_nv_ll("server", NGHTTPD_SERVER),
|
||||||
http2::make_nv_ls("content-length", content_length),
|
|
||||||
http2::make_nv_ll("cache-control", "max-age=3600"),
|
http2::make_nv_ll("cache-control", "max-age=3600"),
|
||||||
http2::make_nv_ls("date", sessions_->get_cached_date()),
|
http2::make_nv_ls("date", sessions_->get_cached_date()),
|
||||||
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
|
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
|
||||||
http2::make_nv_ll("", ""));
|
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
|
||||||
size_t nvlen = 5;
|
size_t nvlen = 4;
|
||||||
|
if (!get_config()->no_content_length) {
|
||||||
|
nva[nvlen++] = http2::make_nv_ls("content-length", content_length);
|
||||||
|
}
|
||||||
if (last_modified != 0) {
|
if (last_modified != 0) {
|
||||||
last_modified_str = util::http_date(last_modified);
|
last_modified_str = util::http_date(last_modified);
|
||||||
nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
|
nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
|
||||||
|
@ -1087,7 +1090,9 @@ void prepare_echo_response(Stream *stream, Http2Handler *hd) {
|
||||||
|
|
||||||
Headers headers;
|
Headers headers;
|
||||||
headers.emplace_back("nghttpd-response", "echo");
|
headers.emplace_back("nghttpd-response", "echo");
|
||||||
|
if (!hd->get_config()->no_content_length) {
|
||||||
headers.emplace_back("content-length", util::utos(length));
|
headers.emplace_back("content-length", util::utos(length));
|
||||||
|
}
|
||||||
|
|
||||||
hd->submit_response("200", stream->stream_id, headers, &data_prd);
|
hd->submit_response("200", stream->stream_id, headers, &data_prd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ struct Config {
|
||||||
bool early_response;
|
bool early_response;
|
||||||
bool hexdump;
|
bool hexdump;
|
||||||
bool echo_upload;
|
bool echo_upload;
|
||||||
|
bool no_content_length;
|
||||||
Config();
|
Config();
|
||||||
~Config();
|
~Config();
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,12 +39,16 @@ using boost::asio::ip::tcp;
|
||||||
|
|
||||||
session::session(boost::asio::io_service &io_service, const std::string &host,
|
session::session(boost::asio::io_service &io_service, const std::string &host,
|
||||||
const std::string &service)
|
const std::string &service)
|
||||||
: impl_(make_unique<session_tcp_impl>(io_service, host, service)) {}
|
: impl_(std::make_shared<session_tcp_impl>(io_service, host, service)) {
|
||||||
|
impl_->start_resolve(host, service);
|
||||||
|
}
|
||||||
|
|
||||||
session::session(boost::asio::io_service &io_service,
|
session::session(boost::asio::io_service &io_service,
|
||||||
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
||||||
const std::string &service)
|
const std::string &service)
|
||||||
: impl_(make_unique<session_tls_impl>(io_service, tls_ctx, host, service)) {
|
: impl_(std::make_shared<session_tls_impl>(io_service, tls_ctx, host,
|
||||||
|
service)) {
|
||||||
|
impl_->start_resolve(host, service);
|
||||||
}
|
}
|
||||||
|
|
||||||
session::~session() {}
|
session::~session() {}
|
||||||
|
@ -93,6 +97,14 @@ const request *session::submit(boost::system::error_code &ec,
|
||||||
return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
|
return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void session::connect_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
impl_->connect_timeout(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void session::read_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
impl_->read_timeout(t);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
} // nghttp2
|
} // nghttp2
|
||||||
|
|
|
@ -40,8 +40,10 @@ namespace client {
|
||||||
|
|
||||||
session_impl::session_impl(boost::asio::io_service &io_service)
|
session_impl::session_impl(boost::asio::io_service &io_service)
|
||||||
: wblen_(0), io_service_(io_service), resolver_(io_service),
|
: wblen_(0), io_service_(io_service), resolver_(io_service),
|
||||||
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
|
deadline_(io_service), connect_timeout_(boost::posix_time::seconds(60)),
|
||||||
writing_(false), inside_callback_(false) {}
|
read_timeout_(boost::posix_time::seconds(60)), session_(nullptr),
|
||||||
|
data_pending_(nullptr), data_pendinglen_(0), writing_(false),
|
||||||
|
inside_callback_(false), stopped_(false) {}
|
||||||
|
|
||||||
session_impl::~session_impl() {
|
session_impl::~session_impl() {
|
||||||
// finish up all active stream
|
// finish up all active stream
|
||||||
|
@ -56,8 +58,12 @@ session_impl::~session_impl() {
|
||||||
|
|
||||||
void session_impl::start_resolve(const std::string &host,
|
void session_impl::start_resolve(const std::string &host,
|
||||||
const std::string &service) {
|
const std::string &service) {
|
||||||
|
deadline_.expires_from_now(connect_timeout_);
|
||||||
|
|
||||||
|
auto self = this->shared_from_this();
|
||||||
|
|
||||||
resolver_.async_resolve({host, service},
|
resolver_.async_resolve({host, service},
|
||||||
[this](const boost::system::error_code &ec,
|
[this, self](const boost::system::error_code &ec,
|
||||||
tcp::resolver::iterator endpoint_it) {
|
tcp::resolver::iterator endpoint_it) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
not_connected(ec);
|
not_connected(ec);
|
||||||
|
@ -66,6 +72,25 @@ void session_impl::start_resolve(const std::string &host,
|
||||||
|
|
||||||
start_connect(endpoint_it);
|
start_connect(endpoint_it);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deadline_.async_wait(std::bind(&session_impl::handle_deadline, self));
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_impl::handle_deadline() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deadline_.expires_at() <=
|
||||||
|
boost::asio::deadline_timer::traits_type::now()) {
|
||||||
|
call_error_cb(boost::asio::error::timed_out);
|
||||||
|
stop();
|
||||||
|
deadline_.expires_at(boost::posix_time::pos_infin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline_.async_wait(
|
||||||
|
std::bind(&session_impl::handle_deadline, this->shared_from_this()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::connected(tcp::resolver::iterator endpoint_it) {
|
void session_impl::connected(tcp::resolver::iterator endpoint_it) {
|
||||||
|
@ -86,6 +111,7 @@ void session_impl::connected(tcp::resolver::iterator endpoint_it) {
|
||||||
|
|
||||||
void session_impl::not_connected(const boost::system::error_code &ec) {
|
void session_impl::not_connected(const boost::system::error_code &ec) {
|
||||||
call_error_cb(ec);
|
call_error_cb(ec);
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
|
void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
|
||||||
|
@ -97,6 +123,9 @@ const connect_cb &session_impl::on_connect() const { return connect_cb_; }
|
||||||
const error_cb &session_impl::on_error() const { return error_cb_; }
|
const error_cb &session_impl::on_error() const { return error_cb_; }
|
||||||
|
|
||||||
void session_impl::call_error_cb(const boost::system::error_code &ec) {
|
void session_impl::call_error_cb(const boost::system::error_code &ec) {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto &error_cb = on_error();
|
auto &error_cb = on_error();
|
||||||
if (!error_cb) {
|
if (!error_cb) {
|
||||||
return;
|
return;
|
||||||
|
@ -350,12 +379,20 @@ int session_impl::write_trailer(stream &strm, header_map h) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::cancel(stream &strm, uint32_t error_code) {
|
void session_impl::cancel(stream &strm, uint32_t error_code) {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
|
nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
|
||||||
error_code);
|
error_code);
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::resume(stream &strm) {
|
void session_impl::resume(stream &strm) {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nghttp2_session_resume_data(session_, strm.stream_id());
|
nghttp2_session_resume_data(session_, strm.stream_id());
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
@ -396,6 +433,11 @@ const request *session_impl::submit(boost::system::error_code &ec,
|
||||||
header_map h) {
|
header_map h) {
|
||||||
ec.clear();
|
ec.clear();
|
||||||
|
|
||||||
|
if (stopped_) {
|
||||||
|
ec = make_error_code(static_cast<nghttp2_error>(NGHTTP2_INTERNAL_ERROR));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
http_parser_url u{};
|
http_parser_url u{};
|
||||||
// TODO Handle CONNECT method
|
// TODO Handle CONNECT method
|
||||||
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
|
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
|
||||||
|
@ -485,6 +527,10 @@ const request *session_impl::submit(boost::system::error_code &ec,
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::shutdown() {
|
void session_impl::shutdown() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
|
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
@ -522,13 +568,21 @@ void session_impl::leave_callback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::do_read() {
|
void session_impl::do_read() {
|
||||||
read_socket([this](const boost::system::error_code &ec,
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline_.expires_from_now(read_timeout_);
|
||||||
|
|
||||||
|
auto self = this->shared_from_this();
|
||||||
|
|
||||||
|
read_socket([this, self](const boost::system::error_code &ec,
|
||||||
std::size_t bytes_transferred) {
|
std::size_t bytes_transferred) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
if (!should_stop()) {
|
if (!should_stop()) {
|
||||||
call_error_cb(ec);
|
call_error_cb(ec);
|
||||||
shutdown_socket();
|
|
||||||
}
|
}
|
||||||
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,7 +595,7 @@ void session_impl::do_read() {
|
||||||
if (rv != static_cast<ssize_t>(bytes_transferred)) {
|
if (rv != static_cast<ssize_t>(bytes_transferred)) {
|
||||||
call_error_cb(make_error_code(
|
call_error_cb(make_error_code(
|
||||||
static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
|
static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
|
||||||
shutdown_socket();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -549,7 +603,7 @@ void session_impl::do_read() {
|
||||||
do_write();
|
do_write();
|
||||||
|
|
||||||
if (should_stop()) {
|
if (should_stop()) {
|
||||||
shutdown_socket();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +612,10 @@ void session_impl::do_read() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_impl::do_write() {
|
void session_impl::do_write() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (writing_) {
|
if (writing_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -579,7 +637,7 @@ void session_impl::do_write() {
|
||||||
auto n = nghttp2_session_mem_send(session_, &data);
|
auto n = nghttp2_session_mem_send(session_, &data);
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
|
call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
|
||||||
shutdown_socket();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,15 +659,25 @@ void session_impl::do_write() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wblen_ == 0) {
|
if (wblen_ == 0) {
|
||||||
|
if (should_stop()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writing_ = true;
|
writing_ = true;
|
||||||
|
|
||||||
write_socket([this](const boost::system::error_code &ec, std::size_t n) {
|
// Reset read deadline here, because normally client is sending
|
||||||
|
// something, it does not expect timeout while doing it.
|
||||||
|
deadline_.expires_from_now(read_timeout_);
|
||||||
|
|
||||||
|
auto self = this->shared_from_this();
|
||||||
|
|
||||||
|
write_socket(
|
||||||
|
[this, self](const boost::system::error_code &ec, std::size_t n) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
call_error_cb(ec);
|
call_error_cb(ec);
|
||||||
shutdown_socket();
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,6 +688,24 @@ void session_impl::do_write() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void session_impl::stop() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown_socket();
|
||||||
|
deadline_.cancel();
|
||||||
|
stopped_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_impl::connect_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
connect_timeout_ = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void session_impl::read_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
read_timeout_ = t;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
} // nghttp2
|
} // nghttp2
|
||||||
|
|
|
@ -41,7 +41,7 @@ class stream;
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
class session_impl {
|
class session_impl : public std::enable_shared_from_this<session_impl> {
|
||||||
public:
|
public:
|
||||||
session_impl(boost::asio::io_service &io_service);
|
session_impl(boost::asio::io_service &io_service);
|
||||||
virtual ~session_impl();
|
virtual ~session_impl();
|
||||||
|
@ -91,6 +91,11 @@ public:
|
||||||
void do_read();
|
void do_read();
|
||||||
void do_write();
|
void do_write();
|
||||||
|
|
||||||
|
void connect_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
void read_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
boost::array<uint8_t, 8_k> rb_;
|
boost::array<uint8_t, 8_k> rb_;
|
||||||
boost::array<uint8_t, 64_k> wb_;
|
boost::array<uint8_t, 64_k> wb_;
|
||||||
|
@ -100,6 +105,7 @@ private:
|
||||||
bool should_stop() const;
|
bool should_stop() const;
|
||||||
bool setup_session();
|
bool setup_session();
|
||||||
void call_error_cb(const boost::system::error_code &ec);
|
void call_error_cb(const boost::system::error_code &ec);
|
||||||
|
void handle_deadline();
|
||||||
|
|
||||||
boost::asio::io_service &io_service_;
|
boost::asio::io_service &io_service_;
|
||||||
tcp::resolver resolver_;
|
tcp::resolver resolver_;
|
||||||
|
@ -109,6 +115,10 @@ private:
|
||||||
connect_cb connect_cb_;
|
connect_cb connect_cb_;
|
||||||
error_cb error_cb_;
|
error_cb error_cb_;
|
||||||
|
|
||||||
|
boost::asio::deadline_timer deadline_;
|
||||||
|
boost::posix_time::time_duration connect_timeout_;
|
||||||
|
boost::posix_time::time_duration read_timeout_;
|
||||||
|
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
|
|
||||||
const uint8_t *data_pending_;
|
const uint8_t *data_pending_;
|
||||||
|
@ -116,6 +126,7 @@ private:
|
||||||
|
|
||||||
bool writing_;
|
bool writing_;
|
||||||
bool inside_callback_;
|
bool inside_callback_;
|
||||||
|
bool stopped_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
|
|
|
@ -31,9 +31,7 @@ namespace client {
|
||||||
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
|
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
|
||||||
const std::string &host,
|
const std::string &host,
|
||||||
const std::string &service)
|
const std::string &service)
|
||||||
: session_impl(io_service), socket_(io_service) {
|
: session_impl(io_service), socket_(io_service) {}
|
||||||
start_resolve(host, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
session_tcp_impl::~session_tcp_impl() {}
|
session_tcp_impl::~session_tcp_impl() {}
|
||||||
|
|
||||||
|
@ -62,7 +60,10 @@ void session_tcp_impl::write_socket(
|
||||||
boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h);
|
boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_tcp_impl::shutdown_socket() { socket_.close(); }
|
void session_tcp_impl::shutdown_socket() {
|
||||||
|
boost::system::error_code ignored_ec;
|
||||||
|
socket_.close(ignored_ec);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
|
|
|
@ -38,8 +38,6 @@ session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
|
||||||
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
|
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
|
||||||
// not used, which is what we want.
|
// not used, which is what we want.
|
||||||
socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
|
socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
|
||||||
|
|
||||||
start_resolve(host, service);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
session_tls_impl::~session_tls_impl() {}
|
session_tls_impl::~session_tls_impl() {}
|
||||||
|
@ -85,7 +83,8 @@ void session_tls_impl::write_socket(
|
||||||
}
|
}
|
||||||
|
|
||||||
void session_tls_impl::shutdown_socket() {
|
void session_tls_impl::shutdown_socket() {
|
||||||
socket_.async_shutdown([](const boost::system::error_code &ec) {});
|
boost::system::error_code ignored_ec;
|
||||||
|
socket_.lowest_layer().close(ignored_ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace client
|
} // namespace client
|
||||||
|
|
|
@ -92,6 +92,11 @@ boost::asio::io_service &io_service_pool::get_io_service() {
|
||||||
return io_service;
|
return io_service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
io_service_pool::io_services() const {
|
||||||
|
return io_services_;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -70,6 +70,10 @@ public:
|
||||||
/// Get an io_service to use.
|
/// Get an io_service to use.
|
||||||
boost::asio::io_service &get_io_service();
|
boost::asio::io_service &get_io_service();
|
||||||
|
|
||||||
|
/// Get access to all io_service objects.
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
io_services() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// The pool of io_services.
|
/// The pool of io_services.
|
||||||
std::vector<std::shared_ptr<boost::asio::io_service>> io_services_;
|
std::vector<std::shared_ptr<boost::asio::io_service>> io_services_;
|
||||||
|
|
|
@ -44,8 +44,12 @@ namespace nghttp2 {
|
||||||
namespace asio_http2 {
|
namespace asio_http2 {
|
||||||
namespace server {
|
namespace server {
|
||||||
|
|
||||||
server::server(std::size_t io_service_pool_size)
|
server::server(std::size_t io_service_pool_size,
|
||||||
: io_service_pool_(io_service_pool_size) {}
|
const boost::posix_time::time_duration &tls_handshake_timeout,
|
||||||
|
const boost::posix_time::time_duration &read_timeout)
|
||||||
|
: io_service_pool_(io_service_pool_size),
|
||||||
|
tls_handshake_timeout_(tls_handshake_timeout),
|
||||||
|
read_timeout_(read_timeout) {}
|
||||||
|
|
||||||
boost::system::error_code
|
boost::system::error_code
|
||||||
server::listen_and_serve(boost::system::error_code &ec,
|
server::listen_and_serve(boost::system::error_code &ec,
|
||||||
|
@ -121,7 +125,8 @@ boost::system::error_code server::bind_and_listen(boost::system::error_code &ec,
|
||||||
void server::start_accept(boost::asio::ssl::context &tls_context,
|
void server::start_accept(boost::asio::ssl::context &tls_context,
|
||||||
tcp::acceptor &acceptor, serve_mux &mux) {
|
tcp::acceptor &acceptor, serve_mux &mux) {
|
||||||
auto new_connection = std::make_shared<connection<ssl_socket>>(
|
auto new_connection = std::make_shared<connection<ssl_socket>>(
|
||||||
mux, io_service_pool_.get_io_service(), tls_context);
|
mux, tls_handshake_timeout_, read_timeout_,
|
||||||
|
io_service_pool_.get_io_service(), tls_context);
|
||||||
|
|
||||||
acceptor.async_accept(
|
acceptor.async_accept(
|
||||||
new_connection->socket().lowest_layer(),
|
new_connection->socket().lowest_layer(),
|
||||||
|
@ -130,14 +135,17 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
|
||||||
if (!e) {
|
if (!e) {
|
||||||
new_connection->socket().lowest_layer().set_option(
|
new_connection->socket().lowest_layer().set_option(
|
||||||
tcp::no_delay(true));
|
tcp::no_delay(true));
|
||||||
|
new_connection->start_tls_handshake_deadline();
|
||||||
new_connection->socket().async_handshake(
|
new_connection->socket().async_handshake(
|
||||||
boost::asio::ssl::stream_base::server,
|
boost::asio::ssl::stream_base::server,
|
||||||
[new_connection](const boost::system::error_code &e) {
|
[new_connection](const boost::system::error_code &e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
|
new_connection->stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tls_h2_negotiated(new_connection->socket())) {
|
if (!tls_h2_negotiated(new_connection->socket())) {
|
||||||
|
new_connection->stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,13 +159,15 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
|
||||||
|
|
||||||
void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
|
void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
|
||||||
auto new_connection = std::make_shared<connection<tcp::socket>>(
|
auto new_connection = std::make_shared<connection<tcp::socket>>(
|
||||||
mux, io_service_pool_.get_io_service());
|
mux, tls_handshake_timeout_, read_timeout_,
|
||||||
|
io_service_pool_.get_io_service());
|
||||||
|
|
||||||
acceptor.async_accept(
|
acceptor.async_accept(
|
||||||
new_connection->socket(), [this, &acceptor, &mux, new_connection](
|
new_connection->socket(), [this, &acceptor, &mux, new_connection](
|
||||||
const boost::system::error_code &e) {
|
const boost::system::error_code &e) {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
new_connection->socket().set_option(tcp::no_delay(true));
|
new_connection->socket().set_option(tcp::no_delay(true));
|
||||||
|
new_connection->start_read_deadline();
|
||||||
new_connection->start();
|
new_connection->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +179,11 @@ void server::stop() { io_service_pool_.stop(); }
|
||||||
|
|
||||||
void server::join() { io_service_pool_.join(); }
|
void server::join() { io_service_pool_.join(); }
|
||||||
|
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
server::io_services() const {
|
||||||
|
return io_service_pool_.io_services();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -63,7 +63,9 @@ using ssl_socket = boost::asio::ssl::stream<tcp::socket>;
|
||||||
|
|
||||||
class server : private boost::noncopyable {
|
class server : private boost::noncopyable {
|
||||||
public:
|
public:
|
||||||
explicit server(std::size_t io_service_pool_size);
|
explicit server(std::size_t io_service_pool_size,
|
||||||
|
const boost::posix_time::time_duration &tls_handshake_timeout,
|
||||||
|
const boost::posix_time::time_duration &read_timeout);
|
||||||
|
|
||||||
boost::system::error_code
|
boost::system::error_code
|
||||||
listen_and_serve(boost::system::error_code &ec,
|
listen_and_serve(boost::system::error_code &ec,
|
||||||
|
@ -73,6 +75,10 @@ public:
|
||||||
void join();
|
void join();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
/// Get access to all io_service objects.
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
io_services() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Initiate an asynchronous accept operation.
|
/// Initiate an asynchronous accept operation.
|
||||||
void start_accept(tcp::acceptor &acceptor, serve_mux &mux);
|
void start_accept(tcp::acceptor &acceptor, serve_mux &mux);
|
||||||
|
@ -94,6 +100,9 @@ private:
|
||||||
std::vector<tcp::acceptor> acceptors_;
|
std::vector<tcp::acceptor> acceptors_;
|
||||||
|
|
||||||
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
|
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
|
||||||
|
|
||||||
|
boost::posix_time::time_duration tls_handshake_timeout_;
|
||||||
|
boost::posix_time::time_duration read_timeout_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
|
@ -64,15 +64,23 @@ class connection : public std::enable_shared_from_this<connection<socket_type>>,
|
||||||
public:
|
public:
|
||||||
/// Construct a connection with the given io_service.
|
/// Construct a connection with the given io_service.
|
||||||
template <typename... SocketArgs>
|
template <typename... SocketArgs>
|
||||||
explicit connection(serve_mux &mux, SocketArgs &&... args)
|
explicit connection(
|
||||||
: socket_(std::forward<SocketArgs>(args)...), mux_(mux), writing_(false) {
|
serve_mux &mux,
|
||||||
}
|
const boost::posix_time::time_duration &tls_handshake_timeout,
|
||||||
|
const boost::posix_time::time_duration &read_timeout,
|
||||||
|
SocketArgs &&... args)
|
||||||
|
: socket_(std::forward<SocketArgs>(args)...), mux_(mux),
|
||||||
|
deadline_(socket_.get_io_service()),
|
||||||
|
tls_handshake_timeout_(tls_handshake_timeout),
|
||||||
|
read_timeout_(read_timeout), writing_(false), stopped_(false) {}
|
||||||
|
|
||||||
/// Start the first asynchronous operation for the connection.
|
/// Start the first asynchronous operation for the connection.
|
||||||
void start() {
|
void start() {
|
||||||
handler_ = std::make_shared<http2_handler>(socket_.get_io_service(),
|
handler_ = std::make_shared<http2_handler>(
|
||||||
|
socket_.get_io_service(), socket_.lowest_layer().remote_endpoint(),
|
||||||
[this]() { do_write(); }, mux_);
|
[this]() { do_write(); }, mux_);
|
||||||
if (handler_->start() != 0) {
|
if (handler_->start() != 0) {
|
||||||
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
do_read();
|
do_read();
|
||||||
|
@ -80,26 +88,61 @@ public:
|
||||||
|
|
||||||
socket_type &socket() { return socket_; }
|
socket_type &socket() { return socket_; }
|
||||||
|
|
||||||
|
void start_tls_handshake_deadline() {
|
||||||
|
deadline_.expires_from_now(tls_handshake_timeout_);
|
||||||
|
deadline_.async_wait(
|
||||||
|
std::bind(&connection::handle_deadline, this->shared_from_this()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_read_deadline() {
|
||||||
|
deadline_.expires_from_now(read_timeout_);
|
||||||
|
deadline_.async_wait(
|
||||||
|
std::bind(&connection::handle_deadline, this->shared_from_this()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_deadline() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deadline_.expires_at() <=
|
||||||
|
boost::asio::deadline_timer::traits_type::now()) {
|
||||||
|
stop();
|
||||||
|
deadline_.expires_at(boost::posix_time::pos_infin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deadline_.async_wait(
|
||||||
|
std::bind(&connection::handle_deadline, this->shared_from_this()));
|
||||||
|
}
|
||||||
|
|
||||||
void do_read() {
|
void do_read() {
|
||||||
auto self = this->shared_from_this();
|
auto self = this->shared_from_this();
|
||||||
|
|
||||||
|
deadline_.expires_from_now(read_timeout_);
|
||||||
|
|
||||||
socket_.async_read_some(
|
socket_.async_read_some(
|
||||||
boost::asio::buffer(buffer_),
|
boost::asio::buffer(buffer_),
|
||||||
[this, self](const boost::system::error_code &e,
|
[this, self](const boost::system::error_code &e,
|
||||||
std::size_t bytes_transferred) {
|
std::size_t bytes_transferred) {
|
||||||
if (!e) {
|
if (e) {
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (handler_->on_read(buffer_, bytes_transferred) != 0) {
|
if (handler_->on_read(buffer_, bytes_transferred) != 0) {
|
||||||
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
do_write();
|
do_write();
|
||||||
|
|
||||||
if (!writing_ && handler_->should_stop()) {
|
if (!writing_ && handler_->should_stop()) {
|
||||||
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
do_read();
|
do_read();
|
||||||
}
|
|
||||||
|
|
||||||
// If an error occurs then no new asynchronous operations are
|
// If an error occurs then no new asynchronous operations are
|
||||||
// started. This means that all shared_ptr references to the
|
// started. This means that all shared_ptr references to the
|
||||||
|
@ -122,23 +165,34 @@ public:
|
||||||
rv = handler_->on_write(outbuf_, nwrite);
|
rv = handler_->on_write(outbuf_, nwrite);
|
||||||
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nwrite == 0) {
|
if (nwrite == 0) {
|
||||||
|
if (handler_->should_stop()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
writing_ = true;
|
writing_ = true;
|
||||||
|
|
||||||
|
// Reset read deadline here, because normally client is sending
|
||||||
|
// something, it does not expect timeout while doing it.
|
||||||
|
deadline_.expires_from_now(read_timeout_);
|
||||||
|
|
||||||
boost::asio::async_write(
|
boost::asio::async_write(
|
||||||
socket_, boost::asio::buffer(outbuf_, nwrite),
|
socket_, boost::asio::buffer(outbuf_, nwrite),
|
||||||
[this, self](const boost::system::error_code &e, std::size_t) {
|
[this, self](const boost::system::error_code &e, std::size_t) {
|
||||||
if (!e) {
|
if (e) {
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
writing_ = false;
|
writing_ = false;
|
||||||
|
|
||||||
do_write();
|
do_write();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// No new asynchronous operations are started. This means that all
|
// No new asynchronous operations are started. This means that all
|
||||||
|
@ -147,6 +201,17 @@ public:
|
||||||
// returns. The connection class's destructor closes the socket.
|
// returns. The connection class's destructor closes the socket.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (stopped_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped_ = true;
|
||||||
|
boost::system::error_code ignored_ec;
|
||||||
|
socket_.lowest_layer().close(ignored_ec);
|
||||||
|
deadline_.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
socket_type socket_;
|
socket_type socket_;
|
||||||
|
|
||||||
|
@ -159,7 +224,12 @@ private:
|
||||||
|
|
||||||
boost::array<uint8_t, 64_k> outbuf_;
|
boost::array<uint8_t, 64_k> outbuf_;
|
||||||
|
|
||||||
|
boost::asio::deadline_timer deadline_;
|
||||||
|
boost::posix_time::time_duration tls_handshake_timeout_;
|
||||||
|
boost::posix_time::time_duration read_timeout_;
|
||||||
|
|
||||||
bool writing_;
|
bool writing_;
|
||||||
|
bool stopped_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
|
@ -69,6 +69,14 @@ void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
|
||||||
|
|
||||||
void http2::backlog(int backlog) { impl_->backlog(backlog); }
|
void http2::backlog(int backlog) { impl_->backlog(backlog); }
|
||||||
|
|
||||||
|
void http2::tls_handshake_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
impl_->tls_handshake_timeout(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http2::read_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
impl_->read_timeout(t);
|
||||||
|
}
|
||||||
|
|
||||||
bool http2::handle(std::string pattern, request_cb cb) {
|
bool http2::handle(std::string pattern, request_cb cb) {
|
||||||
return impl_->handle(std::move(pattern), std::move(cb));
|
return impl_->handle(std::move(pattern), std::move(cb));
|
||||||
}
|
}
|
||||||
|
@ -77,6 +85,11 @@ void http2::stop() { impl_->stop(); }
|
||||||
|
|
||||||
void http2::join() { return impl_->join(); }
|
void http2::join() { return impl_->join(); }
|
||||||
|
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
http2::io_services() const {
|
||||||
|
return impl_->io_services();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
|
|
|
@ -136,6 +136,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &req = strm->request().impl();
|
||||||
|
req.remote_endpoint(handler->remote_endpoint());
|
||||||
|
|
||||||
handler->call_on_request(*strm);
|
handler->call_on_request(*strm);
|
||||||
|
|
||||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
|
@ -225,8 +228,9 @@ int on_frame_not_send_callback(nghttp2_session *session,
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
http2_handler::http2_handler(boost::asio::io_service &io_service,
|
http2_handler::http2_handler(boost::asio::io_service &io_service,
|
||||||
|
boost::asio::ip::tcp::endpoint ep,
|
||||||
connection_write writefun, serve_mux &mux)
|
connection_write writefun, serve_mux &mux)
|
||||||
: writefun_(writefun), mux_(mux), io_service_(io_service),
|
: writefun_(writefun), mux_(mux), io_service_(io_service), remote_ep_(ep),
|
||||||
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false),
|
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false),
|
||||||
tstamp_cached_(time(nullptr)),
|
tstamp_cached_(time(nullptr)),
|
||||||
formatted_date_(util::http_date(tstamp_cached_)) {}
|
formatted_date_(util::http_date(tstamp_cached_)) {}
|
||||||
|
@ -449,6 +453,10 @@ response *http2_handler::push_promise(boost::system::error_code &ec,
|
||||||
|
|
||||||
boost::asio::io_service &http2_handler::io_service() { return io_service_; }
|
boost::asio::io_service &http2_handler::io_service() { return io_service_; }
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::endpoint &http2_handler::remote_endpoint() {
|
||||||
|
return remote_ep_;
|
||||||
|
}
|
||||||
|
|
||||||
callback_guard::callback_guard(http2_handler &h) : handler(h) {
|
callback_guard::callback_guard(http2_handler &h) : handler(h) {
|
||||||
handler.enter_callback();
|
handler.enter_callback();
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,8 @@ using connection_write = std::function<void(void)>;
|
||||||
|
|
||||||
class http2_handler : public std::enable_shared_from_this<http2_handler> {
|
class http2_handler : public std::enable_shared_from_this<http2_handler> {
|
||||||
public:
|
public:
|
||||||
http2_handler(boost::asio::io_service &io_service, connection_write writefun,
|
http2_handler(boost::asio::io_service &io_service,
|
||||||
|
boost::asio::ip::tcp::endpoint ep, connection_write writefun,
|
||||||
serve_mux &mux);
|
serve_mux &mux);
|
||||||
|
|
||||||
~http2_handler();
|
~http2_handler();
|
||||||
|
@ -89,6 +90,8 @@ public:
|
||||||
|
|
||||||
boost::asio::io_service &io_service();
|
boost::asio::io_service &io_service();
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::endpoint &remote_endpoint();
|
||||||
|
|
||||||
const std::string &http_date();
|
const std::string &http_date();
|
||||||
|
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
|
@ -152,6 +155,7 @@ private:
|
||||||
connection_write writefun_;
|
connection_write writefun_;
|
||||||
serve_mux &mux_;
|
serve_mux &mux_;
|
||||||
boost::asio::io_service &io_service_;
|
boost::asio::io_service &io_service_;
|
||||||
|
boost::asio::ip::tcp::endpoint remote_ep_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
const uint8_t *buf_;
|
const uint8_t *buf_;
|
||||||
std::size_t buflen_;
|
std::size_t buflen_;
|
||||||
|
|
|
@ -37,12 +37,16 @@ namespace asio_http2 {
|
||||||
|
|
||||||
namespace server {
|
namespace server {
|
||||||
|
|
||||||
http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {}
|
http2_impl::http2_impl()
|
||||||
|
: num_threads_(1), backlog_(-1),
|
||||||
|
tls_handshake_timeout_(boost::posix_time::seconds(60)),
|
||||||
|
read_timeout_(boost::posix_time::seconds(60)) {}
|
||||||
|
|
||||||
boost::system::error_code http2_impl::listen_and_serve(
|
boost::system::error_code http2_impl::listen_and_serve(
|
||||||
boost::system::error_code &ec, boost::asio::ssl::context *tls_context,
|
boost::system::error_code &ec, boost::asio::ssl::context *tls_context,
|
||||||
const std::string &address, const std::string &port, bool asynchronous) {
|
const std::string &address, const std::string &port, bool asynchronous) {
|
||||||
server_.reset(new server(num_threads_));
|
server_.reset(
|
||||||
|
new server(num_threads_, tls_handshake_timeout_, read_timeout_));
|
||||||
return server_->listen_and_serve(ec, tls_context, address, port, backlog_,
|
return server_->listen_and_serve(ec, tls_context, address, port, backlog_,
|
||||||
mux_, asynchronous);
|
mux_, asynchronous);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +55,15 @@ void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; }
|
||||||
|
|
||||||
void http2_impl::backlog(int backlog) { backlog_ = backlog; }
|
void http2_impl::backlog(int backlog) { backlog_ = backlog; }
|
||||||
|
|
||||||
|
void http2_impl::tls_handshake_timeout(
|
||||||
|
const boost::posix_time::time_duration &t) {
|
||||||
|
tls_handshake_timeout_ = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http2_impl::read_timeout(const boost::posix_time::time_duration &t) {
|
||||||
|
read_timeout_ = t;
|
||||||
|
}
|
||||||
|
|
||||||
bool http2_impl::handle(std::string pattern, request_cb cb) {
|
bool http2_impl::handle(std::string pattern, request_cb cb) {
|
||||||
return mux_.handle(std::move(pattern), std::move(cb));
|
return mux_.handle(std::move(pattern), std::move(cb));
|
||||||
}
|
}
|
||||||
|
@ -59,6 +72,11 @@ void http2_impl::stop() { return server_->stop(); }
|
||||||
|
|
||||||
void http2_impl::join() { return server_->join(); }
|
void http2_impl::join() { return server_->join(); }
|
||||||
|
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
http2_impl::io_services() const {
|
||||||
|
return server_->io_services();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
|
|
|
@ -47,15 +47,21 @@ public:
|
||||||
const std::string &address, const std::string &port, bool asynchronous);
|
const std::string &address, const std::string &port, bool asynchronous);
|
||||||
void num_threads(size_t num_threads);
|
void num_threads(size_t num_threads);
|
||||||
void backlog(int backlog);
|
void backlog(int backlog);
|
||||||
|
void tls_handshake_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
void read_timeout(const boost::posix_time::time_duration &t);
|
||||||
bool handle(std::string pattern, request_cb cb);
|
bool handle(std::string pattern, request_cb cb);
|
||||||
void stop();
|
void stop();
|
||||||
void join();
|
void join();
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
io_services() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<server> server_;
|
std::unique_ptr<server> server_;
|
||||||
std::size_t num_threads_;
|
std::size_t num_threads_;
|
||||||
int backlog_;
|
int backlog_;
|
||||||
serve_mux mux_;
|
serve_mux mux_;
|
||||||
|
boost::posix_time::time_duration tls_handshake_timeout_;
|
||||||
|
boost::posix_time::time_duration read_timeout_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
|
@ -50,6 +50,10 @@ void request::on_data(data_cb cb) const {
|
||||||
|
|
||||||
request_impl &request::impl() const { return *impl_; }
|
request_impl &request::impl() const { return *impl_; }
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::endpoint &request::remote_endpoint() const {
|
||||||
|
return impl_->remote_endpoint();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -54,6 +54,14 @@ void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::endpoint &request_impl::remote_endpoint() const {
|
||||||
|
return remote_ep_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_impl::remote_endpoint(boost::asio::ip::tcp::endpoint ep) {
|
||||||
|
remote_ep_ = std::move(ep);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
} // namespace asio_http2
|
} // namespace asio_http2
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "nghttp2_config.h"
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
#include <nghttp2/asio_http2_server.h>
|
#include <nghttp2/asio_http2_server.h>
|
||||||
|
#include <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
namespace nghttp2 {
|
namespace nghttp2 {
|
||||||
namespace asio_http2 {
|
namespace asio_http2 {
|
||||||
|
@ -54,12 +55,16 @@ public:
|
||||||
void stream(class stream *s);
|
void stream(class stream *s);
|
||||||
void call_on_data(const uint8_t *data, std::size_t len);
|
void call_on_data(const uint8_t *data, std::size_t len);
|
||||||
|
|
||||||
|
const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
|
||||||
|
void remote_endpoint(boost::asio::ip::tcp::endpoint ep);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class stream *strm_;
|
class stream *strm_;
|
||||||
header_map header_;
|
header_map header_;
|
||||||
std::string method_;
|
std::string method_;
|
||||||
uri_ref uri_;
|
uri_ref uri_;
|
||||||
data_cb on_data_cb_;
|
data_cb on_data_cb_;
|
||||||
|
boost::asio::ip::tcp::endpoint remote_ep_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace server
|
} // namespace server
|
||||||
|
|
267
src/h2load.cc
267
src/h2load.cc
|
@ -44,6 +44,7 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <future>
|
#include <future>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
#include <spdylay/spdylay.h>
|
#include <spdylay/spdylay.h>
|
||||||
|
@ -96,15 +97,53 @@ bool Config::is_rate_mode() const { return (this->rate != 0); }
|
||||||
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
RequestStat::RequestStat() : data_offset(0), completed(false) {}
|
namespace {
|
||||||
|
constexpr size_t MAX_SAMPLES = 1000000;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Stats::Stats(size_t req_todo, size_t nclients)
|
Stats::Stats(size_t req_todo, size_t nclients)
|
||||||
: req_todo(0), req_started(0), req_done(0), req_success(0),
|
: req_todo(req_todo), req_started(0), req_done(0), req_success(0),
|
||||||
req_status_success(0), req_failed(0), req_error(0), req_timedout(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),
|
bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
|
||||||
status(), req_stats(req_todo), client_stats(nclients) {}
|
status() {}
|
||||||
|
|
||||||
Stream::Stream() : status_success(-1) {}
|
Stream::Stream() : req_stat{}, status_success(-1) {}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::random_device rd;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void sampling_init(Sampling &smp, size_t total, size_t max_samples) {
|
||||||
|
smp.n = 0;
|
||||||
|
|
||||||
|
if (total <= max_samples) {
|
||||||
|
smp.interval = 0.;
|
||||||
|
smp.point = 0.;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
smp.interval = static_cast<double>(total) / max_samples;
|
||||||
|
|
||||||
|
std::uniform_real_distribution<> dis(0., smp.interval);
|
||||||
|
|
||||||
|
smp.point = dis(gen);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool sampling_should_pick(Sampling &smp) {
|
||||||
|
return smp.interval == 0. || smp.n == ceil(smp.point);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void sampling_advance_point(Sampling &smp) { smp.point += smp.interval; }
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
@ -118,12 +157,14 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
rv = client->connect();
|
rv = client->connect();
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
client->fail();
|
client->fail();
|
||||||
|
delete client;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
client->fail();
|
client->fail();
|
||||||
|
delete client;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -134,6 +175,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
client->restart_timeout();
|
client->restart_timeout();
|
||||||
if (client->do_read() != 0) {
|
if (client->do_read() != 0) {
|
||||||
client->fail();
|
client->fail();
|
||||||
|
delete client;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writecb(loop, &client->wev, revents);
|
writecb(loop, &client->wev, revents);
|
||||||
|
@ -155,14 +197,18 @@ void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
++req_todo;
|
++req_todo;
|
||||||
--worker->nreqs_rem;
|
--worker->nreqs_rem;
|
||||||
}
|
}
|
||||||
worker->clients.push_back(
|
auto client =
|
||||||
make_unique<Client>(worker->next_client_id++, worker, req_todo));
|
make_unique<Client>(worker->next_client_id++, worker, req_todo);
|
||||||
auto &client = worker->clients.back();
|
|
||||||
|
++worker->nconns_made;
|
||||||
|
|
||||||
if (client->connect() != 0) {
|
if (client->connect() != 0) {
|
||||||
std::cerr << "client could not connect to host" << std::endl;
|
std::cerr << "client could not connect to host" << std::endl;
|
||||||
client->fail();
|
client->fail();
|
||||||
|
} else {
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
++worker->nconns_made;
|
worker->report_rate_progress();
|
||||||
}
|
}
|
||||||
if (worker->nconns_made >= worker->nclients) {
|
if (worker->nconns_made >= worker->nclients) {
|
||||||
ev_timer_stop(worker->loop, w);
|
ev_timer_stop(worker->loop, w);
|
||||||
|
@ -240,7 +286,7 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Client::Client(uint32_t id, 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),
|
: cstat{}, worker(worker), ssl(nullptr), next_addr(config.addrs),
|
||||||
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
|
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
|
||||||
req_started(0), req_done(0), id(id), fd(-1),
|
req_started(0), req_done(0), id(id), fd(-1),
|
||||||
new_connection_requested(false) {
|
new_connection_requested(false) {
|
||||||
|
@ -268,6 +314,12 @@ Client::~Client() {
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sampling_should_pick(worker->client_smp)) {
|
||||||
|
sampling_advance_point(worker->client_smp);
|
||||||
|
worker->sample_client_stat(&cstat);
|
||||||
|
}
|
||||||
|
++worker->client_smp.n;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::do_read() { return readfn(*this); }
|
int Client::do_read() { return readfn(*this); }
|
||||||
|
@ -420,9 +472,8 @@ void Client::disconnect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::submit_request() {
|
int Client::submit_request() {
|
||||||
auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
|
++worker->stats.req_started;
|
||||||
|
if (session->submit_request() != 0) {
|
||||||
if (session->submit_request(req_stat) != 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,15 +526,6 @@ void Client::process_request_failure() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::report_progress() {
|
|
||||||
if (!worker->config->is_rate_mode() && worker->id == 0 &&
|
|
||||||
worker->stats.req_done % worker->progress_interval == 0) {
|
|
||||||
std::cout << "progress: "
|
|
||||||
<< worker->stats.req_done * 100 / worker->stats.req_todo
|
|
||||||
<< "% done" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void print_server_tmp_key(SSL *ssl) {
|
void print_server_tmp_key(SSL *ssl) {
|
||||||
// libressl does not have SSL_get_server_tmp_key
|
// libressl does not have SSL_get_server_tmp_key
|
||||||
|
@ -607,28 +649,40 @@ void Client::on_status_code(int32_t stream_id, uint16_t status) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::on_stream_close(int32_t stream_id, bool success,
|
void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
|
||||||
RequestStat *req_stat, bool final) {
|
auto req_stat = get_req_stat(stream_id);
|
||||||
|
if (!req_stat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
req_stat->stream_close_time = std::chrono::steady_clock::now();
|
req_stat->stream_close_time = std::chrono::steady_clock::now();
|
||||||
if (success) {
|
if (success) {
|
||||||
req_stat->completed = true;
|
req_stat->completed = true;
|
||||||
++worker->stats.req_success;
|
++worker->stats.req_success;
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
++cstat.req_success;
|
++cstat.req_success;
|
||||||
}
|
|
||||||
++worker->stats.req_done;
|
|
||||||
++req_done;
|
|
||||||
if (success) {
|
|
||||||
if (streams[stream_id].status_success == 1) {
|
if (streams[stream_id].status_success == 1) {
|
||||||
++worker->stats.req_status_success;
|
++worker->stats.req_status_success;
|
||||||
} else {
|
} else {
|
||||||
++worker->stats.req_failed;
|
++worker->stats.req_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sampling_should_pick(worker->request_times_smp)) {
|
||||||
|
sampling_advance_point(worker->request_times_smp);
|
||||||
|
worker->sample_req_stat(req_stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count up in successful cases only
|
||||||
|
++worker->request_times_smp.n;
|
||||||
} else {
|
} else {
|
||||||
++worker->stats.req_failed;
|
++worker->stats.req_failed;
|
||||||
++worker->stats.req_error;
|
++worker->stats.req_error;
|
||||||
}
|
}
|
||||||
report_progress();
|
|
||||||
|
++worker->stats.req_done;
|
||||||
|
++req_done;
|
||||||
|
|
||||||
|
worker->report_progress();
|
||||||
streams.erase(stream_id);
|
streams.erase(stream_id);
|
||||||
if (req_done == req_todo) {
|
if (req_done == req_todo) {
|
||||||
terminate_session();
|
terminate_session();
|
||||||
|
@ -645,6 +699,15 @@ void Client::on_stream_close(int32_t stream_id, bool success,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RequestStat *Client::get_req_stat(int32_t stream_id) {
|
||||||
|
auto it = streams.find(stream_id);
|
||||||
|
if (it == std::end(streams)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &(*it).second.req_stat;
|
||||||
|
}
|
||||||
|
|
||||||
int Client::connection_made() {
|
int Client::connection_made() {
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
report_tls_info();
|
report_tls_info();
|
||||||
|
@ -750,7 +813,6 @@ int Client::connection_made() {
|
||||||
if (!config.timing_script) {
|
if (!config.timing_script) {
|
||||||
auto nreq =
|
auto nreq =
|
||||||
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
|
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
|
||||||
|
|
||||||
for (; nreq > 0; --nreq) {
|
for (; nreq > 0; --nreq) {
|
||||||
if (submit_request() != 0) {
|
if (submit_request() != 0) {
|
||||||
process_request_failure();
|
process_request_failure();
|
||||||
|
@ -985,17 +1047,14 @@ void Client::record_request_time(RequestStat *req_stat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::record_connect_start_time() {
|
void Client::record_connect_start_time() {
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
cstat.connect_start_time = std::chrono::steady_clock::now();
|
cstat.connect_start_time = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::record_connect_time() {
|
void Client::record_connect_time() {
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
cstat.connect_time = std::chrono::steady_clock::now();
|
cstat.connect_time = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::record_ttfb() {
|
void Client::record_ttfb() {
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
if (recorded(cstat.ttfb)) {
|
if (recorded(cstat.ttfb)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1004,16 +1063,12 @@ void Client::record_ttfb() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::clear_connect_times() {
|
void Client::clear_connect_times() {
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
|
|
||||||
cstat.connect_start_time = std::chrono::steady_clock::time_point();
|
cstat.connect_start_time = std::chrono::steady_clock::time_point();
|
||||||
cstat.connect_time = std::chrono::steady_clock::time_point();
|
cstat.connect_time = std::chrono::steady_clock::time_point();
|
||||||
cstat.ttfb = std::chrono::steady_clock::time_point();
|
cstat.ttfb = std::chrono::steady_clock::time_point();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::record_client_start_time() {
|
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
|
// Record start time only once at the very first connection is going
|
||||||
// to be made.
|
// to be made.
|
||||||
if (recorded(cstat.client_start_time)) {
|
if (recorded(cstat.client_start_time)) {
|
||||||
|
@ -1024,8 +1079,6 @@ void Client::record_client_start_time() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::record_client_end_time() {
|
void Client::record_client_end_time() {
|
||||||
auto &cstat = worker->stats.client_stats[id];
|
|
||||||
|
|
||||||
// Unlike client_start_time, we overwrite client_end_time. This
|
// Unlike client_start_time, we overwrite client_end_time. This
|
||||||
// handles multiple connect/disconnect for HTTP/1.1 benchmark.
|
// handles multiple connect/disconnect for HTTP/1.1 benchmark.
|
||||||
cstat.client_end_time = std::chrono::steady_clock::now();
|
cstat.client_end_time = std::chrono::steady_clock::now();
|
||||||
|
@ -1036,20 +1089,36 @@ void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||||
void Client::try_new_connection() { new_connection_requested = true; }
|
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,
|
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||||
size_t rate, Config *config)
|
size_t rate, size_t max_samples, Config *config)
|
||||||
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
|
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
|
||||||
config(config), id(id), tls_info_report_done(false),
|
config(config), id(id), tls_info_report_done(false),
|
||||||
app_info_report_done(false), nconns_made(0), nclients(nclients),
|
app_info_report_done(false), nconns_made(0), nclients(nclients),
|
||||||
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
|
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
|
||||||
rate(rate), next_client_id(0) {
|
rate(rate), max_samples(max_samples), next_client_id(0) {
|
||||||
stats.req_todo = req_todo;
|
if (!config->is_rate_mode()) {
|
||||||
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
||||||
|
} else {
|
||||||
|
progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
|
||||||
|
}
|
||||||
|
|
||||||
// create timer that will go off every rate_period
|
// create timer that will go off every rate_period
|
||||||
ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
|
ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
|
||||||
config->rate_period);
|
config->rate_period);
|
||||||
timeout_watcher.data = this;
|
timeout_watcher.data = this;
|
||||||
|
|
||||||
|
stats.req_stats.reserve(std::min(req_todo, max_samples));
|
||||||
|
stats.client_stats.reserve(std::min(nclients, max_samples));
|
||||||
|
|
||||||
|
sampling_init(request_times_smp, req_todo, max_samples);
|
||||||
|
sampling_init(client_smp, nclients, max_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
Worker::~Worker() {
|
||||||
|
ev_timer_stop(loop, &timeout_watcher);
|
||||||
|
ev_loop_destroy(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::run() {
|
||||||
if (!config->is_rate_mode()) {
|
if (!config->is_rate_mode()) {
|
||||||
for (size_t i = 0; i < nclients; ++i) {
|
for (size_t i = 0; i < nclients; ++i) {
|
||||||
auto req_todo = nreqs_per_client;
|
auto req_todo = nreqs_per_client;
|
||||||
|
@ -1057,26 +1126,12 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||||
++req_todo;
|
++req_todo;
|
||||||
--nreqs_rem;
|
--nreqs_rem;
|
||||||
}
|
}
|
||||||
clients.push_back(make_unique<Client>(next_client_id++, this, req_todo));
|
auto client = make_unique<Client>(next_client_id++, this, req_todo);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Worker::~Worker() {
|
|
||||||
ev_timer_stop(loop, &timeout_watcher);
|
|
||||||
|
|
||||||
// first clear clients so that io watchers are stopped before
|
|
||||||
// destructing ev_loop.
|
|
||||||
clients.clear();
|
|
||||||
ev_loop_destroy(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Worker::run() {
|
|
||||||
if (!config->is_rate_mode()) {
|
|
||||||
for (auto &client : clients) {
|
|
||||||
if (client->connect() != 0) {
|
if (client->connect() != 0) {
|
||||||
std::cerr << "client could not connect to host" << std::endl;
|
std::cerr << "client could not connect to host" << std::endl;
|
||||||
client->fail();
|
client->fail();
|
||||||
|
} else {
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1088,6 +1143,34 @@ void Worker::run() {
|
||||||
ev_run(loop, 0);
|
ev_run(loop, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Worker::sample_req_stat(RequestStat *req_stat) {
|
||||||
|
stats.req_stats.push_back(*req_stat);
|
||||||
|
assert(stats.req_stats.size() <= max_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::sample_client_stat(ClientStat *cstat) {
|
||||||
|
stats.client_stats.push_back(*cstat);
|
||||||
|
assert(stats.client_stats.size() <= max_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::report_progress() {
|
||||||
|
if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::report_rate_progress() {
|
||||||
|
if (id != 0 || nconns_made % progress_interval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "progress: " << nconns_made * 100 / nclients
|
||||||
|
<< "% of clients started" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Returns percentage of number of samples within mean +/- sd.
|
// Returns percentage of number of samples within mean +/- sd.
|
||||||
double within_sd(const std::vector<double> &samples, double mean, double sd) {
|
double within_sd(const std::vector<double> &samples, double mean, double sd) {
|
||||||
|
@ -1106,12 +1189,15 @@ double within_sd(const std::vector<double> &samples, double mean, double sd) {
|
||||||
namespace {
|
namespace {
|
||||||
// Computes statistics using |samples|. The min, max, mean, sd, and
|
// Computes statistics using |samples|. The min, max, mean, sd, and
|
||||||
// percentage of number of samples within mean +/- sd are computed.
|
// percentage of number of samples within mean +/- sd are computed.
|
||||||
SDStat compute_time_stat(const std::vector<double> &samples) {
|
// If |sampling| is true, this computes sample variance. Otherwise,
|
||||||
|
// population variance.
|
||||||
|
SDStat compute_time_stat(const std::vector<double> &samples,
|
||||||
|
bool sampling = false) {
|
||||||
if (samples.empty()) {
|
if (samples.empty()) {
|
||||||
return {0.0, 0.0, 0.0, 0.0, 0.0};
|
return {0.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
}
|
}
|
||||||
// standard deviation calculated using Rapid calculation method:
|
// standard deviation calculated using Rapid calculation method:
|
||||||
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
// https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||||
double a = 0, q = 0;
|
double a = 0, q = 0;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
|
@ -1130,7 +1216,7 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
|
||||||
|
|
||||||
assert(n > 0);
|
assert(n > 0);
|
||||||
res.mean = sum / n;
|
res.mean = sum / n;
|
||||||
res.sd = sqrt(q / n);
|
res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
|
||||||
res.within_sd = within_sd(samples, res.mean, res.sd);
|
res.within_sd = within_sd(samples, res.mean, res.sd);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -1140,18 +1226,29 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
|
||||||
namespace {
|
namespace {
|
||||||
SDStats
|
SDStats
|
||||||
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
||||||
|
auto request_times_sampling = false;
|
||||||
|
auto client_times_sampling = false;
|
||||||
size_t nrequest_times = 0;
|
size_t nrequest_times = 0;
|
||||||
|
size_t nclient_times = 0;
|
||||||
for (const auto &w : workers) {
|
for (const auto &w : workers) {
|
||||||
nrequest_times += w->stats.req_stats.size();
|
nrequest_times += w->stats.req_stats.size();
|
||||||
|
if (w->request_times_smp.interval != 0.) {
|
||||||
|
request_times_sampling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
nclient_times += w->stats.client_stats.size();
|
||||||
|
if (w->client_smp.interval != 0.) {
|
||||||
|
client_times_sampling = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<double> request_times;
|
std::vector<double> request_times;
|
||||||
request_times.reserve(nrequest_times);
|
request_times.reserve(nrequest_times);
|
||||||
|
|
||||||
std::vector<double> connect_times, ttfb_times, rps_values;
|
std::vector<double> connect_times, ttfb_times, rps_values;
|
||||||
connect_times.reserve(config.nclients);
|
connect_times.reserve(nclient_times);
|
||||||
ttfb_times.reserve(config.nclients);
|
ttfb_times.reserve(nclient_times);
|
||||||
rps_values.reserve(config.nclients);
|
rps_values.reserve(nclient_times);
|
||||||
|
|
||||||
for (const auto &w : workers) {
|
for (const auto &w : workers) {
|
||||||
for (const auto &req_stat : w->stats.req_stats) {
|
for (const auto &req_stat : w->stats.req_stats) {
|
||||||
|
@ -1195,8 +1292,10 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {compute_time_stat(request_times), compute_time_stat(connect_times),
|
return {compute_time_stat(request_times, request_times_sampling),
|
||||||
compute_time_stat(ttfb_times), compute_time_stat(rps_values)};
|
compute_time_stat(connect_times, client_times_sampling),
|
||||||
|
compute_time_stat(ttfb_times, client_times_sampling),
|
||||||
|
compute_time_stat(rps_values, client_times_sampling)};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -1374,7 +1473,7 @@ void read_script_from_file(std::istream &infile,
|
||||||
namespace {
|
namespace {
|
||||||
std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
|
std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
|
||||||
size_t nreqs, size_t nclients,
|
size_t nreqs, size_t nclients,
|
||||||
size_t rate) {
|
size_t rate, size_t max_samples) {
|
||||||
std::stringstream rate_report;
|
std::stringstream rate_report;
|
||||||
if (config.is_rate_mode() && nclients > rate) {
|
if (config.is_rate_mode() && nclients > rate) {
|
||||||
rate_report << "Up to " << rate << " client(s) will be created every "
|
rate_report << "Up to " << rate << " client(s) will be created every "
|
||||||
|
@ -1385,7 +1484,8 @@ std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
|
||||||
<< " total client(s). " << rate_report.str() << nreqs
|
<< " total client(s). " << rate_report.str() << nreqs
|
||||||
<< " total requests" << std::endl;
|
<< " total requests" << std::endl;
|
||||||
|
|
||||||
return make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate, &config);
|
return make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate, max_samples,
|
||||||
|
&config);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -1448,10 +1548,11 @@ Options:
|
||||||
URIs, if present, are ignored. Those in the first URI
|
URIs, if present, are ignored. Those in the first URI
|
||||||
are used solely. Definition of a base URI overrides all
|
are used solely. Definition of a base URI overrides all
|
||||||
scheme, host or port values.
|
scheme, host or port values.
|
||||||
-m, --max-concurrent-streams=(auto|<N>)
|
-m, --max-concurrent-streams=<N>
|
||||||
Max concurrent streams to issue per session. If "auto"
|
Max concurrent streams to issue per session. When
|
||||||
is given, the number of given URIs is used.
|
http/1.1 is used, this specifies the number of HTTP
|
||||||
Default: auto
|
pipelining requests in-flight.
|
||||||
|
Default: 1
|
||||||
-w, --window-bits=<N>
|
-w, --window-bits=<N>
|
||||||
Sets the stream level initial window size to (2**<N>)-1.
|
Sets the stream level initial window size to (2**<N>)-1.
|
||||||
For SPDY, 2**<N> is used instead.
|
For SPDY, 2**<N> is used instead.
|
||||||
|
@ -1626,11 +1727,7 @@ int main(int argc, char **argv) {
|
||||||
#endif // NOTHREADS
|
#endif // NOTHREADS
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
if (util::strieq("auto", optarg)) {
|
|
||||||
config.max_concurrent_streams = -1;
|
|
||||||
} else {
|
|
||||||
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'w':
|
case 'w':
|
||||||
case 'W': {
|
case 'W': {
|
||||||
|
@ -1853,11 +1950,6 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.max_concurrent_streams == -1) {
|
|
||||||
config.max_concurrent_streams = reqlines.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(config.max_concurrent_streams > 0);
|
|
||||||
if (config.nreqs == 0) {
|
if (config.nreqs == 0) {
|
||||||
std::cerr << "-n: the number of requests must be strictly greater than 0."
|
std::cerr << "-n: the number of requests must be strictly greater than 0."
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
|
@ -2099,6 +2191,9 @@ int main(int argc, char **argv) {
|
||||||
size_t rate_per_thread = config.rate / config.nthreads;
|
size_t rate_per_thread = config.rate / config.nthreads;
|
||||||
ssize_t rate_per_thread_rem = config.rate % config.nthreads;
|
ssize_t rate_per_thread_rem = config.rate % config.nthreads;
|
||||||
|
|
||||||
|
size_t max_samples_per_thread =
|
||||||
|
std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
|
||||||
|
|
||||||
std::mutex mu;
|
std::mutex mu;
|
||||||
std::condition_variable cv;
|
std::condition_variable cv;
|
||||||
auto ready = false;
|
auto ready = false;
|
||||||
|
@ -2131,7 +2226,8 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate));
|
workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
|
||||||
|
max_samples_per_thread));
|
||||||
auto &worker = workers.back();
|
auto &worker = workers.back();
|
||||||
futures.push_back(
|
futures.push_back(
|
||||||
std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
|
std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
|
||||||
|
@ -2161,7 +2257,8 @@ int main(int argc, char **argv) {
|
||||||
auto nreqs =
|
auto nreqs =
|
||||||
config.timing_script ? config.nreqs * config.nclients : config.nreqs;
|
config.timing_script ? config.nreqs * config.nclients : config.nreqs;
|
||||||
|
|
||||||
workers.push_back(create_worker(0, ssl_ctx, nreqs, nclients, rate));
|
workers.push_back(
|
||||||
|
create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
|
||||||
|
|
||||||
auto start = std::chrono::steady_clock::now();
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
@ -2253,7 +2350,7 @@ time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
|
||||||
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
|
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
|
||||||
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
|
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
|
||||||
<< util::dtos(ts.ttfb.within_sd) << "%"
|
<< util::dtos(ts.ttfb.within_sd) << "%"
|
||||||
<< "\nreq/s (client) : " << std::setw(10) << ts.rps.min << " "
|
<< "\nreq/s : " << std::setw(10) << ts.rps.min << " "
|
||||||
<< std::setw(10) << ts.rps.max << " " << std::setw(10)
|
<< std::setw(10) << ts.rps.max << " " << std::setw(10)
|
||||||
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
||||||
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
||||||
|
|
34
src/h2load.h
34
src/h2load.h
|
@ -112,7 +112,6 @@ struct Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RequestStat {
|
struct RequestStat {
|
||||||
RequestStat();
|
|
||||||
// time point when request was sent
|
// time point when request was sent
|
||||||
std::chrono::steady_clock::time_point request_time;
|
std::chrono::steady_clock::time_point request_time;
|
||||||
// time point when stream was closed
|
// time point when stream was closed
|
||||||
|
@ -208,9 +207,21 @@ enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
||||||
|
|
||||||
struct Client;
|
struct Client;
|
||||||
|
|
||||||
|
// We use systematic sampling method
|
||||||
|
struct Sampling {
|
||||||
|
// sampling interval
|
||||||
|
double interval;
|
||||||
|
// cumulative value of interval, and the next point is the integer
|
||||||
|
// rounded up from this value.
|
||||||
|
double point;
|
||||||
|
// number of samples seen, including discarded samples.
|
||||||
|
size_t n;
|
||||||
|
};
|
||||||
|
|
||||||
struct Worker {
|
struct Worker {
|
||||||
std::vector<std::unique_ptr<Client>> clients;
|
|
||||||
Stats stats;
|
Stats stats;
|
||||||
|
Sampling request_times_smp;
|
||||||
|
Sampling client_smp;
|
||||||
struct ev_loop *loop;
|
struct ev_loop *loop;
|
||||||
SSL_CTX *ssl_ctx;
|
SSL_CTX *ssl_ctx;
|
||||||
Config *config;
|
Config *config;
|
||||||
|
@ -226,24 +237,32 @@ struct Worker {
|
||||||
// at most nreqs_rem clients get an extra request
|
// at most nreqs_rem clients get an extra request
|
||||||
size_t nreqs_rem;
|
size_t nreqs_rem;
|
||||||
size_t rate;
|
size_t rate;
|
||||||
|
// maximum number of samples in this worker thread
|
||||||
|
size_t max_samples;
|
||||||
ev_timer timeout_watcher;
|
ev_timer timeout_watcher;
|
||||||
// The next client ID this worker assigns
|
// The next client ID this worker assigns
|
||||||
uint32_t next_client_id;
|
uint32_t next_client_id;
|
||||||
|
|
||||||
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
|
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
|
||||||
size_t rate, Config *config);
|
size_t rate, size_t max_samples, Config *config);
|
||||||
~Worker();
|
~Worker();
|
||||||
Worker(Worker &&o) = default;
|
Worker(Worker &&o) = default;
|
||||||
void run();
|
void run();
|
||||||
|
void sample_req_stat(RequestStat *req_stat);
|
||||||
|
void sample_client_stat(ClientStat *cstat);
|
||||||
|
void report_progress();
|
||||||
|
void report_rate_progress();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Stream {
|
struct Stream {
|
||||||
|
RequestStat req_stat;
|
||||||
int status_success;
|
int status_success;
|
||||||
Stream();
|
Stream();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Client {
|
struct Client {
|
||||||
std::unordered_map<int32_t, Stream> streams;
|
std::unordered_map<int32_t, Stream> streams;
|
||||||
|
ClientStat cstat;
|
||||||
std::unique_ptr<Session> session;
|
std::unique_ptr<Session> session;
|
||||||
ev_io wev;
|
ev_io wev;
|
||||||
ev_io rev;
|
ev_io rev;
|
||||||
|
@ -288,7 +307,6 @@ struct Client {
|
||||||
void process_request_failure();
|
void process_request_failure();
|
||||||
void process_timedout_streams();
|
void process_timedout_streams();
|
||||||
void process_abandoned_streams();
|
void process_abandoned_streams();
|
||||||
void report_progress();
|
|
||||||
void report_tls_info();
|
void report_tls_info();
|
||||||
void report_app_info();
|
void report_app_info();
|
||||||
void terminate_session();
|
void terminate_session();
|
||||||
|
@ -318,8 +336,12 @@ struct Client {
|
||||||
// |success| == true means that the request/response was exchanged
|
// |success| == true means that the request/response was exchanged
|
||||||
// |successfully, but it does not mean response carried successful
|
// |successfully, but it does not mean response carried successful
|
||||||
// |HTTP status code.
|
// |HTTP status code.
|
||||||
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat,
|
void on_stream_close(int32_t stream_id, bool success, bool final = false);
|
||||||
bool final = false);
|
// Returns RequestStat for |stream_id|. This function must be
|
||||||
|
// called after on_request(stream_id), and before
|
||||||
|
// on_stream_close(stream_id, ...). Otherwise, this will return
|
||||||
|
// nullptr.
|
||||||
|
RequestStat *get_req_stat(int32_t stream_id);
|
||||||
|
|
||||||
void record_request_time(RequestStat *req_stat);
|
void record_request_time(RequestStat *req_stat);
|
||||||
void record_connect_start_time();
|
void record_connect_start_time();
|
||||||
|
|
|
@ -80,9 +80,11 @@ int htp_msg_completecb(http_parser *htp) {
|
||||||
auto client = session->get_client();
|
auto client = session->get_client();
|
||||||
|
|
||||||
auto final = http_should_keep_alive(htp) == 0;
|
auto final = http_should_keep_alive(htp) == 0;
|
||||||
client->on_stream_close(session->stream_resp_counter_, true,
|
auto req_stat = client->get_req_stat(session->stream_resp_counter_);
|
||||||
session->req_stats_[session->stream_resp_counter_],
|
|
||||||
final);
|
assert(req_stat);
|
||||||
|
|
||||||
|
client->on_stream_close(session->stream_resp_counter_, true, final);
|
||||||
|
|
||||||
session->stream_resp_counter_ += 2;
|
session->stream_resp_counter_ += 2;
|
||||||
|
|
||||||
|
@ -150,7 +152,7 @@ http_parser_settings htp_hooks = {
|
||||||
|
|
||||||
void Http1Session::on_connect() { client_->signal_write(); }
|
void Http1Session::on_connect() { client_->signal_write(); }
|
||||||
|
|
||||||
int Http1Session::submit_request(RequestStat *req_stat) {
|
int Http1Session::submit_request() {
|
||||||
auto config = client_->worker->config;
|
auto config = client_->worker->config;
|
||||||
const auto &req = config->h1reqs[client_->reqidx];
|
const auto &req = config->h1reqs[client_->reqidx];
|
||||||
client_->reqidx++;
|
client_->reqidx++;
|
||||||
|
@ -159,13 +161,13 @@ int Http1Session::submit_request(RequestStat *req_stat) {
|
||||||
client_->reqidx = 0;
|
client_->reqidx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(req_stat);
|
client_->on_request(stream_req_counter_);
|
||||||
|
|
||||||
|
auto req_stat = client_->get_req_stat(stream_req_counter_);
|
||||||
|
|
||||||
client_->record_request_time(req_stat);
|
client_->record_request_time(req_stat);
|
||||||
client_->wb.write(req.c_str(), req.size());
|
client_->wb.write(req.c_str(), req.size());
|
||||||
|
|
||||||
client_->on_request(stream_req_counter_);
|
|
||||||
req_stats_[stream_req_counter_] = req_stat;
|
|
||||||
|
|
||||||
// increment for next request
|
// increment for next request
|
||||||
stream_req_counter_ += 2;
|
stream_req_counter_ += 2;
|
||||||
|
|
||||||
|
|
|
@ -38,14 +38,13 @@ public:
|
||||||
Http1Session(Client *client);
|
Http1Session(Client *client);
|
||||||
virtual ~Http1Session();
|
virtual ~Http1Session();
|
||||||
virtual void on_connect();
|
virtual void on_connect();
|
||||||
virtual int submit_request(RequestStat *req_stat);
|
virtual int submit_request();
|
||||||
virtual int on_read(const uint8_t *data, size_t len);
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual void terminate();
|
virtual void terminate();
|
||||||
Client *get_client();
|
Client *get_client();
|
||||||
int32_t stream_req_counter_;
|
int32_t stream_req_counter_;
|
||||||
int32_t stream_resp_counter_;
|
int32_t stream_resp_counter_;
|
||||||
std::unordered_map<int32_t, RequestStat *> req_stats_;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Client *client_;
|
Client *client_;
|
||||||
|
|
|
@ -89,12 +89,25 @@ namespace {
|
||||||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
uint32_t error_code, void *user_data) {
|
uint32_t error_code, void *user_data) {
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
auto req_stat = static_cast<RequestStat *>(
|
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
|
||||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
|
||||||
if (!req_stat) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR, req_stat);
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int on_frame_not_send_callback(nghttp2_session *session,
|
||||||
|
const nghttp2_frame *frame, int lib_error_code,
|
||||||
|
void *user_data) {
|
||||||
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
||||||
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto client = static_cast<Client *>(user_data);
|
||||||
|
// request was not sent. Mark it as error.
|
||||||
|
client->on_stream_close(frame->hd.stream_id, false);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -108,9 +121,7 @@ int before_frame_send_callback(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
|
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
client->on_request(frame->hd.stream_id);
|
auto req_stat = client->get_req_stat(frame->hd.stream_id);
|
||||||
auto req_stat = static_cast<RequestStat *>(
|
|
||||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
||||||
assert(req_stat);
|
assert(req_stat);
|
||||||
client->record_request_time(req_stat);
|
client->record_request_time(req_stat);
|
||||||
|
|
||||||
|
@ -124,8 +135,7 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
nghttp2_data_source *source, void *user_data) {
|
nghttp2_data_source *source, void *user_data) {
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
auto config = client->worker->config;
|
auto config = client->worker->config;
|
||||||
auto req_stat = static_cast<RequestStat *>(
|
auto req_stat = client->get_req_stat(stream_id);
|
||||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
|
||||||
assert(req_stat);
|
assert(req_stat);
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||||
|
@ -183,6 +193,9 @@ void Http2Session::on_connect() {
|
||||||
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
||||||
on_header_callback);
|
on_header_callback);
|
||||||
|
|
||||||
|
nghttp2_session_callbacks_set_on_frame_not_send_callback(
|
||||||
|
callbacks, on_frame_not_send_callback);
|
||||||
|
|
||||||
nghttp2_session_callbacks_set_before_frame_send_callback(
|
nghttp2_session_callbacks_set_before_frame_send_callback(
|
||||||
callbacks, before_frame_send_callback);
|
callbacks, before_frame_send_callback);
|
||||||
|
|
||||||
|
@ -212,7 +225,7 @@ void Http2Session::on_connect() {
|
||||||
client_->signal_write();
|
client_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Session::submit_request(RequestStat *req_stat) {
|
int Http2Session::submit_request() {
|
||||||
if (nghttp2_session_check_request_allowed(session_) == 0) {
|
if (nghttp2_session_check_request_allowed(session_) == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -228,11 +241,13 @@ int Http2Session::submit_request(RequestStat *req_stat) {
|
||||||
|
|
||||||
auto stream_id =
|
auto stream_id =
|
||||||
nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
|
nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
|
||||||
config->data_fd == -1 ? nullptr : &prd, req_stat);
|
config->data_fd == -1 ? nullptr : &prd, nullptr);
|
||||||
if (stream_id < 0) {
|
if (stream_id < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_->on_request(stream_id);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ public:
|
||||||
Http2Session(Client *client);
|
Http2Session(Client *client);
|
||||||
virtual ~Http2Session();
|
virtual ~Http2Session();
|
||||||
virtual void on_connect();
|
virtual void on_connect();
|
||||||
virtual int submit_request(RequestStat *req_stat);
|
virtual int submit_request();
|
||||||
virtual int on_read(const uint8_t *data, size_t len);
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual void terminate();
|
virtual void terminate();
|
||||||
|
|
|
@ -41,7 +41,7 @@ public:
|
||||||
// Called when the connection was made.
|
// Called when the connection was made.
|
||||||
virtual void on_connect() = 0;
|
virtual void on_connect() = 0;
|
||||||
// Called when one request must be issued.
|
// Called when one request must be issued.
|
||||||
virtual int submit_request(RequestStat *req_stat) = 0;
|
virtual int submit_request() = 0;
|
||||||
// Called when incoming bytes are available. The subclass has to
|
// Called when incoming bytes are available. The subclass has to
|
||||||
// return the number of bytes read.
|
// return the number of bytes read.
|
||||||
virtual int on_read(const uint8_t *data, size_t len) = 0;
|
virtual int on_read(const uint8_t *data, size_t len) = 0;
|
||||||
|
|
|
@ -48,9 +48,7 @@ void before_ctrl_send_callback(spdylay_session *session,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client->on_request(frame->syn_stream.stream_id);
|
client->on_request(frame->syn_stream.stream_id);
|
||||||
auto req_stat =
|
auto req_stat = client->get_req_stat(frame->syn_stream.stream_id);
|
||||||
static_cast<RequestStat *>(spdylay_session_get_stream_user_data(
|
|
||||||
session, frame->syn_stream.stream_id));
|
|
||||||
client->record_request_time(req_stat);
|
client->record_request_time(req_stat);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -104,9 +102,7 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
|
||||||
spdylay_status_code status_code,
|
spdylay_status_code status_code,
|
||||||
void *user_data) {
|
void *user_data) {
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
auto req_stat = static_cast<RequestStat *>(
|
client->on_stream_close(stream_id, status_code == SPDYLAY_OK);
|
||||||
spdylay_session_get_stream_user_data(session, stream_id));
|
|
||||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK, req_stat);
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -130,8 +126,7 @@ ssize_t file_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
spdylay_data_source *source, void *user_data) {
|
spdylay_data_source *source, void *user_data) {
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
auto config = client->worker->config;
|
auto config = client->worker->config;
|
||||||
auto req_stat = static_cast<RequestStat *>(
|
auto req_stat = client->get_req_stat(stream_id);
|
||||||
spdylay_session_get_stream_user_data(session, stream_id));
|
|
||||||
|
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||||
|
@ -185,7 +180,7 @@ void SpdySession::on_connect() {
|
||||||
client_->signal_write();
|
client_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdySession::submit_request(RequestStat *req_stat) {
|
int SpdySession::submit_request() {
|
||||||
int rv;
|
int rv;
|
||||||
auto config = client_->worker->config;
|
auto config = client_->worker->config;
|
||||||
auto &nv = config->nv[client_->reqidx++];
|
auto &nv = config->nv[client_->reqidx++];
|
||||||
|
@ -197,7 +192,7 @@ int SpdySession::submit_request(RequestStat *req_stat) {
|
||||||
spdylay_data_provider prd{{0}, file_read_callback};
|
spdylay_data_provider prd{{0}, file_read_callback};
|
||||||
|
|
||||||
rv = spdylay_submit_request(session_, 0, nv.data(),
|
rv = spdylay_submit_request(session_, 0, nv.data(),
|
||||||
config->data_fd == -1 ? nullptr : &prd, req_stat);
|
config->data_fd == -1 ? nullptr : &prd, nullptr);
|
||||||
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -40,7 +40,7 @@ public:
|
||||||
SpdySession(Client *client, uint16_t spdy_version);
|
SpdySession(Client *client, uint16_t spdy_version);
|
||||||
virtual ~SpdySession();
|
virtual ~SpdySession();
|
||||||
virtual void on_connect();
|
virtual void on_connect();
|
||||||
virtual int submit_request(RequestStat *req_stat);
|
virtual int submit_request();
|
||||||
virtual int on_read(const uint8_t *data, size_t len);
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual void terminate();
|
virtual void terminate();
|
||||||
|
|
|
@ -113,6 +113,8 @@ std::string get_status_string(unsigned int status_code) {
|
||||||
return "429 Too Many Requests";
|
return "429 Too Many Requests";
|
||||||
case 431:
|
case 431:
|
||||||
return "431 Request Header Fields Too Large";
|
return "431 Request Header Fields Too Large";
|
||||||
|
case 451:
|
||||||
|
return "451 Unavailable For Legal Reasons";
|
||||||
case 500:
|
case 500:
|
||||||
return "500 Internal Server Error";
|
return "500 Internal Server Error";
|
||||||
case 501:
|
case 501:
|
||||||
|
@ -215,6 +217,8 @@ const char *stringify_status(unsigned int status_code) {
|
||||||
return "429";
|
return "429";
|
||||||
case 431:
|
case 431:
|
||||||
return "431";
|
return "431";
|
||||||
|
case 451:
|
||||||
|
return "451";
|
||||||
case 500:
|
case 500:
|
||||||
return "500";
|
return "500";
|
||||||
case 501:
|
case 501:
|
||||||
|
|
|
@ -144,6 +144,12 @@ public:
|
||||||
// and session is terminated.
|
// and session is terminated.
|
||||||
void on_error(error_cb cb) const;
|
void on_error(error_cb cb) const;
|
||||||
|
|
||||||
|
// Sets connect timeout, which defaults to 60 seconds.
|
||||||
|
void connect_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
|
||||||
|
// Sets read timeout, which defaults to 60 seconds.
|
||||||
|
void read_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
|
||||||
// Shutdowns connection.
|
// Shutdowns connection.
|
||||||
void shutdown() const;
|
void shutdown() const;
|
||||||
|
|
||||||
|
@ -177,7 +183,7 @@ public:
|
||||||
generator_cb cb, header_map h = header_map{}) const;
|
generator_cb cb, header_map h = header_map{}) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<session_impl> impl_;
|
std::shared_ptr<session_impl> impl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// configure |tls_ctx| for client use. Currently, we just set NPN
|
// configure |tls_ctx| for client use. Currently, we just set NPN
|
||||||
|
|
|
@ -59,6 +59,9 @@ public:
|
||||||
// Application must not call this directly.
|
// Application must not call this directly.
|
||||||
request_impl &impl() const;
|
request_impl &impl() const;
|
||||||
|
|
||||||
|
// Returns the remote endpoint of the request
|
||||||
|
const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<request_impl> impl_;
|
std::unique_ptr<request_impl> impl_;
|
||||||
};
|
};
|
||||||
|
@ -195,12 +198,22 @@ public:
|
||||||
// connections.
|
// connections.
|
||||||
void backlog(int backlog);
|
void backlog(int backlog);
|
||||||
|
|
||||||
|
// Sets TLS handshake timeout, which defaults to 60 seconds.
|
||||||
|
void tls_handshake_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
|
||||||
|
// Sets read timeout, which defaults to 60 seconds.
|
||||||
|
void read_timeout(const boost::posix_time::time_duration &t);
|
||||||
|
|
||||||
// Gracefully stop http2 server
|
// Gracefully stop http2 server
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
// Join on http2 server and wait for it to fully stop
|
// Join on http2 server and wait for it to fully stop
|
||||||
void join();
|
void join();
|
||||||
|
|
||||||
|
// Get access to the io_service objects.
|
||||||
|
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||||
|
io_services() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<http2_impl> impl_;
|
std::unique_ptr<http2_impl> impl_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -164,6 +164,8 @@ Options:
|
||||||
Path to file that contains MIME media types and the
|
Path to file that contains MIME media types and the
|
||||||
extensions that represent them.
|
extensions that represent them.
|
||||||
Default: )" << config.mime_types_file << R"(
|
Default: )" << config.mime_types_file << R"(
|
||||||
|
--no-content-length
|
||||||
|
Don't send content-length header field.
|
||||||
--version Display version information and exit.
|
--version Display version information and exit.
|
||||||
-h, --help Display this help and exit.
|
-h, --help Display this help and exit.
|
||||||
|
|
||||||
|
@ -209,6 +211,7 @@ int main(int argc, char **argv) {
|
||||||
{"hexdump", no_argument, &flag, 7},
|
{"hexdump", no_argument, &flag, 7},
|
||||||
{"echo-upload", no_argument, &flag, 8},
|
{"echo-upload", no_argument, &flag, 8},
|
||||||
{"mime-types-file", required_argument, &flag, 9},
|
{"mime-types-file", required_argument, &flag, 9},
|
||||||
|
{"no-content-length", no_argument, &flag, 10},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
|
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
|
||||||
|
@ -340,6 +343,10 @@ int main(int argc, char **argv) {
|
||||||
mime_types_file_set_manually = true;
|
mime_types_file_set_manually = true;
|
||||||
config.mime_types_file = optarg;
|
config.mime_types_file = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 10:
|
||||||
|
// no-content-length option
|
||||||
|
config.no_content_length = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1610,14 +1610,15 @@ HTTP:
|
||||||
used several times to specify multiple header fields.
|
used several times to specify multiple header fields.
|
||||||
Example: --add-response-header="foo: bar"
|
Example: --add-response-header="foo: bar"
|
||||||
--header-field-buffer=<SIZE>
|
--header-field-buffer=<SIZE>
|
||||||
Set maximum buffer size for incoming HTTP header field
|
Set maximum buffer size for incoming HTTP request header
|
||||||
list. This is the sum of header name and value in
|
field list. This is the sum of header name and value in
|
||||||
bytes.
|
bytes.
|
||||||
Default: )"
|
Default: )"
|
||||||
<< util::utos_with_unit(get_config()->header_field_buffer) << R"(
|
<< util::utos_with_unit(get_config()->header_field_buffer) << R"(
|
||||||
--max-header-fields=<N>
|
--max-header-fields=<N>
|
||||||
Set maximum number of incoming HTTP header fields, which
|
Set maximum number of incoming HTTP request header
|
||||||
appear in one request or response header field list.
|
fields, which appear in one request or response header
|
||||||
|
field list.
|
||||||
Default: )" << get_config()->max_header_fields << R"(
|
Default: )" << get_config()->max_header_fields << R"(
|
||||||
|
|
||||||
Debug:
|
Debug:
|
||||||
|
|
|
@ -44,4 +44,8 @@
|
||||||
|
|
||||||
#define DIE() _Exit(EXIT_FAILURE)
|
#define DIE() _Exit(EXIT_FAILURE)
|
||||||
|
|
||||||
|
#if defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
|
||||||
|
inline int initgroups(const char *user, gid_t group) { return 0; }
|
||||||
|
#endif // defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
|
||||||
|
|
||||||
#endif // SHRPX_H
|
#endif // SHRPX_H
|
||||||
|
|
|
@ -718,6 +718,34 @@ void ClientHandler::direct_http2_upgrade() {
|
||||||
|
|
||||||
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
||||||
auto upstream = make_unique<Http2Upstream>(this);
|
auto upstream = make_unique<Http2Upstream>(this);
|
||||||
|
|
||||||
|
auto output = upstream->get_response_buf();
|
||||||
|
|
||||||
|
// We might have written non-final header in response_buf, in this
|
||||||
|
// case, response_state is still INITIAL. If this non-final header
|
||||||
|
// and upgrade header fit in output buffer, do upgrade. Otherwise,
|
||||||
|
// to avoid to send this non-final header as response body in HTTP/2
|
||||||
|
// upstream, fail upgrade.
|
||||||
|
auto downstream = http->get_downstream();
|
||||||
|
auto input = downstream->get_response_buf();
|
||||||
|
|
||||||
|
static constexpr char res[] =
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
"Connection: Upgrade\r\n"
|
||||||
|
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
auto required_size = str_size(res) + input->rleft();
|
||||||
|
|
||||||
|
if (output->wleft() < required_size) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, this)
|
||||||
|
<< "HTTP Upgrade failed because of insufficient buffer space: need "
|
||||||
|
<< required_size << ", available " << output->wleft();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (upstream->upgrade_upstream(http) != 0) {
|
if (upstream->upgrade_upstream(http) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -729,11 +757,11 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
||||||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||||
write_ = &ClientHandler::write_clear;
|
write_ = &ClientHandler::write_clear;
|
||||||
|
|
||||||
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
|
auto nread =
|
||||||
"Connection: Upgrade\r\n"
|
downstream->get_response_buf()->remove(output->last, output->wleft());
|
||||||
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
output->write(nread);
|
||||||
"\r\n";
|
|
||||||
upstream->get_response_buf()->write(res, sizeof(res) - 1);
|
output->write(res, str_size(res));
|
||||||
upstream_ = std::move(upstream);
|
upstream_ = std::move(upstream);
|
||||||
|
|
||||||
signal_write();
|
signal_write();
|
||||||
|
|
|
@ -276,11 +276,9 @@ std::string read_passwd_from_file(const char *filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::string, std::string> parse_header(const char *optarg) {
|
std::pair<std::string, std::string> parse_header(const char *optarg) {
|
||||||
// We skip possible ":" at the start of optarg.
|
const auto *colon = strchr(optarg, ':');
|
||||||
const auto *colon = strchr(optarg + 1, ':');
|
|
||||||
|
|
||||||
// name = ":" is not allowed
|
if (colon == nullptr || colon == optarg) {
|
||||||
if (colon == nullptr || (optarg[0] == ':' && colon == optarg + 1)) {
|
|
||||||
return {"", ""};
|
return {"", ""};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +289,14 @@ std::pair<std::string, std::string> parse_header(const char *optarg) {
|
||||||
auto p = std::make_pair(std::string(optarg, colon),
|
auto p = std::make_pair(std::string(optarg, colon),
|
||||||
std::string(value, strlen(value)));
|
std::string(value, strlen(value)));
|
||||||
util::inp_strlower(p.first);
|
util::inp_strlower(p.first);
|
||||||
util::inp_strlower(p.second);
|
|
||||||
|
if (!nghttp2_check_header_name(
|
||||||
|
reinterpret_cast<const uint8_t *>(p.first.c_str()), p.first.size()) ||
|
||||||
|
!nghttp2_check_header_value(
|
||||||
|
reinterpret_cast<const uint8_t *>(p.second.c_str()),
|
||||||
|
p.second.size())) {
|
||||||
|
return {"", ""};
|
||||||
|
}
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -1800,7 +1805,7 @@ int parse_config(const char *opt, const char *optarg,
|
||||||
case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
|
case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
|
||||||
auto p = parse_header(optarg);
|
auto p = parse_header(optarg);
|
||||||
if (p.first.empty()) {
|
if (p.first.empty()) {
|
||||||
LOG(ERROR) << opt << ": header field name is empty: " << optarg;
|
LOG(ERROR) << opt << ": invalid header field: " << optarg;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
|
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
|
||||||
|
|
|
@ -46,8 +46,7 @@ void test_shrpx_config_parse_header(void) {
|
||||||
CU_ASSERT("b" == p.second);
|
CU_ASSERT("b" == p.second);
|
||||||
|
|
||||||
p = parse_header(":a: b");
|
p = parse_header(":a: b");
|
||||||
CU_ASSERT(":a" == p.first);
|
CU_ASSERT(p.first.empty());
|
||||||
CU_ASSERT("b" == p.second);
|
|
||||||
|
|
||||||
p = parse_header("a: :b");
|
p = parse_header("a: :b");
|
||||||
CU_ASSERT("a" == p.first);
|
CU_ASSERT("a" == p.first);
|
||||||
|
@ -59,6 +58,12 @@ void test_shrpx_config_parse_header(void) {
|
||||||
p = parse_header("alpha: bravo charlie");
|
p = parse_header("alpha: bravo charlie");
|
||||||
CU_ASSERT("alpha" == p.first);
|
CU_ASSERT("alpha" == p.first);
|
||||||
CU_ASSERT("bravo charlie" == p.second);
|
CU_ASSERT("bravo charlie" == p.second);
|
||||||
|
|
||||||
|
p = parse_header("a,: b");
|
||||||
|
CU_ASSERT(p.first.empty());
|
||||||
|
|
||||||
|
p = parse_header("a: b\x0a");
|
||||||
|
CU_ASSERT(p.first.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_shrpx_config_parse_log_format(void) {
|
void test_shrpx_config_parse_log_format(void) {
|
||||||
|
|
|
@ -757,26 +757,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||||
!downstream->get_expect_final_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 (trailer) {
|
|
||||||
// we don't care trailer part exceeds header size limit; just
|
|
||||||
// discard it.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trailer) {
|
if (trailer) {
|
||||||
// just store header fields for trailer part
|
// just store header fields for trailer part
|
||||||
downstream->add_response_trailer(name, namelen, value, valuelen,
|
downstream->add_response_trailer(name, namelen, value, valuelen,
|
||||||
|
@ -803,21 +783,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
|
|
||||||
assert(promised_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);
|
auto token = http2::lookup_token(name, namelen);
|
||||||
promised_downstream->add_request_header(name, namelen, value, valuelen,
|
promised_downstream->add_request_header(name, namelen, value, valuelen,
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
||||||
|
|
|
@ -169,7 +169,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
|
|
||||||
if (downstream->get_request_headers_sum() + namelen + valuelen >
|
if (downstream->get_request_headers_sum() + namelen + valuelen >
|
||||||
get_config()->header_field_buffer ||
|
get_config()->header_field_buffer ||
|
||||||
downstream->get_request_headers().size() >=
|
downstream->get_request_headers().size() +
|
||||||
|
downstream->get_request_trailers().size() >=
|
||||||
get_config()->max_header_fields) {
|
get_config()->max_header_fields) {
|
||||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -562,29 +562,10 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
namespace {
|
namespace {
|
||||||
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
auto downstream = static_cast<Downstream *>(htp->data);
|
auto downstream = static_cast<Downstream *>(htp->data);
|
||||||
|
|
||||||
if (downstream->get_response_headers_sum() + len >
|
|
||||||
get_config()->header_field_buffer) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream) << "Too large header block size="
|
|
||||||
<< downstream->get_response_headers_sum() + len;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||||
if (downstream->get_response_header_key_prev()) {
|
if (downstream->get_response_header_key_prev()) {
|
||||||
downstream->append_last_response_header_key(data, len);
|
downstream->append_last_response_header_key(data, len);
|
||||||
} else {
|
} else {
|
||||||
if (downstream->get_response_headers().size() >=
|
|
||||||
get_config()->max_header_fields) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream)
|
|
||||||
<< "Too many header field num="
|
|
||||||
<< downstream->get_response_headers().size() + 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
downstream->add_response_header(std::string(data, len), "");
|
downstream->add_response_header(std::string(data, len), "");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -592,15 +573,6 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
if (downstream->get_response_trailer_key_prev()) {
|
if (downstream->get_response_trailer_key_prev()) {
|
||||||
downstream->append_last_response_trailer_key(data, len);
|
downstream->append_last_response_trailer_key(data, len);
|
||||||
} else {
|
} else {
|
||||||
if (downstream->get_response_headers().size() >=
|
|
||||||
get_config()->max_header_fields) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream)
|
|
||||||
<< "Too many header field num="
|
|
||||||
<< downstream->get_response_headers().size() + 1;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
downstream->add_response_trailer(std::string(data, len), "");
|
downstream->add_response_trailer(std::string(data, len), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -611,14 +583,6 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
namespace {
|
namespace {
|
||||||
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||||
auto downstream = static_cast<Downstream *>(htp->data);
|
auto downstream = static_cast<Downstream *>(htp->data);
|
||||||
if (downstream->get_response_headers_sum() + len >
|
|
||||||
get_config()->header_field_buffer) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream) << "Too large header block size="
|
|
||||||
<< downstream->get_response_headers_sum() + len;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||||
if (downstream->get_response_header_key_prev()) {
|
if (downstream->get_response_header_key_prev()) {
|
||||||
downstream->set_last_response_header_value(data, len);
|
downstream->set_last_response_header_value(data, len);
|
||||||
|
|
|
@ -146,7 +146,8 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||||
if (downstream->get_request_trailer_key_prev()) {
|
if (downstream->get_request_trailer_key_prev()) {
|
||||||
downstream->append_last_request_trailer_key(data, len);
|
downstream->append_last_request_trailer_key(data, len);
|
||||||
} else {
|
} else {
|
||||||
if (downstream->get_request_headers().size() >=
|
if (downstream->get_request_headers().size() +
|
||||||
|
downstream->get_request_trailers().size() >=
|
||||||
get_config()->max_header_fields) {
|
get_config()->max_header_fields) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, upstream) << "Too many header field num="
|
ULOG(INFO, upstream) << "Too many header field num="
|
||||||
|
@ -409,11 +410,6 @@ int htp_msg_completecb(http_parser *htp) {
|
||||||
|
|
||||||
if (handler->get_http2_upgrade_allowed() &&
|
if (handler->get_http2_upgrade_allowed() &&
|
||||||
downstream->get_http2_upgrade_request() &&
|
downstream->get_http2_upgrade_request() &&
|
||||||
// we may write non-final header in response_buf, in this case,
|
|
||||||
// response_state is still INITIAL. So don't upgrade in this
|
|
||||||
// case, otherwise we end up send this non-final header as
|
|
||||||
// response body in HTTP/2 upstream.
|
|
||||||
downstream->get_response_buf()->rleft() == 0 &&
|
|
||||||
handler->perform_http2_upgrade(upstream) != 0) {
|
handler->perform_http2_upgrade(upstream) != 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";
|
ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";
|
||||||
|
|
|
@ -169,6 +169,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||||
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
|
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spdy does not define usage of trailer fields, and we ignores
|
||||||
|
// them.
|
||||||
if (header_buffer > get_config()->header_field_buffer ||
|
if (header_buffer > get_config()->header_field_buffer ||
|
||||||
num_headers > get_config()->max_header_fields) {
|
num_headers > get_config()->max_header_fields) {
|
||||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/dh.h>
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
|
13
src/ssl.cc
13
src/ssl.cc
|
@ -80,12 +80,7 @@ LibsslGlobalLock::LibsslGlobalLock() {
|
||||||
LibsslGlobalLock::~LibsslGlobalLock() { ssl_global_locks.clear(); }
|
LibsslGlobalLock::~LibsslGlobalLock() { ssl_global_locks.clear(); }
|
||||||
|
|
||||||
const char *get_tls_protocol(SSL *ssl) {
|
const char *get_tls_protocol(SSL *ssl) {
|
||||||
auto session = SSL_get_session(ssl);
|
switch (SSL_version(ssl)) {
|
||||||
if (!session) {
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (session->ssl_version) {
|
|
||||||
case SSL2_VERSION:
|
case SSL2_VERSION:
|
||||||
return "SSLv2";
|
return "SSLv2";
|
||||||
case SSL3_VERSION:
|
case SSL3_VERSION:
|
||||||
|
@ -113,10 +108,12 @@ TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) {
|
||||||
|
|
||||||
tls_info->cipher = SSL_get_cipher_name(ssl);
|
tls_info->cipher = SSL_get_cipher_name(ssl);
|
||||||
tls_info->protocol = get_tls_protocol(ssl);
|
tls_info->protocol = get_tls_protocol(ssl);
|
||||||
tls_info->session_id = session->session_id;
|
|
||||||
tls_info->session_id_length = session->session_id_length;
|
|
||||||
tls_info->session_reused = SSL_session_reused(ssl);
|
tls_info->session_reused = SSL_session_reused(ssl);
|
||||||
|
|
||||||
|
unsigned int session_id_length;
|
||||||
|
tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length);
|
||||||
|
tls_info->session_id_length = session_id_length;
|
||||||
|
|
||||||
return tls_info;
|
return tls_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
tests/main.c
10
tests/main.c
|
@ -89,6 +89,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||||
test_nghttp2_session_recv_headers_with_priority) ||
|
test_nghttp2_session_recv_headers_with_priority) ||
|
||||||
!CU_add_test(pSuite, "session_recv_headers_early_response",
|
!CU_add_test(pSuite, "session_recv_headers_early_response",
|
||||||
test_nghttp2_session_recv_headers_early_response) ||
|
test_nghttp2_session_recv_headers_early_response) ||
|
||||||
|
!CU_add_test(pSuite, "session_server_recv_push_response",
|
||||||
|
test_nghttp2_session_server_recv_push_response) ||
|
||||||
!CU_add_test(pSuite, "session_recv_premature_headers",
|
!CU_add_test(pSuite, "session_recv_premature_headers",
|
||||||
test_nghttp2_session_recv_premature_headers) ||
|
test_nghttp2_session_recv_premature_headers) ||
|
||||||
!CU_add_test(pSuite, "session_recv_unknown_frame",
|
!CU_add_test(pSuite, "session_recv_unknown_frame",
|
||||||
|
@ -128,6 +130,8 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||||
test_nghttp2_session_on_window_update_received) ||
|
test_nghttp2_session_on_window_update_received) ||
|
||||||
!CU_add_test(pSuite, "session_on_data_received",
|
!CU_add_test(pSuite, "session_on_data_received",
|
||||||
test_nghttp2_session_on_data_received) ||
|
test_nghttp2_session_on_data_received) ||
|
||||||
|
!CU_add_test(pSuite, "session_on_data_received_fail_fast",
|
||||||
|
test_nghttp2_session_on_data_received_fail_fast) ||
|
||||||
!CU_add_test(pSuite, "session_send_headers_start_stream",
|
!CU_add_test(pSuite, "session_send_headers_start_stream",
|
||||||
test_nghttp2_session_send_headers_start_stream) ||
|
test_nghttp2_session_send_headers_start_stream) ||
|
||||||
!CU_add_test(pSuite, "session_send_headers_reply",
|
!CU_add_test(pSuite, "session_send_headers_reply",
|
||||||
|
@ -205,8 +209,6 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||||
test_nghttp2_session_reply_fail) ||
|
test_nghttp2_session_reply_fail) ||
|
||||||
!CU_add_test(pSuite, "session_max_concurrent_streams",
|
!CU_add_test(pSuite, "session_max_concurrent_streams",
|
||||||
test_nghttp2_session_max_concurrent_streams) ||
|
test_nghttp2_session_max_concurrent_streams) ||
|
||||||
!CU_add_test(pSuite, "session_stream_close_on_headers_push",
|
|
||||||
test_nghttp2_session_stream_close_on_headers_push) ||
|
|
||||||
!CU_add_test(pSuite, "session_stop_data_with_rst_stream",
|
!CU_add_test(pSuite, "session_stop_data_with_rst_stream",
|
||||||
test_nghttp2_session_stop_data_with_rst_stream) ||
|
test_nghttp2_session_stop_data_with_rst_stream) ||
|
||||||
!CU_add_test(pSuite, "session_defer_data",
|
!CU_add_test(pSuite, "session_defer_data",
|
||||||
|
@ -291,6 +293,10 @@ int main(int argc _U_, char *argv[] _U_) {
|
||||||
!CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
|
!CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
|
||||||
!CU_add_test(pSuite, "session_change_stream_priority",
|
!CU_add_test(pSuite, "session_change_stream_priority",
|
||||||
test_nghttp2_session_change_stream_priority) ||
|
test_nghttp2_session_change_stream_priority) ||
|
||||||
|
!CU_add_test(pSuite, "session_repeated_priority_change",
|
||||||
|
test_nghttp2_session_repeated_priority_change) ||
|
||||||
|
!CU_add_test(pSuite, "session_repeated_priority_submission",
|
||||||
|
test_nghttp2_session_repeated_priority_submission) ||
|
||||||
!CU_add_test(pSuite, "http_mandatory_headers",
|
!CU_add_test(pSuite, "http_mandatory_headers",
|
||||||
test_nghttp2_http_mandatory_headers) ||
|
test_nghttp2_http_mandatory_headers) ||
|
||||||
!CU_add_test(pSuite, "http_content_length",
|
!CU_add_test(pSuite, "http_content_length",
|
||||||
|
|
|
@ -342,16 +342,6 @@ static int send_data_callback(nghttp2_session *session _U_,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* static void no_stream_user_data_stream_close_callback */
|
|
||||||
/* (nghttp2_session *session, */
|
|
||||||
/* int32_t stream_id, */
|
|
||||||
/* nghttp2_error_code error_code, */
|
|
||||||
/* void *user_data) */
|
|
||||||
/* { */
|
|
||||||
/* my_user_data* my_data = (my_user_data*)user_data; */
|
|
||||||
/* ++my_data->stream_close_cb_called; */
|
|
||||||
/* } */
|
|
||||||
|
|
||||||
static ssize_t block_count_send_callback(nghttp2_session *session _U_,
|
static ssize_t block_count_send_callback(nghttp2_session *session _U_,
|
||||||
const uint8_t *data _U_, size_t len,
|
const uint8_t *data _U_, size_t len,
|
||||||
int flags _U_, void *user_data) {
|
int flags _U_, void *user_data) {
|
||||||
|
@ -1487,6 +1477,58 @@ void test_nghttp2_session_recv_headers_early_response(void) {
|
||||||
nghttp2_bufs_free(&bufs);
|
nghttp2_bufs_free(&bufs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_nghttp2_session_server_recv_push_response(void) {
|
||||||
|
nghttp2_session *session;
|
||||||
|
nghttp2_session_callbacks callbacks;
|
||||||
|
nghttp2_bufs bufs;
|
||||||
|
nghttp2_buf *buf;
|
||||||
|
ssize_t rv;
|
||||||
|
my_user_data ud;
|
||||||
|
nghttp2_mem *mem;
|
||||||
|
nghttp2_frame frame;
|
||||||
|
nghttp2_hd_deflater deflater;
|
||||||
|
nghttp2_nv *nva;
|
||||||
|
size_t nvlen;
|
||||||
|
|
||||||
|
mem = nghttp2_mem_default();
|
||||||
|
frame_pack_bufs_init(&bufs);
|
||||||
|
|
||||||
|
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||||
|
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
|
||||||
|
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, &ud);
|
||||||
|
|
||||||
|
nghttp2_hd_deflate_init(&deflater, mem);
|
||||||
|
|
||||||
|
nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
|
||||||
|
&pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL);
|
||||||
|
|
||||||
|
nvlen = ARRLEN(resnv);
|
||||||
|
nghttp2_nv_array_copy(&nva, resnv, nvlen, mem);
|
||||||
|
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
|
||||||
|
NGHTTP2_HCAT_HEADERS, &pri_spec_default, nva,
|
||||||
|
nvlen);
|
||||||
|
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
|
||||||
|
|
||||||
|
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||||
|
|
||||||
|
buf = &bufs.head->buf;
|
||||||
|
|
||||||
|
ud.invalid_frame_recv_cb_called = 0;
|
||||||
|
|
||||||
|
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
|
||||||
|
|
||||||
|
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
|
||||||
|
CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
|
||||||
|
|
||||||
|
nghttp2_bufs_free(&bufs);
|
||||||
|
nghttp2_hd_deflate_free(&deflater);
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
}
|
||||||
|
|
||||||
void test_nghttp2_session_recv_premature_headers(void) {
|
void test_nghttp2_session_recv_premature_headers(void) {
|
||||||
nghttp2_session *session;
|
nghttp2_session *session;
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
|
@ -2329,6 +2371,25 @@ void test_nghttp2_session_on_request_headers_received(void) {
|
||||||
nghttp2_frame_headers_free(&frame.headers, mem);
|
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, &user_data);
|
||||||
|
|
||||||
|
/* HEADERS to closed stream */
|
||||||
|
stream = open_stream(session, 1);
|
||||||
|
session->last_recv_stream_id = 1;
|
||||||
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
|
nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
|
||||||
|
|
||||||
|
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
|
||||||
|
NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
|
||||||
|
nghttp2_session_on_request_headers_received(session, &frame));
|
||||||
|
CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
|
||||||
|
|
||||||
|
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_nghttp2_session_on_response_headers_received(void) {
|
void test_nghttp2_session_on_response_headers_received(void) {
|
||||||
|
@ -2822,14 +2883,20 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||||
CU_ASSERT(1 == session->num_incoming_reserved_streams);
|
CU_ASSERT(1 == session->num_incoming_reserved_streams);
|
||||||
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
|
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
|
||||||
item = nghttp2_session_get_next_ob_item(session);
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||||
CU_ASSERT(4 == item->frame.hd.stream_id);
|
CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code);
|
||||||
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.rst_stream.error_code);
|
|
||||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
CU_ASSERT(4 == session->last_recv_stream_id);
|
CU_ASSERT(4 == session->last_recv_stream_id);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, &user_data);
|
||||||
|
|
||||||
|
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
|
||||||
|
&pri_spec_default,
|
||||||
|
NGHTTP2_STREAM_OPENING, NULL);
|
||||||
|
|
||||||
/* Attempt to PUSH_PROMISE against stream in closing state */
|
/* Attempt to PUSH_PROMISE against stream in closing state */
|
||||||
stream->shut_flags = NGHTTP2_SHUT_NONE;
|
|
||||||
stream->state = NGHTTP2_STREAM_CLOSING;
|
stream->state = NGHTTP2_STREAM_CLOSING;
|
||||||
frame.push_promise.promised_stream_id = 6;
|
frame.push_promise.promised_stream_id = 6;
|
||||||
|
|
||||||
|
@ -2839,7 +2906,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||||
nghttp2_session_on_push_promise_received(session, &frame));
|
nghttp2_session_on_push_promise_received(session, &frame));
|
||||||
|
|
||||||
CU_ASSERT(0 == user_data.begin_headers_cb_called);
|
CU_ASSERT(0 == user_data.begin_headers_cb_called);
|
||||||
CU_ASSERT(1 == session->num_incoming_reserved_streams);
|
CU_ASSERT(0 == session->num_incoming_reserved_streams);
|
||||||
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
|
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
|
||||||
item = nghttp2_session_get_next_ob_item(session);
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
||||||
|
@ -2847,7 +2914,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||||
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
|
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
|
||||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
|
|
||||||
/* Attempt to PUSH_PROMISE against non-existent stream */
|
/* Attempt to PUSH_PROMISE against idle stream */
|
||||||
frame.hd.stream_id = 3;
|
frame.hd.stream_id = 3;
|
||||||
frame.push_promise.promised_stream_id = 8;
|
frame.push_promise.promised_stream_id = 8;
|
||||||
|
|
||||||
|
@ -2857,7 +2924,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
||||||
nghttp2_session_on_push_promise_received(session, &frame));
|
nghttp2_session_on_push_promise_received(session, &frame));
|
||||||
|
|
||||||
CU_ASSERT(0 == user_data.begin_headers_cb_called);
|
CU_ASSERT(0 == user_data.begin_headers_cb_called);
|
||||||
CU_ASSERT(1 == session->num_incoming_reserved_streams);
|
CU_ASSERT(0 == session->num_incoming_reserved_streams);
|
||||||
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
|
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
|
||||||
item = nghttp2_session_get_next_ob_item(session);
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||||
|
@ -3241,6 +3308,53 @@ void test_nghttp2_session_on_data_received(void) {
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_nghttp2_session_on_data_received_fail_fast(void) {
|
||||||
|
nghttp2_session *session;
|
||||||
|
nghttp2_session_callbacks callbacks;
|
||||||
|
uint8_t buf[9];
|
||||||
|
nghttp2_stream *stream;
|
||||||
|
nghttp2_frame_hd hd;
|
||||||
|
nghttp2_outbound_item *item;
|
||||||
|
|
||||||
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
|
||||||
|
nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1);
|
||||||
|
nghttp2_frame_pack_frame_hd(buf, &hd);
|
||||||
|
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
/* DATA to closed (remote) */
|
||||||
|
stream = open_stream(session, 1);
|
||||||
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
|
|
||||||
|
CU_ASSERT((ssize_t)sizeof(buf) ==
|
||||||
|
nghttp2_session_mem_recv(session, buf, sizeof(buf)));
|
||||||
|
|
||||||
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
|
|
||||||
|
CU_ASSERT(NULL != item);
|
||||||
|
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
/* DATA to closed stream with explicit closed (remote) */
|
||||||
|
stream = open_stream(session, 1);
|
||||||
|
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||||
|
nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
|
||||||
|
|
||||||
|
CU_ASSERT((ssize_t)sizeof(buf) ==
|
||||||
|
nghttp2_session_mem_recv(session, buf, sizeof(buf)));
|
||||||
|
|
||||||
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
|
|
||||||
|
CU_ASSERT(NULL != item);
|
||||||
|
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
}
|
||||||
|
|
||||||
void test_nghttp2_session_send_headers_start_stream(void) {
|
void test_nghttp2_session_send_headers_start_stream(void) {
|
||||||
nghttp2_session *session;
|
nghttp2_session *session;
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
|
@ -3984,6 +4098,7 @@ void test_nghttp2_submit_request_without_data(void) {
|
||||||
nva_out out;
|
nva_out out;
|
||||||
nghttp2_bufs bufs;
|
nghttp2_bufs bufs;
|
||||||
nghttp2_mem *mem;
|
nghttp2_mem *mem;
|
||||||
|
nghttp2_priority_spec pri_spec;
|
||||||
|
|
||||||
mem = nghttp2_mem_default();
|
mem = nghttp2_mem_default();
|
||||||
frame_pack_bufs_init(&bufs);
|
frame_pack_bufs_init(&bufs);
|
||||||
|
@ -4017,6 +4132,15 @@ void test_nghttp2_submit_request_without_data(void) {
|
||||||
|
|
||||||
nghttp2_bufs_free(&bufs);
|
nghttp2_bufs_free(&bufs);
|
||||||
nghttp2_hd_inflate_free(&inflater);
|
nghttp2_hd_inflate_free(&inflater);
|
||||||
|
|
||||||
|
/* Try to depend on itself is error */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, (int32_t)session->next_stream_id, 16,
|
||||||
|
0);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
|
||||||
|
nghttp2_submit_request(session, &pri_spec, reqnv, ARRLEN(reqnv),
|
||||||
|
NULL, NULL));
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4308,6 +4432,7 @@ void test_nghttp2_submit_headers(void) {
|
||||||
nva_out out;
|
nva_out out;
|
||||||
nghttp2_bufs bufs;
|
nghttp2_bufs bufs;
|
||||||
nghttp2_mem *mem;
|
nghttp2_mem *mem;
|
||||||
|
nghttp2_priority_spec pri_spec;
|
||||||
|
|
||||||
mem = nghttp2_mem_default();
|
mem = nghttp2_mem_default();
|
||||||
frame_pack_bufs_init(&bufs);
|
frame_pack_bufs_init(&bufs);
|
||||||
|
@ -4362,6 +4487,21 @@ void test_nghttp2_submit_headers(void) {
|
||||||
nghttp2_frame_headers_free(&frame.headers, mem);
|
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||||
|
|
||||||
nghttp2_hd_inflate_free(&inflater);
|
nghttp2_hd_inflate_free(&inflater);
|
||||||
|
|
||||||
|
/* Try to depend on itself */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 3, 16, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
|
||||||
|
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, &pri_spec,
|
||||||
|
reqnv, ARRLEN(reqnv), NULL));
|
||||||
|
|
||||||
|
session->next_stream_id = 5;
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 5, 16, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
|
||||||
|
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, &pri_spec,
|
||||||
|
reqnv, ARRLEN(reqnv), NULL));
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5318,38 +5458,6 @@ void test_nghttp2_session_max_concurrent_streams(void) {
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Check that on_stream_close_callback is called when server pushed
|
|
||||||
* HEADERS have NGHTTP2_FLAG_END_STREAM.
|
|
||||||
*/
|
|
||||||
void test_nghttp2_session_stream_close_on_headers_push(void) {
|
|
||||||
/* nghttp2_session *session; */
|
|
||||||
/* nghttp2_session_callbacks callbacks; */
|
|
||||||
/* const char *nv[] = { NULL }; */
|
|
||||||
/* my_user_data ud; */
|
|
||||||
/* nghttp2_frame frame; */
|
|
||||||
|
|
||||||
/* memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); */
|
|
||||||
/* callbacks.on_stream_close_callback = */
|
|
||||||
/* no_stream_user_data_stream_close_callback; */
|
|
||||||
/* ud.stream_close_cb_called = 0; */
|
|
||||||
|
|
||||||
/* nghttp2_session_client_new(&session, NGHTTP2_PROTO_SPDY2, &callbacks, &ud);
|
|
||||||
*/
|
|
||||||
/* nghttp2_session_open_stream(session, 1, NGHTTP2_CTRL_FLAG_NONE, 3, */
|
|
||||||
/* NGHTTP2_STREAM_OPENING, NULL); */
|
|
||||||
/* nghttp2_frame_syn_stream_init(&frame.syn_stream, NGHTTP2_PROTO_SPDY2, */
|
|
||||||
/* NGHTTP2_CTRL_FLAG_FIN | */
|
|
||||||
/* NGHTTP2_CTRL_FLAG_UNIDIRECTIONAL, */
|
|
||||||
/* 2, 1, 3, dup_nv(nv)); */
|
|
||||||
|
|
||||||
/* CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session,
|
|
||||||
* &frame)); */
|
|
||||||
|
|
||||||
/* nghttp2_frame_syn_stream_free(&frame.syn_stream); */
|
|
||||||
/* nghttp2_session_del(session); */
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_nghttp2_session_stop_data_with_rst_stream(void) {
|
void test_nghttp2_session_stop_data_with_rst_stream(void) {
|
||||||
nghttp2_session *session;
|
nghttp2_session *session;
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
|
@ -7746,7 +7854,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
const size_t max_concurrent_streams = 5;
|
const size_t max_concurrent_streams = 5;
|
||||||
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
||||||
max_concurrent_streams};
|
(uint32_t)max_concurrent_streams};
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
@ -7781,6 +7889,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
||||||
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
|
CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
|
||||||
|
|
||||||
open_stream(session, 11);
|
open_stream(session, 11);
|
||||||
|
nghttp2_session_adjust_closed_stream(session);
|
||||||
|
|
||||||
CU_ASSERT(1 == session->num_closed_streams);
|
CU_ASSERT(1 == session->num_closed_streams);
|
||||||
CU_ASSERT(5 == session->closed_stream_tail->stream_id);
|
CU_ASSERT(5 == session->closed_stream_tail->stream_id);
|
||||||
|
@ -7789,6 +7898,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
||||||
CU_ASSERT(NULL == session->closed_stream_head->closed_next);
|
CU_ASSERT(NULL == session->closed_stream_head->closed_next);
|
||||||
|
|
||||||
open_stream(session, 13);
|
open_stream(session, 13);
|
||||||
|
nghttp2_session_adjust_closed_stream(session);
|
||||||
|
|
||||||
CU_ASSERT(0 == session->num_closed_streams);
|
CU_ASSERT(0 == session->num_closed_streams);
|
||||||
CU_ASSERT(NULL == session->closed_stream_tail);
|
CU_ASSERT(NULL == session->closed_stream_tail);
|
||||||
|
@ -7801,6 +7911,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
||||||
|
|
||||||
/* server initiated stream is not counted to max concurrent limit */
|
/* server initiated stream is not counted to max concurrent limit */
|
||||||
open_stream(session, 2);
|
open_stream(session, 2);
|
||||||
|
nghttp2_session_adjust_closed_stream(session);
|
||||||
|
|
||||||
CU_ASSERT(1 == session->num_closed_streams);
|
CU_ASSERT(1 == session->num_closed_streams);
|
||||||
CU_ASSERT(3 == session->closed_stream_head->stream_id);
|
CU_ASSERT(3 == session->closed_stream_head->stream_id);
|
||||||
|
@ -7818,8 +7929,9 @@ void test_nghttp2_session_keep_idle_stream(void) {
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
const size_t max_concurrent_streams = 1;
|
const size_t max_concurrent_streams = 1;
|
||||||
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
||||||
max_concurrent_streams};
|
(uint32_t)max_concurrent_streams};
|
||||||
int i;
|
int i;
|
||||||
|
int32_t stream_id;
|
||||||
|
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
callbacks.send_callback = null_send_callback;
|
callbacks.send_callback = null_send_callback;
|
||||||
|
@ -7828,25 +7940,29 @@ void test_nghttp2_session_keep_idle_stream(void) {
|
||||||
|
|
||||||
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
|
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
|
||||||
|
|
||||||
/* We at least allow 2 idle streams even if max concurrent streams
|
/* We at least allow NGHTTP2_MIN_IDLE_STREAM idle streams even if
|
||||||
is very low. */
|
max concurrent streams is very low. */
|
||||||
for (i = 0; i < 2; ++i) {
|
for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) {
|
||||||
nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE,
|
nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE,
|
||||||
&pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
|
&pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
|
||||||
|
nghttp2_session_adjust_idle_stream(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
CU_ASSERT(2 == session->num_idle_streams);
|
CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
|
||||||
|
|
||||||
|
stream_id = (NGHTTP2_MIN_IDLE_STREAMS - 1) * 2 + 1;
|
||||||
CU_ASSERT(1 == session->idle_stream_head->stream_id);
|
CU_ASSERT(1 == session->idle_stream_head->stream_id);
|
||||||
CU_ASSERT(3 == session->idle_stream_tail->stream_id);
|
CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
|
||||||
|
|
||||||
nghttp2_session_open_stream(session, 5, NGHTTP2_FLAG_NONE, &pri_spec_default,
|
stream_id += 2;
|
||||||
NGHTTP2_STREAM_IDLE, NULL);
|
|
||||||
|
|
||||||
CU_ASSERT(2 == session->num_idle_streams);
|
nghttp2_session_open_stream(session, stream_id, NGHTTP2_FLAG_NONE,
|
||||||
|
&pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
|
||||||
|
nghttp2_session_adjust_idle_stream(session);
|
||||||
|
|
||||||
|
CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
|
||||||
CU_ASSERT(3 == session->idle_stream_head->stream_id);
|
CU_ASSERT(3 == session->idle_stream_head->stream_id);
|
||||||
CU_ASSERT(5 == session->idle_stream_tail->stream_id);
|
CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
@ -8639,7 +8755,7 @@ void test_nghttp2_session_flooding(void) {
|
||||||
void test_nghttp2_session_change_stream_priority(void) {
|
void test_nghttp2_session_change_stream_priority(void) {
|
||||||
nghttp2_session *session;
|
nghttp2_session *session;
|
||||||
nghttp2_session_callbacks callbacks;
|
nghttp2_session_callbacks callbacks;
|
||||||
nghttp2_stream *stream1, *stream2, *stream3;
|
nghttp2_stream *stream1, *stream2, *stream3, *stream5;
|
||||||
nghttp2_priority_spec pri_spec;
|
nghttp2_priority_spec pri_spec;
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
|
@ -8672,6 +8788,41 @@ void test_nghttp2_session_change_stream_priority(void) {
|
||||||
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
|
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
|
||||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||||
|
|
||||||
|
/* Depends on the non-existing idle stream. This creates that idle
|
||||||
|
stream. */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
|
||||||
|
|
||||||
|
rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
stream5 = nghttp2_session_get_stream_raw(session, 5);
|
||||||
|
|
||||||
|
CU_ASSERT(NULL != stream5);
|
||||||
|
CU_ASSERT(&session->root == stream5->dep_prev);
|
||||||
|
CU_ASSERT(stream5 == stream2->dep_prev);
|
||||||
|
CU_ASSERT(9 == stream2->weight);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Check that this works in client session too */
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
stream1 = open_stream(session, 1);
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
|
||||||
|
|
||||||
|
rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
stream5 = nghttp2_session_get_stream_raw(session, 5);
|
||||||
|
|
||||||
|
CU_ASSERT(NULL != stream5);
|
||||||
|
CU_ASSERT(&session->root == stream5->dep_prev);
|
||||||
|
CU_ASSERT(stream5 == stream1->dep_prev);
|
||||||
|
CU_ASSERT(9 == stream1->weight);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8681,8 +8832,10 @@ void test_nghttp2_session_create_idle_stream(void) {
|
||||||
nghttp2_stream *stream2, *stream4, *stream8, *stream10;
|
nghttp2_stream *stream2, *stream4, *stream8, *stream10;
|
||||||
nghttp2_priority_spec pri_spec;
|
nghttp2_priority_spec pri_spec;
|
||||||
int rv;
|
int rv;
|
||||||
|
int i;
|
||||||
|
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
callbacks.send_callback = null_send_callback;
|
||||||
|
|
||||||
nghttp2_session_server_new(&session, &callbacks, NULL);
|
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
@ -8744,6 +8897,170 @@ void test_nghttp2_session_create_idle_stream(void) {
|
||||||
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Check that this works in client session too */
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 4, 99, 1);
|
||||||
|
|
||||||
|
rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
stream4 = nghttp2_session_get_stream_raw(session, 4);
|
||||||
|
stream2 = nghttp2_session_get_stream_raw(session, 2);
|
||||||
|
|
||||||
|
CU_ASSERT(NULL != stream4);
|
||||||
|
CU_ASSERT(NULL != stream2);
|
||||||
|
CU_ASSERT(&session->root == stream4->dep_prev);
|
||||||
|
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight);
|
||||||
|
CU_ASSERT(stream4 == stream2->dep_prev);
|
||||||
|
CU_ASSERT(99 == stream2->weight);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Check that idle stream is reduced when nghttp2_session_send() is
|
||||||
|
called. */
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
session->local_settings.max_concurrent_streams = 30;
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
|
||||||
|
for (i = 0; i < 100; ++i) {
|
||||||
|
rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CU_ASSERT(100 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
|
CU_ASSERT(30 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(141 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Check that idle stream is reduced when nghttp2_session_mem_recv() is
|
||||||
|
called. */
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
session->local_settings.max_concurrent_streams = 30;
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
|
||||||
|
for (i = 0; i < 100; ++i) {
|
||||||
|
rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
CU_ASSERT(100 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0));
|
||||||
|
CU_ASSERT(30 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(141 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_nghttp2_session_repeated_priority_change(void) {
|
||||||
|
nghttp2_session *session;
|
||||||
|
nghttp2_session_callbacks callbacks;
|
||||||
|
nghttp2_frame frame;
|
||||||
|
nghttp2_priority_spec pri_spec;
|
||||||
|
int32_t stream_id, last_stream_id;
|
||||||
|
int32_t max_streams = 20;
|
||||||
|
|
||||||
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
|
||||||
|
nghttp2_session_server_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
session->local_settings.max_concurrent_streams = (uint32_t)max_streams;
|
||||||
|
|
||||||
|
/* 1 -> 0 */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
|
||||||
|
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
|
||||||
|
|
||||||
|
nghttp2_frame_priority_free(&frame.priority);
|
||||||
|
|
||||||
|
last_stream_id = max_streams * 2 + 1;
|
||||||
|
|
||||||
|
for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
|
||||||
|
/* 1 -> stream_id */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
|
||||||
|
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
|
||||||
|
|
||||||
|
nghttp2_frame_priority_free(&frame.priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
CU_ASSERT(20 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(1 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
/* 1 -> last_stream_id */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
|
||||||
|
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
|
||||||
|
|
||||||
|
nghttp2_frame_priority_free(&frame.priority);
|
||||||
|
|
||||||
|
CU_ASSERT(20 == session->num_idle_streams);
|
||||||
|
CU_ASSERT(3 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_nghttp2_session_repeated_priority_submission(void) {
|
||||||
|
nghttp2_session *session;
|
||||||
|
nghttp2_session_callbacks callbacks;
|
||||||
|
nghttp2_priority_spec pri_spec;
|
||||||
|
int32_t stream_id, last_stream_id;
|
||||||
|
uint32_t max_streams = NGHTTP2_MIN_IDLE_STREAMS;
|
||||||
|
|
||||||
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
|
|
||||||
|
callbacks.send_callback = null_send_callback;
|
||||||
|
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, NULL);
|
||||||
|
|
||||||
|
session->local_settings.max_concurrent_streams = max_streams;
|
||||||
|
|
||||||
|
/* 1 -> 0 */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(0 ==
|
||||||
|
nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
|
||||||
|
|
||||||
|
last_stream_id = (int32_t)(max_streams * 2 + 1);
|
||||||
|
|
||||||
|
for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
|
||||||
|
/* 1 -> stream_id */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(
|
||||||
|
0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
|
CU_ASSERT(max_streams == session->num_idle_streams);
|
||||||
|
CU_ASSERT(1 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
/* 1 -> last_stream_id */
|
||||||
|
nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
|
||||||
|
|
||||||
|
CU_ASSERT(0 ==
|
||||||
|
nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
|
||||||
|
|
||||||
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
|
CU_ASSERT(max_streams == session->num_idle_streams);
|
||||||
|
CU_ASSERT(3 == session->idle_stream_head->stream_id);
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void check_nghttp2_http_recv_headers_fail(
|
static void check_nghttp2_http_recv_headers_fail(
|
||||||
|
|
|
@ -34,6 +34,7 @@ void test_nghttp2_session_recv_data_no_auto_flow_control(void);
|
||||||
void test_nghttp2_session_recv_continuation(void);
|
void test_nghttp2_session_recv_continuation(void);
|
||||||
void test_nghttp2_session_recv_headers_with_priority(void);
|
void test_nghttp2_session_recv_headers_with_priority(void);
|
||||||
void test_nghttp2_session_recv_headers_early_response(void);
|
void test_nghttp2_session_recv_headers_early_response(void);
|
||||||
|
void test_nghttp2_session_server_recv_push_response(void);
|
||||||
void test_nghttp2_session_recv_premature_headers(void);
|
void test_nghttp2_session_recv_premature_headers(void);
|
||||||
void test_nghttp2_session_recv_unknown_frame(void);
|
void test_nghttp2_session_recv_unknown_frame(void);
|
||||||
void test_nghttp2_session_recv_unexpected_continuation(void);
|
void test_nghttp2_session_recv_unexpected_continuation(void);
|
||||||
|
@ -54,6 +55,7 @@ void test_nghttp2_session_on_ping_received(void);
|
||||||
void test_nghttp2_session_on_goaway_received(void);
|
void test_nghttp2_session_on_goaway_received(void);
|
||||||
void test_nghttp2_session_on_window_update_received(void);
|
void test_nghttp2_session_on_window_update_received(void);
|
||||||
void test_nghttp2_session_on_data_received(void);
|
void test_nghttp2_session_on_data_received(void);
|
||||||
|
void test_nghttp2_session_on_data_received_fail_fast(void);
|
||||||
void test_nghttp2_session_send_headers_start_stream(void);
|
void test_nghttp2_session_send_headers_start_stream(void);
|
||||||
void test_nghttp2_session_send_headers_reply(void);
|
void test_nghttp2_session_send_headers_reply(void);
|
||||||
void test_nghttp2_session_send_headers_frame_size_error(void);
|
void test_nghttp2_session_send_headers_frame_size_error(void);
|
||||||
|
@ -95,7 +97,6 @@ void test_nghttp2_session_get_next_ob_item(void);
|
||||||
void test_nghttp2_session_pop_next_ob_item(void);
|
void test_nghttp2_session_pop_next_ob_item(void);
|
||||||
void test_nghttp2_session_reply_fail(void);
|
void test_nghttp2_session_reply_fail(void);
|
||||||
void test_nghttp2_session_max_concurrent_streams(void);
|
void test_nghttp2_session_max_concurrent_streams(void);
|
||||||
void test_nghttp2_session_stream_close_on_headers_push(void);
|
|
||||||
void test_nghttp2_session_stop_data_with_rst_stream(void);
|
void test_nghttp2_session_stop_data_with_rst_stream(void);
|
||||||
void test_nghttp2_session_defer_data(void);
|
void test_nghttp2_session_defer_data(void);
|
||||||
void test_nghttp2_session_flow_control(void);
|
void test_nghttp2_session_flow_control(void);
|
||||||
|
@ -139,6 +140,8 @@ void test_nghttp2_session_detach_item_from_closed_stream(void);
|
||||||
void test_nghttp2_session_flooding(void);
|
void test_nghttp2_session_flooding(void);
|
||||||
void test_nghttp2_session_change_stream_priority(void);
|
void test_nghttp2_session_change_stream_priority(void);
|
||||||
void test_nghttp2_session_create_idle_stream(void);
|
void test_nghttp2_session_create_idle_stream(void);
|
||||||
|
void test_nghttp2_session_repeated_priority_change(void);
|
||||||
|
void test_nghttp2_session_repeated_priority_submission(void);
|
||||||
void test_nghttp2_http_mandatory_headers(void);
|
void test_nghttp2_http_mandatory_headers(void);
|
||||||
void test_nghttp2_http_content_length(void);
|
void test_nghttp2_http_content_length(void);
|
||||||
void test_nghttp2_http_content_length_mismatch(void);
|
void test_nghttp2_http_content_length_mismatch(void);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 81eff20bd84b4d0dce2cbbd1a5ad1384d086423b
|
Subproject commit 5c47587bc2855f2b9577a9bd369ed70088b77fec
|
Loading…
Reference in New Issue