Merge branch 'master' into simple-extensions
This commit is contained in:
commit
0caefe20ef
2
COPYING
2
COPYING
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
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_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.
|
||||
|
||||
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
|
||||
|
||||
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_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
@ -46,9 +46,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
|||
|
||||
dnl See versioning rule:
|
||||
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
|
||||
AC_SUBST(LT_CURRENT, 17)
|
||||
AC_SUBST(LT_CURRENT, 18)
|
||||
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"`
|
||||
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])],
|
||||
[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],
|
||||
[AS_HELP_STRING([--with-libxml2],
|
||||
[Use libxml2 [default=check]])],
|
||||
|
@ -150,6 +155,13 @@ PKG_PROG_PKG_CONFIG([0.20])
|
|||
|
||||
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
|
||||
AX_PYTHON_DEVEL([>= '2.7'])
|
||||
fi
|
||||
|
@ -243,6 +255,11 @@ if test "x${have_zlib}" = "xno"; then
|
|||
AC_MSG_NOTICE($ZLIB_PKG_ERRORS)
|
||||
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
|
||||
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
|
||||
|
@ -613,6 +630,10 @@ AC_CHECK_FUNCS([ \
|
|||
AC_CHECK_FUNC([timerfd_create],
|
||||
[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
|
||||
AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])
|
||||
|
|
|
@ -66,7 +66,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
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
|
||||
# |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.
|
||||
.
|
||||
.TH "H2LOAD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
|
||||
.TH "H2LOAD" "1" "December 23, 2015" "1.6.0" "nghttp2"
|
||||
.SH NAME
|
||||
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
|
||||
connections.
|
||||
.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
|
||||
.SH FLOW CONTROL
|
||||
.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
|
||||
is omitted, a second is used as unit.
|
||||
|
||||
.. _h2load-1-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
|
||||
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
|
||||
------------
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.. _h2load-1-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
|
||||
connections.
|
||||
|
||||
req/s (client)
|
||||
req/s
|
||||
min
|
||||
The minimum request per second among all clients.
|
||||
max
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" 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
|
||||
nghttp \- HTTP/2 client
|
||||
.
|
||||
|
@ -152,7 +152,8 @@ Default: \fB16\fP
|
|||
.B \-M, \-\-peer\-max\-concurrent\-streams=<N>
|
||||
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
||||
remote endpoint as if it is received in SETTINGS frame.
|
||||
The default is large enough as it is seen as unlimited.
|
||||
.sp
|
||||
Default: \fB100\fP
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
|
|
|
@ -116,7 +116,8 @@ OPTIONS
|
|||
|
||||
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
|
||||
remote endpoint as if it is received in SETTINGS frame.
|
||||
The default is large enough as it is seen as unlimited.
|
||||
|
||||
Default: ``100``
|
||||
|
||||
.. option:: -c, --header-table-size=<SIZE>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" 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
|
||||
nghttpd \- HTTP/2 server
|
||||
.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" 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
|
||||
nghttpx \- HTTP/2 proxy
|
||||
.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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
|
||||
supports SPDY protocol. It supports SSL/TLS and clear text for both
|
||||
HTTP/2 and SPDY.
|
||||
supports SPDY protocol. It supports SSL/TLS and clear text for all
|
||||
supported protocols.
|
||||
|
||||
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.
|
||||
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
|
||||
using total 100000 requests, 100 concurrent clients and 10 max
|
||||
concurrent streams::
|
||||
concurrent streams:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ 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
|
||||
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
|
||||
.. code-block:: text
|
||||
|
||||
The number of ``failed`` is the number of requests returned with non
|
||||
2xx status. The number of ``error`` is the number of ``failed`` plus
|
||||
the number of requests which failed with connection error.
|
||||
finished in 7.08s, 141164.80 req/s, 555.33MB/s
|
||||
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
|
||||
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
|
||||
data. If SSL/TLS is used, this number is calculated after decryption.
|
||||
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.
|
||||
See the h2load manual page :ref:`h2load-1-output` section for the
|
||||
explanation of the above numbers.
|
||||
|
||||
Flow Control
|
||||
------------
|
||||
|
|
|
@ -3296,6 +3296,9 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
|
|||
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
|
||||
* No stream ID is available because maximum stream ID was
|
||||
* reached.
|
||||
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
|
||||
* Trying to depend on itself (new stream ID equals
|
||||
* ``pri_spec->stream_id``).
|
||||
*
|
||||
* .. 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
|
||||
* any response HEADERS submission (usually by
|
||||
* `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()`,
|
||||
* 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
|
||||
* reached.
|
||||
* :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`
|
||||
* DATA or HEADERS has been already submitted and not fully
|
||||
* 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`
|
||||
* The |stream_id| is 0.
|
||||
* :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::
|
||||
*
|
||||
|
@ -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
|
||||
* that peer initiated.
|
||||
* :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::
|
||||
*
|
||||
|
@ -4336,7 +4340,7 @@ typedef enum {
|
|||
* :enum:`NGHTTP2_ERR_HEADER_COMP`
|
||||
* Inflation process has failed.
|
||||
* :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::
|
||||
*
|
||||
|
|
|
@ -43,9 +43,6 @@ typedef struct {
|
|||
/* nonzero if request HEADERS is canceled. The error code is stored
|
||||
in |error_code|. */
|
||||
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;
|
||||
|
||||
/* 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) {
|
||||
nghttp2_pq_entry *t = pq->q[i];
|
||||
pq->q[i] = pq->q[j];
|
||||
pq->q[i]->index = i;
|
||||
pq->q[j] = t;
|
||||
pq->q[j]->index = j;
|
||||
nghttp2_pq_entry *a = pq->q[i];
|
||||
nghttp2_pq_entry *b = pq->q[j];
|
||||
|
||||
pq->q[i] = b;
|
||||
b->index = i;
|
||||
pq->q[j] = a;
|
||||
a->index = j;
|
||||
}
|
||||
|
||||
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) {
|
||||
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)) {
|
||||
|
||||
nghttp2_priority_spec_default_init(&pri_spec_default);
|
||||
|
@ -671,15 +671,9 @@ int nghttp2_session_reprioritize_stream(
|
|||
assert(dep_stream);
|
||||
|
||||
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
|
||||
/* This is minor optimization when just weight is changed.
|
||||
Currently, we don't reschedule stream in this case, since we
|
||||
don't retain enough information to do that
|
||||
(descendant_last_cycle we used to schedule it). This means new
|
||||
weight is only applied in the next scheduling, and if weight is
|
||||
drastically increased, library is not responding very quickly.
|
||||
If this is really an issue, we will do workaround for this. */
|
||||
dep_stream->sum_dep_weight += pri_spec->weight - stream->weight;
|
||||
stream->weight = pri_spec->weight;
|
||||
/* This is minor optimization when just weight is changed. */
|
||||
nghttp2_stream_change_weight(stream, pri_spec->weight);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -729,20 +723,6 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
|||
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);
|
||||
item->queued = 1;
|
||||
break;
|
||||
|
@ -778,6 +758,10 @@ int nghttp2_session_add_item(nghttp2_session *session,
|
|||
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);
|
||||
item->queued = 1;
|
||||
|
||||
|
@ -909,15 +893,6 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
return NULL;
|
||||
}
|
||||
} 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));
|
||||
if (stream == NULL) {
|
||||
return NULL;
|
||||
|
@ -929,7 +904,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
if (pri_spec->stream_id != 0) {
|
||||
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)) {
|
||||
/* Depends on idle stream, which does not exist in memory.
|
||||
Assign default priority for it. */
|
||||
|
@ -994,11 +969,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
|
|||
case NGHTTP2_STREAM_IDLE:
|
||||
/* Idle stream does not count toward the concurrent streams limit.
|
||||
This is used as anchor node in dependency tree. */
|
||||
assert(session->server);
|
||||
rv = nghttp2_session_keep_idle_stream(session, stream);
|
||||
if (rv != 0) {
|
||||
return NULL;
|
||||
}
|
||||
nghttp2_session_keep_idle_stream(session, stream);
|
||||
break;
|
||||
default:
|
||||
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
|
||||
combined with the current active incoming streams to make
|
||||
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 {
|
||||
rv = nghttp2_session_destroy_stream(session, stream);
|
||||
}
|
||||
|
@ -1138,10 +1111,8 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream) {
|
||||
int rv;
|
||||
|
||||
void nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream) {
|
||||
DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
|
||||
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->num_closed_streams;
|
||||
|
||||
rv = nghttp2_session_adjust_closed_stream(session, 0);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream) {
|
||||
int rv;
|
||||
|
||||
void nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream) {
|
||||
DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream,
|
||||
stream->stream_id, stream->state));
|
||||
|
||||
|
@ -1179,13 +1141,6 @@ int nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
|||
session->idle_stream_tail = stream;
|
||||
|
||||
++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,
|
||||
|
@ -1216,8 +1171,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
|||
--session->num_idle_streams;
|
||||
}
|
||||
|
||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
||||
size_t offset) {
|
||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session) {
|
||||
size_t num_stream_max;
|
||||
int rv;
|
||||
|
||||
|
@ -1231,7 +1185,7 @@ int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
|||
num_stream_max));
|
||||
|
||||
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) {
|
||||
nghttp2_stream *head_stream;
|
||||
nghttp2_stream *next;
|
||||
|
@ -1267,13 +1221,11 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
|
|||
size_t max;
|
||||
int rv;
|
||||
|
||||
/* Make minimum number of idle streams 2 so that allocating 2
|
||||
streams at once is easy. This happens when PRIORITY frame to
|
||||
idle stream, which depends on idle stream which does not
|
||||
exist. */
|
||||
max =
|
||||
nghttp2_max(2, nghttp2_min(session->local_settings.max_concurrent_streams,
|
||||
session->pending_local_max_concurrent_stream));
|
||||
/* Make minimum number of idle streams 16, which is arbitrary chosen
|
||||
number. */
|
||||
max = nghttp2_max(16,
|
||||
nghttp2_min(session->local_settings.max_concurrent_streams,
|
||||
session->pending_local_max_concurrent_stream));
|
||||
|
||||
DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
|
||||
"num_idle_streams=%zu, max=%zu\n",
|
||||
|
@ -1331,6 +1283,15 @@ int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
|
|||
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.
|
||||
*/
|
||||
|
@ -1850,6 +1811,9 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
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(
|
||||
session, frame->headers.nva, frame->headers.nvlen,
|
||||
NGHTTP2_PRIORITY_SPECLEN);
|
||||
|
@ -1899,7 +1863,7 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
}
|
||||
|
||||
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.
|
||||
// Search stream including closed again.
|
||||
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);
|
||||
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.
|
||||
// Search stream including closed again.
|
||||
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);
|
||||
|
||||
if (!stream) {
|
||||
break;
|
||||
if (!session_detect_idle_stream(session, frame->hd.stream_id)) {
|
||||
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,
|
||||
&frame->priority.pri_spec);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
rv = nghttp2_session_reprioritize_stream(session, stream,
|
||||
&frame->priority.pri_spec);
|
||||
rv = nghttp2_session_adjust_idle_stream(session);
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
|
@ -2612,6 +2590,8 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
|||
nghttp2_bufs *framebufs = &aob->framebufs;
|
||||
nghttp2_frame *frame;
|
||||
nghttp2_mem *mem;
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_data_aux_data *aux_data;
|
||||
|
||||
mem = &session->mem;
|
||||
frame = &item->frame;
|
||||
|
@ -2634,50 +2614,46 @@ static int session_after_frame_sent2(nghttp2_session *session) {
|
|||
active_outbound_item_reset(&session->aob, mem);
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_data_aux_data *aux_data;
|
||||
}
|
||||
|
||||
aux_data = &item->aux_data.data;
|
||||
/* DATA frame */
|
||||
|
||||
/* On EOF, we have already detached data. Please note that
|
||||
application may issue nghttp2_submit_data() in
|
||||
on_frame_send_callback (call from session_after_frame_sent1),
|
||||
which attach data to stream. We don't want to detach it. */
|
||||
if (aux_data->eof) {
|
||||
active_outbound_item_reset(aob, mem);
|
||||
aux_data = &item->aux_data.data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Reset no_copy here because next write may not use this. */
|
||||
aux_data->no_copy = 0;
|
||||
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
||||
/* If session is closed or RST_STREAM was queued, we won't send
|
||||
further data. */
|
||||
if (nghttp2_session_predicate_data_send(session, stream) != 0) {
|
||||
if (stream) {
|
||||
rv = nghttp2_stream_detach_item(stream);
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
active_outbound_item_reset(aob, mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
aob->item = NULL;
|
||||
active_outbound_item_reset(&session->aob, mem);
|
||||
/* On EOF, we have already detached data. Please note that
|
||||
application may issue nghttp2_submit_data() in
|
||||
on_frame_send_callback (call from session_after_frame_sent1),
|
||||
which attach data to stream. We don't want to detach it. */
|
||||
if (aux_data->eof) {
|
||||
active_outbound_item_reset(aob, mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
/* Unreachable */
|
||||
assert(0);
|
||||
|
||||
/* Reset no_copy here because next write may not use this. */
|
||||
aux_data->no_copy = 0;
|
||||
|
||||
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
|
||||
|
||||
/* If session is closed or RST_STREAM was queued, we won't send
|
||||
further data. */
|
||||
if (nghttp2_session_predicate_data_send(session, stream) != 0) {
|
||||
if (stream) {
|
||||
rv = nghttp2_stream_detach_item(stream);
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
active_outbound_item_reset(aob, mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
aob->item = NULL;
|
||||
active_outbound_item_reset(&session->aob, mem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2722,6 +2698,14 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
|
|||
aob = &session->aob;
|
||||
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;
|
||||
for (;;) {
|
||||
switch (aob->state) {
|
||||
|
@ -3519,35 +3503,44 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
|
|||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||
}
|
||||
|
||||
assert(session->server);
|
||||
|
||||
if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
|
||||
/* The spec says if an endpoint receives a HEADERS with invalid
|
||||
stream ID, it MUST issue connection error with error code
|
||||
PROTOCOL_ERROR. But we could get trailer HEADERS after we have
|
||||
sent RST_STREAM to this stream and peer have not received it.
|
||||
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. */
|
||||
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(
|
||||
session, frame, NGHTTP2_ERR_PROTO,
|
||||
"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;
|
||||
}
|
||||
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)) {
|
||||
return session_inflate_handle_invalid_connection(
|
||||
session, frame, NGHTTP2_ERR_PROTO,
|
||||
"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) {
|
||||
return session_inflate_handle_invalid_connection(
|
||||
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) {
|
||||
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;
|
||||
|
||||
rv = session_call_on_begin_headers(session, frame);
|
||||
if (rv != 0) {
|
||||
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
|
||||
in this state it MUST respond with a stream error (Section
|
||||
5.4.2) of type STREAM_CLOSED.
|
||||
|
||||
We go further, and make it connection error.
|
||||
*/
|
||||
return session_inflate_handle_invalid_stream(session, frame,
|
||||
NGHTTP2_ERR_STREAM_CLOSED);
|
||||
return session_inflate_handle_invalid_connection(
|
||||
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
|
||||
}
|
||||
stream->state = NGHTTP2_STREAM_OPENED;
|
||||
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,
|
||||
"push response HEADERS: stream_id == 0");
|
||||
}
|
||||
if (session->goaway_flags) {
|
||||
/* We don't accept new stream after GOAWAY is sent or received. */
|
||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||
|
||||
if (session->server) {
|
||||
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)) {
|
||||
|
@ -3622,6 +3626,12 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
|
|||
session, frame, NGHTTP2_ERR_PROTO,
|
||||
"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)) {
|
||||
return session_inflate_handle_invalid_stream(session, frame,
|
||||
NGHTTP2_ERR_REFUSED_STREAM);
|
||||
|
@ -3647,23 +3657,17 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
|
|||
return session_inflate_handle_invalid_connection(
|
||||
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)) {
|
||||
/* half closed (remote): from the spec:
|
||||
|
||||
If an endpoint receives additional frames for a stream that is
|
||||
in this state it MUST respond with a stream error (Section
|
||||
5.4.2) of type STREAM_CLOSED.
|
||||
|
||||
we go further, and make it connection error.
|
||||
*/
|
||||
return session_inflate_handle_invalid_stream(session, frame,
|
||||
NGHTTP2_ERR_STREAM_CLOSED);
|
||||
return session_inflate_handle_invalid_connection(
|
||||
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
|
||||
}
|
||||
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
|
||||
if (stream->state == NGHTTP2_STREAM_OPENED) {
|
||||
|
@ -3672,15 +3676,9 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
|
|||
return rv;
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
|
||||
return nghttp2_session_on_push_response_headers_received(session, frame,
|
||||
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;
|
||||
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) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
|
||||
rv = nghttp2_session_adjust_idle_stream(session);
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return rv;
|
||||
}
|
||||
} else {
|
||||
rv = nghttp2_session_reprioritize_stream(session, stream,
|
||||
&frame->priority.pri_spec);
|
||||
|
@ -3776,6 +3777,11 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
|
|||
if (nghttp2_is_fatal(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);
|
||||
|
@ -3805,6 +3811,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
|
|||
return session_handle_invalid_connection(
|
||||
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);
|
||||
|
@ -4220,17 +4229,17 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
|||
return session_inflate_handle_invalid_connection(
|
||||
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)) {
|
||||
return session_inflate_handle_invalid_connection(
|
||||
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,
|
||||
frame->push_promise.promised_stream_id)) {
|
||||
/* 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(
|
||||
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(
|
||||
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
|
||||
if (rv != 0) {
|
||||
|
@ -4259,25 +4272,13 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
|||
}
|
||||
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
|
||||
}
|
||||
|
||||
if (stream->shut_flags & NGHTTP2_SHUT_RD) {
|
||||
if (session->callbacks.on_invalid_frame_recv_callback) {
|
||||
if (session->callbacks.on_invalid_frame_recv_callback(
|
||||
session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data) !=
|
||||
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;
|
||||
return session_inflate_handle_invalid_connection(
|
||||
session, frame, NGHTTP2_ERR_STREAM_CLOSED,
|
||||
"PUSH_PROMISE: stream closed");
|
||||
}
|
||||
|
||||
/* 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_DEFAULT_WEIGHT, 0);
|
||||
|
||||
|
@ -4289,6 +4290,9 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
|
|||
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;
|
||||
rv = session_call_on_begin_headers(session, frame);
|
||||
if (rv != 0) {
|
||||
|
@ -4392,8 +4396,9 @@ session_on_connection_window_update_received(nghttp2_session *session,
|
|||
nghttp2_frame *frame) {
|
||||
/* Handle connection-level flow control */
|
||||
if (frame->window_update.window_size_increment == 0) {
|
||||
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
|
||||
NULL);
|
||||
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 <
|
||||
|
@ -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");
|
||||
}
|
||||
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 <
|
||||
stream->remote_window_size) {
|
||||
|
@ -4736,6 +4743,14 @@ static int session_on_data_received_fail_fast(nghttp2_session *session) {
|
|||
error_code = NGHTTP2_PROTOCOL_ERROR;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
|
||||
/* 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 (;;) {
|
||||
switch (iframe->state) {
|
||||
case NGHTTP2_IB_READ_CLIENT_MAGIC:
|
||||
|
@ -6671,6 +6694,10 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
|
|||
if (stream == NULL) {
|
||||
return NGHTTP2_ERR_NOMEM;
|
||||
}
|
||||
|
||||
/* We don't call nghttp2_session_adjust_closed_stream(), since this
|
||||
should be the first stream open. */
|
||||
|
||||
if (session->server) {
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
|
||||
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(
|
||||
nghttp2_session *session, int32_t stream_id,
|
||||
const nghttp2_priority_spec *pri_spec) {
|
||||
int rv;
|
||||
nghttp2_stream *stream;
|
||||
nghttp2_priority_spec pri_spec_copy;
|
||||
|
||||
|
@ -6900,7 +6928,18 @@ int nghttp2_session_change_stream_priority(
|
|||
pri_spec_copy = *pri_spec;
|
||||
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,
|
||||
|
@ -6929,5 +6968,10 @@ int nghttp2_session_create_idle_stream(nghttp2_session *session,
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -73,6 +73,10 @@ typedef struct {
|
|||
/* The default maximum number of incoming reserved streams */
|
||||
#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
|
||||
as flooding caused by peer. All frames are not considered here.
|
||||
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
|
||||
* 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,
|
||||
int32_t stream_id, uint8_t flags,
|
||||
|
@ -498,29 +507,17 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
|
|||
* limitation of maximum number of streams in memory, |stream| is not
|
||||
* closed and just deleted from memory (see
|
||||
* 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,
|
||||
nghttp2_stream *stream);
|
||||
void nghttp2_session_keep_closed_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
/*
|
||||
* Appends |stream| to linked list |session->idle_stream_head|. We
|
||||
* apply fixed limit for list size. To fit into that limit, one or
|
||||
* 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,
|
||||
nghttp2_stream *stream);
|
||||
void nghttp2_session_keep_idle_stream(nghttp2_session *session,
|
||||
nghttp2_stream *stream);
|
||||
|
||||
/*
|
||||
* Detaches |stream| from idle streams linked list.
|
||||
|
@ -531,9 +528,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
|||
/*
|
||||
* Deletes closed stream to ensure that number of incoming streams
|
||||
* including active and closed is in the maximum number of allowed
|
||||
* stream. If |offset| is nonzero, it is decreased from the maximum
|
||||
* number of allowed stream when comparing number of active and closed
|
||||
* stream and the maximum number.
|
||||
* stream.
|
||||
*
|
||||
* This function returns 0 if it succeeds, or one the following
|
||||
* negative error codes:
|
||||
|
@ -541,8 +536,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
|
|||
* NGHTTP2_ERR_NOMEM
|
||||
* Out of memory
|
||||
*/
|
||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
|
||||
size_t offset);
|
||||
int nghttp2_session_adjust_closed_stream(nghttp2_session *session);
|
||||
|
||||
/*
|
||||
* 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->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
|
||||
* negative error codes:
|
||||
*
|
||||
|
|
|
@ -80,6 +80,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
|
|||
stream->queued = 0;
|
||||
stream->descendant_last_cycle = 0;
|
||||
stream->cycle = 0;
|
||||
stream->pending_penalty = 0;
|
||||
stream->descendant_next_seq = 0;
|
||||
stream->seq = 0;
|
||||
stream->last_writelen = 0;
|
||||
|
@ -115,9 +116,14 @@ static int stream_subtree_active(nghttp2_stream *stream) {
|
|||
/*
|
||||
* Returns next cycle for |stream|.
|
||||
*/
|
||||
static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
|
||||
return last_cycle +
|
||||
stream->last_writelen * NGHTTP2_MAX_WEIGHT / (uint32_t)stream->weight;
|
||||
static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
|
||||
size_t penalty;
|
||||
|
||||
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) {
|
||||
|
@ -125,8 +131,7 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
|
|||
|
||||
for (; dep_stream && !stream->queued;
|
||||
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++;
|
||||
|
||||
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%ld\n",
|
||||
|
@ -169,6 +174,7 @@ static void stream_obq_remove(nghttp2_stream *stream) {
|
|||
|
||||
stream->queued = 0;
|
||||
stream->cycle = 0;
|
||||
stream->pending_penalty = 0;
|
||||
stream->descendant_last_cycle = 0;
|
||||
stream->last_writelen = 0;
|
||||
|
||||
|
@ -207,25 +213,12 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
|
|||
dep_stream = 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_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||
stream->seq = dep_stream->descendant_next_seq++;
|
||||
|
||||
stream->cycle =
|
||||
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
|
||||
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",
|
||||
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) {
|
||||
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_stream_next_outbound_item(nghttp2_stream *stream) {
|
||||
nghttp2_pq_entry *ent;
|
||||
nghttp2_stream *si;
|
||||
|
||||
for (;;) {
|
||||
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;
|
||||
}
|
||||
ent = nghttp2_pq_top(&stream->obq);
|
||||
|
|
|
@ -197,6 +197,8 @@ struct nghttp2_stream {
|
|||
int32_t local_window_size;
|
||||
/* weight of this stream */
|
||||
int32_t weight;
|
||||
/* This is unpaid penalty (offset) when calculating cycle. */
|
||||
uint32_t pending_penalty;
|
||||
/* sum of weight of direct descendants */
|
||||
int32_t sum_dep_weight;
|
||||
nghttp2_stream_state state;
|
||||
|
@ -419,7 +421,14 @@ int nghttp2_stream_in_dep_tree(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_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,
|
||||
nghttp2_nv *nva_copy, size_t nvlen,
|
||||
const nghttp2_data_provider *data_prd,
|
||||
void *stream_user_data,
|
||||
uint8_t attach_stream) {
|
||||
void *stream_user_data) {
|
||||
int rv;
|
||||
uint8_t flags_copy;
|
||||
nghttp2_outbound_item *item = NULL;
|
||||
|
@ -56,6 +55,16 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
|
|||
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));
|
||||
if (item == NULL) {
|
||||
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.attach_stream = attach_stream;
|
||||
|
||||
flags_copy =
|
||||
(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_nv *nva, size_t nvlen,
|
||||
const nghttp2_data_provider *data_prd,
|
||||
void *stream_user_data,
|
||||
uint8_t attach_stream) {
|
||||
void *stream_user_data) {
|
||||
int rv;
|
||||
nghttp2_nv *nva_copy;
|
||||
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,
|
||||
nva_copy, nvlen, data_prd, stream_user_data,
|
||||
attach_stream);
|
||||
nva_copy, nvlen, data_prd, stream_user_data);
|
||||
}
|
||||
|
||||
int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
|
||||
const nghttp2_nv *nva, size_t nvlen) {
|
||||
return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
|
||||
stream_id, NULL, nva, nvlen, NULL, NULL,
|
||||
0);
|
||||
stream_id, NULL, nva, nvlen, NULL,
|
||||
NULL);
|
||||
}
|
||||
|
||||
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,
|
||||
nvlen, NULL, stream_user_data, 0);
|
||||
nvlen, NULL, stream_user_data);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
|
@ -414,7 +420,7 @@ int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
|
|||
const nghttp2_data_provider *data_prd) {
|
||||
uint8_t flags = set_response_flags(data_prd);
|
||||
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,
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#include <deque>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/dh.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
|
@ -105,7 +106,7 @@ Config::Config()
|
|||
max_concurrent_streams(100), header_table_size(-1), port(0),
|
||||
verbose(false), daemon(false), verify_client(false), no_tls(false),
|
||||
error_gzip(false), early_response(false), hexdump(false),
|
||||
echo_upload(false) {}
|
||||
echo_upload(false), no_content_length(false) {}
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
|
@ -872,12 +873,14 @@ int Http2Handler::submit_file_response(const std::string &status,
|
|||
std::string last_modified_str;
|
||||
auto nva = make_array(http2::make_nv_ls(":status", status),
|
||||
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_ls("date", sessions_->get_cached_date()),
|
||||
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
|
||||
http2::make_nv_ll("", ""));
|
||||
size_t nvlen = 5;
|
||||
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
|
||||
size_t nvlen = 4;
|
||||
if (!get_config()->no_content_length) {
|
||||
nva[nvlen++] = http2::make_nv_ls("content-length", content_length);
|
||||
}
|
||||
if (last_modified != 0) {
|
||||
last_modified_str = util::http_date(last_modified);
|
||||
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.emplace_back("nghttpd-response", "echo");
|
||||
headers.emplace_back("content-length", util::utos(length));
|
||||
if (!hd->get_config()->no_content_length) {
|
||||
headers.emplace_back("content-length", util::utos(length));
|
||||
}
|
||||
|
||||
hd->submit_response("200", stream->stream_id, headers, &data_prd);
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ struct Config {
|
|||
bool early_response;
|
||||
bool hexdump;
|
||||
bool echo_upload;
|
||||
bool no_content_length;
|
||||
Config();
|
||||
~Config();
|
||||
};
|
||||
|
|
|
@ -39,12 +39,16 @@ using boost::asio::ip::tcp;
|
|||
|
||||
session::session(boost::asio::io_service &io_service, const std::string &host,
|
||||
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,
|
||||
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
||||
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() {}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
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 asio_http2
|
||||
} // nghttp2
|
||||
|
|
|
@ -40,8 +40,10 @@ namespace client {
|
|||
|
||||
session_impl::session_impl(boost::asio::io_service &io_service)
|
||||
: wblen_(0), io_service_(io_service), resolver_(io_service),
|
||||
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
|
||||
writing_(false), inside_callback_(false) {}
|
||||
deadline_(io_service), connect_timeout_(boost::posix_time::seconds(60)),
|
||||
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() {
|
||||
// finish up all active stream
|
||||
|
@ -56,9 +58,13 @@ session_impl::~session_impl() {
|
|||
|
||||
void session_impl::start_resolve(const std::string &host,
|
||||
const std::string &service) {
|
||||
deadline_.expires_from_now(connect_timeout_);
|
||||
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
resolver_.async_resolve({host, service},
|
||||
[this](const boost::system::error_code &ec,
|
||||
tcp::resolver::iterator endpoint_it) {
|
||||
[this, self](const boost::system::error_code &ec,
|
||||
tcp::resolver::iterator endpoint_it) {
|
||||
if (ec) {
|
||||
not_connected(ec);
|
||||
return;
|
||||
|
@ -66,6 +72,25 @@ void session_impl::start_resolve(const std::string &host,
|
|||
|
||||
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) {
|
||||
|
@ -86,6 +111,7 @@ void session_impl::connected(tcp::resolver::iterator endpoint_it) {
|
|||
|
||||
void session_impl::not_connected(const boost::system::error_code &ec) {
|
||||
call_error_cb(ec);
|
||||
stop();
|
||||
}
|
||||
|
||||
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_; }
|
||||
|
||||
void session_impl::call_error_cb(const boost::system::error_code &ec) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
auto &error_cb = on_error();
|
||||
if (!error_cb) {
|
||||
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) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
|
||||
error_code);
|
||||
signal_write();
|
||||
}
|
||||
|
||||
void session_impl::resume(stream &strm) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
nghttp2_session_resume_data(session_, strm.stream_id());
|
||||
signal_write();
|
||||
}
|
||||
|
@ -396,6 +433,11 @@ const request *session_impl::submit(boost::system::error_code &ec,
|
|||
header_map h) {
|
||||
ec.clear();
|
||||
|
||||
if (stopped_) {
|
||||
ec = make_error_code(static_cast<nghttp2_error>(NGHTTP2_INTERNAL_ERROR));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
http_parser_url u{};
|
||||
// TODO Handle CONNECT method
|
||||
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() {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
|
||||
signal_write();
|
||||
}
|
||||
|
@ -522,13 +568,21 @@ void session_impl::leave_callback() {
|
|||
}
|
||||
|
||||
void session_impl::do_read() {
|
||||
read_socket([this](const boost::system::error_code &ec,
|
||||
std::size_t bytes_transferred) {
|
||||
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) {
|
||||
if (ec) {
|
||||
if (!should_stop()) {
|
||||
call_error_cb(ec);
|
||||
shutdown_socket();
|
||||
}
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -541,7 +595,7 @@ void session_impl::do_read() {
|
|||
if (rv != static_cast<ssize_t>(bytes_transferred)) {
|
||||
call_error_cb(make_error_code(
|
||||
static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
|
||||
shutdown_socket();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -549,7 +603,7 @@ void session_impl::do_read() {
|
|||
do_write();
|
||||
|
||||
if (should_stop()) {
|
||||
shutdown_socket();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -558,6 +612,10 @@ void session_impl::do_read() {
|
|||
}
|
||||
|
||||
void session_impl::do_write() {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (writing_) {
|
||||
return;
|
||||
}
|
||||
|
@ -579,7 +637,7 @@ void session_impl::do_write() {
|
|||
auto n = nghttp2_session_mem_send(session_, &data);
|
||||
if (n < 0) {
|
||||
call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
|
||||
shutdown_socket();
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -601,23 +659,51 @@ void session_impl::do_write() {
|
|||
}
|
||||
|
||||
if (wblen_ == 0) {
|
||||
if (should_stop()) {
|
||||
stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
writing_ = true;
|
||||
|
||||
write_socket([this](const boost::system::error_code &ec, std::size_t n) {
|
||||
if (ec) {
|
||||
call_error_cb(ec);
|
||||
shutdown_socket();
|
||||
return;
|
||||
}
|
||||
// Reset read deadline here, because normally client is sending
|
||||
// something, it does not expect timeout while doing it.
|
||||
deadline_.expires_from_now(read_timeout_);
|
||||
|
||||
wblen_ = 0;
|
||||
writing_ = false;
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
do_write();
|
||||
});
|
||||
write_socket(
|
||||
[this, self](const boost::system::error_code &ec, std::size_t n) {
|
||||
if (ec) {
|
||||
call_error_cb(ec);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
wblen_ = 0;
|
||||
writing_ = false;
|
||||
|
||||
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
|
||||
|
|
|
@ -41,7 +41,7 @@ class stream;
|
|||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
class session_impl {
|
||||
class session_impl : public std::enable_shared_from_this<session_impl> {
|
||||
public:
|
||||
session_impl(boost::asio::io_service &io_service);
|
||||
virtual ~session_impl();
|
||||
|
@ -91,6 +91,11 @@ public:
|
|||
void do_read();
|
||||
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:
|
||||
boost::array<uint8_t, 8_k> rb_;
|
||||
boost::array<uint8_t, 64_k> wb_;
|
||||
|
@ -100,6 +105,7 @@ private:
|
|||
bool should_stop() const;
|
||||
bool setup_session();
|
||||
void call_error_cb(const boost::system::error_code &ec);
|
||||
void handle_deadline();
|
||||
|
||||
boost::asio::io_service &io_service_;
|
||||
tcp::resolver resolver_;
|
||||
|
@ -109,6 +115,10 @@ private:
|
|||
connect_cb connect_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_;
|
||||
|
||||
const uint8_t *data_pending_;
|
||||
|
@ -116,6 +126,7 @@ private:
|
|||
|
||||
bool writing_;
|
||||
bool inside_callback_;
|
||||
bool stopped_;
|
||||
};
|
||||
|
||||
} // namespace client
|
||||
|
|
|
@ -31,9 +31,7 @@ namespace client {
|
|||
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
|
||||
const std::string &host,
|
||||
const std::string &service)
|
||||
: session_impl(io_service), socket_(io_service) {
|
||||
start_resolve(host, service);
|
||||
}
|
||||
: session_impl(io_service), socket_(io_service) {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 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
|
||||
// not used, which is what we want.
|
||||
socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
|
||||
|
||||
start_resolve(host, service);
|
||||
}
|
||||
|
||||
session_tls_impl::~session_tls_impl() {}
|
||||
|
@ -85,7 +83,8 @@ void session_tls_impl::write_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
|
||||
|
|
|
@ -92,6 +92,11 @@ boost::asio::io_service &io_service_pool::get_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 nghttp2
|
||||
|
|
|
@ -70,6 +70,10 @@ public:
|
|||
/// Get an io_service to use.
|
||||
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:
|
||||
/// The pool of io_services.
|
||||
std::vector<std::shared_ptr<boost::asio::io_service>> io_services_;
|
||||
|
|
|
@ -44,8 +44,12 @@ namespace nghttp2 {
|
|||
namespace asio_http2 {
|
||||
namespace server {
|
||||
|
||||
server::server(std::size_t io_service_pool_size)
|
||||
: io_service_pool_(io_service_pool_size) {}
|
||||
server::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)
|
||||
: io_service_pool_(io_service_pool_size),
|
||||
tls_handshake_timeout_(tls_handshake_timeout),
|
||||
read_timeout_(read_timeout) {}
|
||||
|
||||
boost::system::error_code
|
||||
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,
|
||||
tcp::acceptor &acceptor, serve_mux &mux) {
|
||||
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(
|
||||
new_connection->socket().lowest_layer(),
|
||||
|
@ -130,14 +135,17 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
|
|||
if (!e) {
|
||||
new_connection->socket().lowest_layer().set_option(
|
||||
tcp::no_delay(true));
|
||||
new_connection->start_tls_handshake_deadline();
|
||||
new_connection->socket().async_handshake(
|
||||
boost::asio::ssl::stream_base::server,
|
||||
[new_connection](const boost::system::error_code &e) {
|
||||
if (e) {
|
||||
new_connection->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tls_h2_negotiated(new_connection->socket())) {
|
||||
new_connection->stop();
|
||||
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) {
|
||||
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(
|
||||
new_connection->socket(), [this, &acceptor, &mux, new_connection](
|
||||
const boost::system::error_code &e) {
|
||||
if (!e) {
|
||||
new_connection->socket().set_option(tcp::no_delay(true));
|
||||
new_connection->start_read_deadline();
|
||||
new_connection->start();
|
||||
}
|
||||
|
||||
|
@ -169,6 +179,11 @@ void server::stop() { io_service_pool_.stop(); }
|
|||
|
||||
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 asio_http2
|
||||
} // namespace nghttp2
|
||||
|
|
|
@ -63,7 +63,9 @@ using ssl_socket = boost::asio::ssl::stream<tcp::socket>;
|
|||
|
||||
class server : private boost::noncopyable {
|
||||
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
|
||||
listen_and_serve(boost::system::error_code &ec,
|
||||
|
@ -73,6 +75,10 @@ public:
|
|||
void join();
|
||||
void stop();
|
||||
|
||||
/// Get access to all io_service objects.
|
||||
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||
io_services() const;
|
||||
|
||||
private:
|
||||
/// Initiate an asynchronous accept operation.
|
||||
void start_accept(tcp::acceptor &acceptor, serve_mux &mux);
|
||||
|
@ -94,6 +100,9 @@ private:
|
|||
std::vector<tcp::acceptor> acceptors_;
|
||||
|
||||
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
|
||||
|
|
|
@ -64,15 +64,23 @@ class connection : public std::enable_shared_from_this<connection<socket_type>>,
|
|||
public:
|
||||
/// Construct a connection with the given io_service.
|
||||
template <typename... SocketArgs>
|
||||
explicit connection(serve_mux &mux, SocketArgs &&... args)
|
||||
: socket_(std::forward<SocketArgs>(args)...), mux_(mux), writing_(false) {
|
||||
}
|
||||
explicit connection(
|
||||
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.
|
||||
void start() {
|
||||
handler_ = std::make_shared<http2_handler>(socket_.get_io_service(),
|
||||
[this]() { do_write(); }, mux_);
|
||||
handler_ = std::make_shared<http2_handler>(
|
||||
socket_.get_io_service(), socket_.lowest_layer().remote_endpoint(),
|
||||
[this]() { do_write(); }, mux_);
|
||||
if (handler_->start() != 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
do_read();
|
||||
|
@ -80,27 +88,62 @@ public:
|
|||
|
||||
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() {
|
||||
auto self = this->shared_from_this();
|
||||
|
||||
deadline_.expires_from_now(read_timeout_);
|
||||
|
||||
socket_.async_read_some(
|
||||
boost::asio::buffer(buffer_),
|
||||
[this, self](const boost::system::error_code &e,
|
||||
std::size_t bytes_transferred) {
|
||||
if (!e) {
|
||||
if (handler_->on_read(buffer_, bytes_transferred) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_write();
|
||||
|
||||
if (!writing_ && handler_->should_stop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
do_read();
|
||||
if (e) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler_->on_read(buffer_, bytes_transferred) != 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
do_write();
|
||||
|
||||
if (!writing_ && handler_->should_stop()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
do_read();
|
||||
|
||||
// If an error occurs then no new asynchronous operations are
|
||||
// started. This means that all shared_ptr references to the
|
||||
// connection object will disappear and the object will be
|
||||
|
@ -122,23 +165,34 @@ public:
|
|||
rv = handler_->on_write(outbuf_, nwrite);
|
||||
|
||||
if (rv != 0) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (nwrite == 0) {
|
||||
if (handler_->should_stop()) {
|
||||
stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
socket_, boost::asio::buffer(outbuf_, nwrite),
|
||||
[this, self](const boost::system::error_code &e, std::size_t) {
|
||||
if (!e) {
|
||||
writing_ = false;
|
||||
|
||||
do_write();
|
||||
if (e) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
writing_ = false;
|
||||
|
||||
do_write();
|
||||
});
|
||||
|
||||
// No new asynchronous operations are started. This means that all
|
||||
|
@ -147,6 +201,17 @@ public:
|
|||
// 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:
|
||||
socket_type socket_;
|
||||
|
||||
|
@ -159,7 +224,12 @@ private:
|
|||
|
||||
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 stopped_;
|
||||
};
|
||||
|
||||
} // 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::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) {
|
||||
return impl_->handle(std::move(pattern), std::move(cb));
|
||||
}
|
||||
|
@ -77,6 +85,11 @@ void http2::stop() { impl_->stop(); }
|
|||
|
||||
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 asio_http2
|
||||
|
|
|
@ -136,6 +136,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
break;
|
||||
}
|
||||
|
||||
auto &req = strm->request().impl();
|
||||
req.remote_endpoint(handler->remote_endpoint());
|
||||
|
||||
handler->call_on_request(*strm);
|
||||
|
||||
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||
|
@ -225,8 +228,9 @@ int on_frame_not_send_callback(nghttp2_session *session,
|
|||
} // namespace
|
||||
|
||||
http2_handler::http2_handler(boost::asio::io_service &io_service,
|
||||
boost::asio::ip::tcp::endpoint ep,
|
||||
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),
|
||||
tstamp_cached_(time(nullptr)),
|
||||
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_; }
|
||||
|
||||
const boost::asio::ip::tcp::endpoint &http2_handler::remote_endpoint() {
|
||||
return remote_ep_;
|
||||
}
|
||||
|
||||
callback_guard::callback_guard(http2_handler &h) : handler(h) {
|
||||
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> {
|
||||
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);
|
||||
|
||||
~http2_handler();
|
||||
|
@ -89,6 +90,8 @@ public:
|
|||
|
||||
boost::asio::io_service &io_service();
|
||||
|
||||
const boost::asio::ip::tcp::endpoint &remote_endpoint();
|
||||
|
||||
const std::string &http_date();
|
||||
|
||||
template <size_t N>
|
||||
|
@ -152,6 +155,7 @@ private:
|
|||
connection_write writefun_;
|
||||
serve_mux &mux_;
|
||||
boost::asio::io_service &io_service_;
|
||||
boost::asio::ip::tcp::endpoint remote_ep_;
|
||||
nghttp2_session *session_;
|
||||
const uint8_t *buf_;
|
||||
std::size_t buflen_;
|
||||
|
|
|
@ -37,12 +37,16 @@ namespace asio_http2 {
|
|||
|
||||
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 &ec, boost::asio::ssl::context *tls_context,
|
||||
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_,
|
||||
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::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) {
|
||||
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(); }
|
||||
|
||||
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||
http2_impl::io_services() const {
|
||||
return server_->io_services();
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
|
||||
} // namespace asio_http2
|
||||
|
|
|
@ -47,15 +47,21 @@ public:
|
|||
const std::string &address, const std::string &port, bool asynchronous);
|
||||
void num_threads(size_t num_threads);
|
||||
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);
|
||||
void stop();
|
||||
void join();
|
||||
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||
io_services() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<server> server_;
|
||||
std::size_t num_threads_;
|
||||
int backlog_;
|
||||
serve_mux mux_;
|
||||
boost::posix_time::time_duration tls_handshake_timeout_;
|
||||
boost::posix_time::time_duration read_timeout_;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
|
|
|
@ -50,6 +50,10 @@ void request::on_data(data_cb cb) const {
|
|||
|
||||
request_impl &request::impl() const { return *impl_; }
|
||||
|
||||
const boost::asio::ip::tcp::endpoint &request::remote_endpoint() const {
|
||||
return impl_->remote_endpoint();
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace asio_http2
|
||||
} // 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 asio_http2
|
||||
} // namespace nghttp2
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "nghttp2_config.h"
|
||||
|
||||
#include <nghttp2/asio_http2_server.h>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
namespace nghttp2 {
|
||||
namespace asio_http2 {
|
||||
|
@ -54,12 +55,16 @@ public:
|
|||
void stream(class stream *s);
|
||||
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:
|
||||
class stream *strm_;
|
||||
header_map header_;
|
||||
std::string method_;
|
||||
uri_ref uri_;
|
||||
data_cb on_data_cb_;
|
||||
boost::asio::ip::tcp::endpoint remote_ep_;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
|
|
271
src/h2load.cc
271
src/h2load.cc
|
@ -44,6 +44,7 @@
|
|||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <random>
|
||||
|
||||
#ifdef HAVE_SPDYLAY
|
||||
#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()); }
|
||||
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)
|
||||
: 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),
|
||||
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 {
|
||||
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();
|
||||
if (rv != 0) {
|
||||
client->fail();
|
||||
delete client;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (rv != 0) {
|
||||
client->fail();
|
||||
delete client;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -134,6 +175,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
client->restart_timeout();
|
||||
if (client->do_read() != 0) {
|
||||
client->fail();
|
||||
delete client;
|
||||
return;
|
||||
}
|
||||
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;
|
||||
--worker->nreqs_rem;
|
||||
}
|
||||
worker->clients.push_back(
|
||||
make_unique<Client>(worker->next_client_id++, worker, req_todo));
|
||||
auto &client = worker->clients.back();
|
||||
auto client =
|
||||
make_unique<Client>(worker->next_client_id++, worker, req_todo);
|
||||
|
||||
++worker->nconns_made;
|
||||
|
||||
if (client->connect() != 0) {
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
client->fail();
|
||||
} else {
|
||||
client.release();
|
||||
}
|
||||
++worker->nconns_made;
|
||||
worker->report_rate_progress();
|
||||
}
|
||||
if (worker->nconns_made >= worker->nclients) {
|
||||
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
|
||||
|
||||
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),
|
||||
req_started(0), req_done(0), id(id), fd(-1),
|
||||
new_connection_requested(false) {
|
||||
|
@ -268,6 +314,12 @@ Client::~Client() {
|
|||
if (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); }
|
||||
|
@ -420,9 +472,8 @@ void Client::disconnect() {
|
|||
}
|
||||
|
||||
int Client::submit_request() {
|
||||
auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
|
||||
|
||||
if (session->submit_request(req_stat) != 0) {
|
||||
++worker->stats.req_started;
|
||||
if (session->submit_request() != 0) {
|
||||
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 {
|
||||
void print_server_tmp_key(SSL *ssl) {
|
||||
// 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,
|
||||
RequestStat *req_stat, bool final) {
|
||||
void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
|
||||
auto req_stat = get_req_stat(stream_id);
|
||||
if (!req_stat) {
|
||||
return;
|
||||
}
|
||||
|
||||
req_stat->stream_close_time = std::chrono::steady_clock::now();
|
||||
if (success) {
|
||||
req_stat->completed = true;
|
||||
++worker->stats.req_success;
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
++cstat.req_success;
|
||||
}
|
||||
++worker->stats.req_done;
|
||||
++req_done;
|
||||
if (success) {
|
||||
|
||||
if (streams[stream_id].status_success == 1) {
|
||||
++worker->stats.req_status_success;
|
||||
} else {
|
||||
++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 {
|
||||
++worker->stats.req_failed;
|
||||
++worker->stats.req_error;
|
||||
}
|
||||
report_progress();
|
||||
|
||||
++worker->stats.req_done;
|
||||
++req_done;
|
||||
|
||||
worker->report_progress();
|
||||
streams.erase(stream_id);
|
||||
if (req_done == req_todo) {
|
||||
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() {
|
||||
if (ssl) {
|
||||
report_tls_info();
|
||||
|
@ -750,7 +813,6 @@ int Client::connection_made() {
|
|||
if (!config.timing_script) {
|
||||
auto nreq =
|
||||
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
|
||||
|
||||
for (; nreq > 0; --nreq) {
|
||||
if (submit_request() != 0) {
|
||||
process_request_failure();
|
||||
|
@ -985,17 +1047,14 @@ void Client::record_request_time(RequestStat *req_stat) {
|
|||
}
|
||||
|
||||
void Client::record_connect_start_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
cstat.connect_start_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_connect_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
cstat.connect_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Client::record_ttfb() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
if (recorded(cstat.ttfb)) {
|
||||
return;
|
||||
}
|
||||
|
@ -1004,16 +1063,12 @@ void Client::record_ttfb() {
|
|||
}
|
||||
|
||||
void Client::clear_connect_times() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
cstat.connect_start_time = std::chrono::steady_clock::time_point();
|
||||
cstat.connect_time = std::chrono::steady_clock::time_point();
|
||||
cstat.ttfb = std::chrono::steady_clock::time_point();
|
||||
}
|
||||
|
||||
void Client::record_client_start_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
// Record start time only once at the very first connection is going
|
||||
// to be made.
|
||||
if (recorded(cstat.client_start_time)) {
|
||||
|
@ -1024,8 +1079,6 @@ void Client::record_client_start_time() {
|
|||
}
|
||||
|
||||
void Client::record_client_end_time() {
|
||||
auto &cstat = worker->stats.client_stats[id];
|
||||
|
||||
// Unlike client_start_time, we overwrite client_end_time. This
|
||||
// handles multiple connect/disconnect for HTTP/1.1 benchmark.
|
||||
cstat.client_end_time = std::chrono::steady_clock::now();
|
||||
|
@ -1036,20 +1089,36 @@ void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
|||
void Client::try_new_connection() { new_connection_requested = true; }
|
||||
|
||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||
size_t rate, Config *config)
|
||||
size_t rate, size_t max_samples, Config *config)
|
||||
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
|
||||
config(config), id(id), tls_info_report_done(false),
|
||||
app_info_report_done(false), nconns_made(0), nclients(nclients),
|
||||
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
|
||||
rate(rate), next_client_id(0) {
|
||||
stats.req_todo = req_todo;
|
||||
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
||||
rate(rate), max_samples(max_samples), next_client_id(0) {
|
||||
if (!config->is_rate_mode()) {
|
||||
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
|
||||
ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
|
||||
config->rate_period);
|
||||
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()) {
|
||||
for (size_t i = 0; i < nclients; ++i) {
|
||||
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;
|
||||
--nreqs_rem;
|
||||
}
|
||||
clients.push_back(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) {
|
||||
auto client = make_unique<Client>(next_client_id++, this, req_todo);
|
||||
if (client->connect() != 0) {
|
||||
std::cerr << "client could not connect to host" << std::endl;
|
||||
client->fail();
|
||||
} else {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1088,6 +1143,34 @@ void Worker::run() {
|
|||
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 {
|
||||
// Returns percentage of number of samples within mean +/- 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 {
|
||||
// Computes statistics using |samples|. The min, max, mean, sd, and
|
||||
// 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()) {
|
||||
return {0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
}
|
||||
// standard deviation calculated using Rapid calculation method:
|
||||
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
// https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
|
||||
double a = 0, q = 0;
|
||||
size_t n = 0;
|
||||
double sum = 0;
|
||||
|
@ -1130,7 +1216,7 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
|
|||
|
||||
assert(n > 0);
|
||||
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);
|
||||
|
||||
return res;
|
||||
|
@ -1140,18 +1226,29 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
|
|||
namespace {
|
||||
SDStats
|
||||
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 nclient_times = 0;
|
||||
for (const auto &w : workers) {
|
||||
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;
|
||||
request_times.reserve(nrequest_times);
|
||||
|
||||
std::vector<double> connect_times, ttfb_times, rps_values;
|
||||
connect_times.reserve(config.nclients);
|
||||
ttfb_times.reserve(config.nclients);
|
||||
rps_values.reserve(config.nclients);
|
||||
connect_times.reserve(nclient_times);
|
||||
ttfb_times.reserve(nclient_times);
|
||||
rps_values.reserve(nclient_times);
|
||||
|
||||
for (const auto &w : workers) {
|
||||
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),
|
||||
compute_time_stat(ttfb_times), compute_time_stat(rps_values)};
|
||||
return {compute_time_stat(request_times, request_times_sampling),
|
||||
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
|
||||
|
||||
|
@ -1374,7 +1473,7 @@ void read_script_from_file(std::istream &infile,
|
|||
namespace {
|
||||
std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
|
||||
size_t nreqs, size_t nclients,
|
||||
size_t rate) {
|
||||
size_t rate, size_t max_samples) {
|
||||
std::stringstream rate_report;
|
||||
if (config.is_rate_mode() && nclients > rate) {
|
||||
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 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
|
||||
|
||||
|
@ -1448,10 +1548,11 @@ Options:
|
|||
URIs, if present, are ignored. Those in the first URI
|
||||
are used solely. Definition of a base URI overrides all
|
||||
scheme, host or port values.
|
||||
-m, --max-concurrent-streams=(auto|<N>)
|
||||
Max concurrent streams to issue per session. If "auto"
|
||||
is given, the number of given URIs is used.
|
||||
Default: auto
|
||||
-m, --max-concurrent-streams=<N>
|
||||
Max concurrent streams to issue per session. When
|
||||
http/1.1 is used, this specifies the number of HTTP
|
||||
pipelining requests in-flight.
|
||||
Default: 1
|
||||
-w, --window-bits=<N>
|
||||
Sets the stream level initial window size to (2**<N>)-1.
|
||||
For SPDY, 2**<N> is used instead.
|
||||
|
@ -1626,11 +1727,7 @@ int main(int argc, char **argv) {
|
|||
#endif // NOTHREADS
|
||||
break;
|
||||
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;
|
||||
case 'w':
|
||||
case 'W': {
|
||||
|
@ -1853,11 +1950,6 @@ int main(int argc, char **argv) {
|
|||
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) {
|
||||
std::cerr << "-n: the number of requests must be strictly greater than 0."
|
||||
<< std::endl;
|
||||
|
@ -2099,6 +2191,9 @@ int main(int argc, char **argv) {
|
|||
size_t rate_per_thread = 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::condition_variable cv;
|
||||
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();
|
||||
futures.push_back(
|
||||
std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
|
||||
|
@ -2161,7 +2257,8 @@ int main(int argc, char **argv) {
|
|||
auto 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();
|
||||
|
||||
|
@ -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.sd) << std::setw(9)
|
||||
<< 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)
|
||||
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
|
||||
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
|
||||
|
|
34
src/h2load.h
34
src/h2load.h
|
@ -112,7 +112,6 @@ struct Config {
|
|||
};
|
||||
|
||||
struct RequestStat {
|
||||
RequestStat();
|
||||
// time point when request was sent
|
||||
std::chrono::steady_clock::time_point request_time;
|
||||
// time point when stream was closed
|
||||
|
@ -208,9 +207,21 @@ enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
|
|||
|
||||
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 {
|
||||
std::vector<std::unique_ptr<Client>> clients;
|
||||
Stats stats;
|
||||
Sampling request_times_smp;
|
||||
Sampling client_smp;
|
||||
struct ev_loop *loop;
|
||||
SSL_CTX *ssl_ctx;
|
||||
Config *config;
|
||||
|
@ -226,24 +237,32 @@ struct Worker {
|
|||
// at most nreqs_rem clients get an extra request
|
||||
size_t nreqs_rem;
|
||||
size_t rate;
|
||||
// maximum number of samples in this worker thread
|
||||
size_t max_samples;
|
||||
ev_timer timeout_watcher;
|
||||
// The next client ID this worker assigns
|
||||
uint32_t next_client_id;
|
||||
|
||||
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
|
||||
size_t rate, Config *config);
|
||||
size_t rate, size_t max_samples, Config *config);
|
||||
~Worker();
|
||||
Worker(Worker &&o) = default;
|
||||
void run();
|
||||
void sample_req_stat(RequestStat *req_stat);
|
||||
void sample_client_stat(ClientStat *cstat);
|
||||
void report_progress();
|
||||
void report_rate_progress();
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
RequestStat req_stat;
|
||||
int status_success;
|
||||
Stream();
|
||||
};
|
||||
|
||||
struct Client {
|
||||
std::unordered_map<int32_t, Stream> streams;
|
||||
ClientStat cstat;
|
||||
std::unique_ptr<Session> session;
|
||||
ev_io wev;
|
||||
ev_io rev;
|
||||
|
@ -288,7 +307,6 @@ struct Client {
|
|||
void process_request_failure();
|
||||
void process_timedout_streams();
|
||||
void process_abandoned_streams();
|
||||
void report_progress();
|
||||
void report_tls_info();
|
||||
void report_app_info();
|
||||
void terminate_session();
|
||||
|
@ -318,8 +336,12 @@ struct Client {
|
|||
// |success| == true means that the request/response was exchanged
|
||||
// |successfully, but it does not mean response carried successful
|
||||
// |HTTP status code.
|
||||
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat,
|
||||
bool final = false);
|
||||
void on_stream_close(int32_t stream_id, bool success, 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_connect_start_time();
|
||||
|
|
|
@ -80,9 +80,11 @@ int htp_msg_completecb(http_parser *htp) {
|
|||
auto client = session->get_client();
|
||||
|
||||
auto final = http_should_keep_alive(htp) == 0;
|
||||
client->on_stream_close(session->stream_resp_counter_, true,
|
||||
session->req_stats_[session->stream_resp_counter_],
|
||||
final);
|
||||
auto req_stat = client->get_req_stat(session->stream_resp_counter_);
|
||||
|
||||
assert(req_stat);
|
||||
|
||||
client->on_stream_close(session->stream_resp_counter_, true, final);
|
||||
|
||||
session->stream_resp_counter_ += 2;
|
||||
|
||||
|
@ -150,7 +152,7 @@ http_parser_settings htp_hooks = {
|
|||
|
||||
void Http1Session::on_connect() { client_->signal_write(); }
|
||||
|
||||
int Http1Session::submit_request(RequestStat *req_stat) {
|
||||
int Http1Session::submit_request() {
|
||||
auto config = client_->worker->config;
|
||||
const auto &req = config->h1reqs[client_->reqidx];
|
||||
client_->reqidx++;
|
||||
|
@ -159,13 +161,13 @@ int Http1Session::submit_request(RequestStat *req_stat) {
|
|||
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_->wb.write(req.c_str(), req.size());
|
||||
|
||||
client_->on_request(stream_req_counter_);
|
||||
req_stats_[stream_req_counter_] = req_stat;
|
||||
|
||||
// increment for next request
|
||||
stream_req_counter_ += 2;
|
||||
|
||||
|
|
|
@ -38,14 +38,13 @@ public:
|
|||
Http1Session(Client *client);
|
||||
virtual ~Http1Session();
|
||||
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_write();
|
||||
virtual void terminate();
|
||||
Client *get_client();
|
||||
int32_t stream_req_counter_;
|
||||
int32_t stream_resp_counter_;
|
||||
std::unordered_map<int32_t, RequestStat *> req_stats_;
|
||||
|
||||
private:
|
||||
Client *client_;
|
||||
|
|
|
@ -89,12 +89,25 @@ namespace {
|
|||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||
uint32_t error_code, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||
if (!req_stat) {
|
||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // 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;
|
||||
}
|
||||
client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR, req_stat);
|
||||
|
||||
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;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -108,9 +121,7 @@ int before_frame_send_callback(nghttp2_session *session,
|
|||
}
|
||||
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
client->on_request(frame->hd.stream_id);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
||||
auto req_stat = client->get_req_stat(frame->hd.stream_id);
|
||||
assert(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) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto config = client->worker->config;
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
nghttp2_session_get_stream_user_data(session, stream_id));
|
||||
auto req_stat = client->get_req_stat(stream_id);
|
||||
assert(req_stat);
|
||||
ssize_t nread;
|
||||
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,
|
||||
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(
|
||||
callbacks, before_frame_send_callback);
|
||||
|
||||
|
@ -212,7 +225,7 @@ void Http2Session::on_connect() {
|
|||
client_->signal_write();
|
||||
}
|
||||
|
||||
int Http2Session::submit_request(RequestStat *req_stat) {
|
||||
int Http2Session::submit_request() {
|
||||
if (nghttp2_session_check_request_allowed(session_) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -228,11 +241,13 @@ int Http2Session::submit_request(RequestStat *req_stat) {
|
|||
|
||||
auto stream_id =
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
client_->on_request(stream_id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
Http2Session(Client *client);
|
||||
virtual ~Http2Session();
|
||||
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_write();
|
||||
virtual void terminate();
|
||||
|
|
|
@ -41,7 +41,7 @@ public:
|
|||
// Called when the connection was made.
|
||||
virtual void on_connect() = 0;
|
||||
// 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
|
||||
// return the number of bytes read.
|
||||
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;
|
||||
}
|
||||
client->on_request(frame->syn_stream.stream_id);
|
||||
auto req_stat =
|
||||
static_cast<RequestStat *>(spdylay_session_get_stream_user_data(
|
||||
session, frame->syn_stream.stream_id));
|
||||
auto req_stat = client->get_req_stat(frame->syn_stream.stream_id);
|
||||
client->record_request_time(req_stat);
|
||||
}
|
||||
} // namespace
|
||||
|
@ -104,9 +102,7 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
|
|||
spdylay_status_code status_code,
|
||||
void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
spdylay_session_get_stream_user_data(session, stream_id));
|
||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK, req_stat);
|
||||
client->on_stream_close(stream_id, status_code == SPDYLAY_OK);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -130,8 +126,7 @@ ssize_t file_read_callback(spdylay_session *session, int32_t stream_id,
|
|||
spdylay_data_source *source, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto config = client->worker->config;
|
||||
auto req_stat = static_cast<RequestStat *>(
|
||||
spdylay_session_get_stream_user_data(session, stream_id));
|
||||
auto req_stat = client->get_req_stat(stream_id);
|
||||
|
||||
ssize_t nread;
|
||||
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
|
||||
|
@ -185,7 +180,7 @@ void SpdySession::on_connect() {
|
|||
client_->signal_write();
|
||||
}
|
||||
|
||||
int SpdySession::submit_request(RequestStat *req_stat) {
|
||||
int SpdySession::submit_request() {
|
||||
int rv;
|
||||
auto config = client_->worker->config;
|
||||
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};
|
||||
|
||||
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) {
|
||||
return -1;
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
SpdySession(Client *client, uint16_t spdy_version);
|
||||
virtual ~SpdySession();
|
||||
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_write();
|
||||
virtual void terminate();
|
||||
|
|
|
@ -113,6 +113,8 @@ std::string get_status_string(unsigned int status_code) {
|
|||
return "429 Too Many Requests";
|
||||
case 431:
|
||||
return "431 Request Header Fields Too Large";
|
||||
case 451:
|
||||
return "451 Unavailable For Legal Reasons";
|
||||
case 500:
|
||||
return "500 Internal Server Error";
|
||||
case 501:
|
||||
|
@ -215,6 +217,8 @@ const char *stringify_status(unsigned int status_code) {
|
|||
return "429";
|
||||
case 431:
|
||||
return "431";
|
||||
case 451:
|
||||
return "451";
|
||||
case 500:
|
||||
return "500";
|
||||
case 501:
|
||||
|
|
|
@ -144,6 +144,12 @@ public:
|
|||
// and session is terminated.
|
||||
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.
|
||||
void shutdown() const;
|
||||
|
||||
|
@ -177,7 +183,7 @@ public:
|
|||
generator_cb cb, header_map h = header_map{}) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<session_impl> impl_;
|
||||
std::shared_ptr<session_impl> impl_;
|
||||
};
|
||||
|
||||
// configure |tls_ctx| for client use. Currently, we just set NPN
|
||||
|
|
|
@ -59,6 +59,9 @@ public:
|
|||
// Application must not call this directly.
|
||||
request_impl &impl() const;
|
||||
|
||||
// Returns the remote endpoint of the request
|
||||
const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<request_impl> impl_;
|
||||
};
|
||||
|
@ -195,12 +198,22 @@ public:
|
|||
// connections.
|
||||
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
|
||||
void stop();
|
||||
|
||||
// Join on http2 server and wait for it to fully stop
|
||||
void join();
|
||||
|
||||
// Get access to the io_service objects.
|
||||
const std::vector<std::shared_ptr<boost::asio::io_service>> &
|
||||
io_services() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<http2_impl> impl_;
|
||||
};
|
||||
|
|
|
@ -164,6 +164,8 @@ Options:
|
|||
Path to file that contains MIME media types and the
|
||||
extensions that represent them.
|
||||
Default: )" << config.mime_types_file << R"(
|
||||
--no-content-length
|
||||
Don't send content-length header field.
|
||||
--version Display version information and exit.
|
||||
-h, --help Display this help and exit.
|
||||
|
||||
|
@ -209,6 +211,7 @@ int main(int argc, char **argv) {
|
|||
{"hexdump", no_argument, &flag, 7},
|
||||
{"echo-upload", no_argument, &flag, 8},
|
||||
{"mime-types-file", required_argument, &flag, 9},
|
||||
{"no-content-length", no_argument, &flag, 10},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
int option_index = 0;
|
||||
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;
|
||||
config.mime_types_file = optarg;
|
||||
break;
|
||||
case 10:
|
||||
// no-content-length option
|
||||
config.no_content_length = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -1610,14 +1610,15 @@ HTTP:
|
|||
used several times to specify multiple header fields.
|
||||
Example: --add-response-header="foo: bar"
|
||||
--header-field-buffer=<SIZE>
|
||||
Set maximum buffer size for incoming HTTP header field
|
||||
list. This is the sum of header name and value in
|
||||
Set maximum buffer size for incoming HTTP request header
|
||||
field list. This is the sum of header name and value in
|
||||
bytes.
|
||||
Default: )"
|
||||
<< util::utos_with_unit(get_config()->header_field_buffer) << R"(
|
||||
--max-header-fields=<N>
|
||||
Set maximum number of incoming HTTP header fields, which
|
||||
appear in one request or response header field list.
|
||||
Set maximum number of incoming HTTP request header
|
||||
fields, which appear in one request or response header
|
||||
field list.
|
||||
Default: )" << get_config()->max_header_fields << R"(
|
||||
|
||||
Debug:
|
||||
|
|
|
@ -44,4 +44,8 @@
|
|||
|
||||
#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
|
||||
|
|
|
@ -718,6 +718,34 @@ void ClientHandler::direct_http2_upgrade() {
|
|||
|
||||
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -729,11 +757,11 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
|||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||
write_ = &ClientHandler::write_clear;
|
||||
|
||||
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
||||
"\r\n";
|
||||
upstream->get_response_buf()->write(res, sizeof(res) - 1);
|
||||
auto nread =
|
||||
downstream->get_response_buf()->remove(output->last, output->wleft());
|
||||
output->write(nread);
|
||||
|
||||
output->write(res, str_size(res));
|
||||
upstream_ = std::move(upstream);
|
||||
|
||||
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) {
|
||||
// We skip possible ":" at the start of optarg.
|
||||
const auto *colon = strchr(optarg + 1, ':');
|
||||
const auto *colon = strchr(optarg, ':');
|
||||
|
||||
// name = ":" is not allowed
|
||||
if (colon == nullptr || (optarg[0] == ':' && colon == optarg + 1)) {
|
||||
if (colon == nullptr || colon == optarg) {
|
||||
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),
|
||||
std::string(value, strlen(value)));
|
||||
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;
|
||||
}
|
||||
|
@ -1800,7 +1805,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
|
||||
auto p = parse_header(optarg);
|
||||
if (p.first.empty()) {
|
||||
LOG(ERROR) << opt << ": header field name is empty: " << optarg;
|
||||
LOG(ERROR) << opt << ": invalid header field: " << optarg;
|
||||
return -1;
|
||||
}
|
||||
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
|
||||
|
|
|
@ -46,8 +46,7 @@ void test_shrpx_config_parse_header(void) {
|
|||
CU_ASSERT("b" == p.second);
|
||||
|
||||
p = parse_header(":a: b");
|
||||
CU_ASSERT(":a" == p.first);
|
||||
CU_ASSERT("b" == p.second);
|
||||
CU_ASSERT(p.first.empty());
|
||||
|
||||
p = parse_header("a: :b");
|
||||
CU_ASSERT("a" == p.first);
|
||||
|
@ -59,6 +58,12 @@ void test_shrpx_config_parse_header(void) {
|
|||
p = parse_header("alpha: bravo charlie");
|
||||
CU_ASSERT("alpha" == p.first);
|
||||
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) {
|
||||
|
|
|
@ -757,26 +757,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||
!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) {
|
||||
// just store header fields for trailer part
|
||||
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);
|
||||
|
||||
if (promised_downstream->get_request_headers_sum() + namelen + valuelen >
|
||||
get_config()->header_field_buffer ||
|
||||
promised_downstream->get_request_headers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, promised_downstream)
|
||||
<< "Too large or many header field size="
|
||||
<< promised_downstream->get_request_headers_sum() + namelen +
|
||||
valuelen << ", num="
|
||||
<< promised_downstream->get_request_headers().size() + 1;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
promised_downstream->add_request_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
||||
|
|
|
@ -169,7 +169,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
if (downstream->get_request_headers_sum() + namelen + valuelen >
|
||||
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) {
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return 0;
|
||||
|
|
|
@ -562,29 +562,10 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
namespace {
|
||||
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||
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_header_key_prev()) {
|
||||
downstream->append_last_response_header_key(data, len);
|
||||
} 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), "");
|
||||
}
|
||||
} 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()) {
|
||||
downstream->append_last_response_trailer_key(data, len);
|
||||
} 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), "");
|
||||
}
|
||||
}
|
||||
|
@ -611,14 +583,6 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
|||
namespace {
|
||||
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||
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_header_key_prev()) {
|
||||
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()) {
|
||||
downstream->append_last_request_trailer_key(data, len);
|
||||
} else {
|
||||
if (downstream->get_request_headers().size() >=
|
||||
if (downstream->get_request_headers().size() +
|
||||
downstream->get_request_trailers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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() &&
|
||||
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) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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]);
|
||||
}
|
||||
|
||||
// spdy does not define usage of trailer fields, and we ignores
|
||||
// them.
|
||||
if (header_buffer > get_config()->header_field_buffer ||
|
||||
num_headers > get_config()->max_header_fields) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/dh.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(); }
|
||||
|
||||
const char *get_tls_protocol(SSL *ssl) {
|
||||
auto session = SSL_get_session(ssl);
|
||||
if (!session) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
switch (session->ssl_version) {
|
||||
switch (SSL_version(ssl)) {
|
||||
case SSL2_VERSION:
|
||||
return "SSLv2";
|
||||
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->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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
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) ||
|
||||
!CU_add_test(pSuite, "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",
|
||||
test_nghttp2_session_recv_premature_headers) ||
|
||||
!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) ||
|
||||
!CU_add_test(pSuite, "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",
|
||||
test_nghttp2_session_send_headers_start_stream) ||
|
||||
!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) ||
|
||||
!CU_add_test(pSuite, "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",
|
||||
test_nghttp2_session_stop_data_with_rst_stream) ||
|
||||
!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_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",
|
||||
test_nghttp2_http_mandatory_headers) ||
|
||||
!CU_add_test(pSuite, "http_content_length",
|
||||
|
|
|
@ -342,16 +342,6 @@ static int send_data_callback(nghttp2_session *session _U_,
|
|||
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_,
|
||||
const uint8_t *data _U_, size_t len,
|
||||
int flags _U_, void *user_data) {
|
||||
|
@ -1487,6 +1477,58 @@ void test_nghttp2_session_recv_headers_early_response(void) {
|
|||
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) {
|
||||
nghttp2_session *session;
|
||||
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_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) {
|
||||
|
@ -2822,14 +2883,20 @@ void test_nghttp2_session_on_push_promise_received(void) {
|
|||
CU_ASSERT(1 == session->num_incoming_reserved_streams);
|
||||
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
|
||||
CU_ASSERT(4 == item->frame.hd.stream_id);
|
||||
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.rst_stream.error_code);
|
||||
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||
CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code);
|
||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||
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 */
|
||||
stream->shut_flags = NGHTTP2_SHUT_NONE;
|
||||
stream->state = NGHTTP2_STREAM_CLOSING;
|
||||
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));
|
||||
|
||||
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));
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
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(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.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));
|
||||
|
||||
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));
|
||||
item = nghttp2_session_get_next_ob_item(session);
|
||||
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
|
||||
|
@ -3241,6 +3308,53 @@ void test_nghttp2_session_on_data_received(void) {
|
|||
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) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
|
@ -3984,6 +4098,7 @@ void test_nghttp2_submit_request_without_data(void) {
|
|||
nva_out out;
|
||||
nghttp2_bufs bufs;
|
||||
nghttp2_mem *mem;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
|
||||
mem = nghttp2_mem_default();
|
||||
frame_pack_bufs_init(&bufs);
|
||||
|
@ -4017,6 +4132,15 @@ void test_nghttp2_submit_request_without_data(void) {
|
|||
|
||||
nghttp2_bufs_free(&bufs);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -4308,6 +4432,7 @@ void test_nghttp2_submit_headers(void) {
|
|||
nva_out out;
|
||||
nghttp2_bufs bufs;
|
||||
nghttp2_mem *mem;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
|
||||
mem = nghttp2_mem_default();
|
||||
frame_pack_bufs_init(&bufs);
|
||||
|
@ -4362,6 +4487,21 @@ void test_nghttp2_submit_headers(void) {
|
|||
nghttp2_frame_headers_free(&frame.headers, mem);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -5318,38 +5458,6 @@ void test_nghttp2_session_max_concurrent_streams(void) {
|
|||
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) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
|
@ -7746,7 +7854,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
|
|||
nghttp2_session_callbacks callbacks;
|
||||
const size_t max_concurrent_streams = 5;
|
||||
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
||||
max_concurrent_streams};
|
||||
(uint32_t)max_concurrent_streams};
|
||||
size_t i;
|
||||
|
||||
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);
|
||||
|
||||
open_stream(session, 11);
|
||||
nghttp2_session_adjust_closed_stream(session);
|
||||
|
||||
CU_ASSERT(1 == session->num_closed_streams);
|
||||
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);
|
||||
|
||||
open_stream(session, 13);
|
||||
nghttp2_session_adjust_closed_stream(session);
|
||||
|
||||
CU_ASSERT(0 == session->num_closed_streams);
|
||||
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 */
|
||||
open_stream(session, 2);
|
||||
nghttp2_session_adjust_closed_stream(session);
|
||||
|
||||
CU_ASSERT(1 == session->num_closed_streams);
|
||||
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;
|
||||
const size_t max_concurrent_streams = 1;
|
||||
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
|
||||
max_concurrent_streams};
|
||||
(uint32_t)max_concurrent_streams};
|
||||
int i;
|
||||
int32_t stream_id;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
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);
|
||||
|
||||
/* We at least allow 2 idle streams even if max concurrent streams
|
||||
is very low. */
|
||||
for (i = 0; i < 2; ++i) {
|
||||
/* We at least allow NGHTTP2_MIN_IDLE_STREAM idle streams even if
|
||||
max concurrent streams is very low. */
|
||||
for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) {
|
||||
nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE,
|
||||
&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(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,
|
||||
NGHTTP2_STREAM_IDLE, NULL);
|
||||
stream_id += 2;
|
||||
|
||||
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(5 == session->idle_stream_tail->stream_id);
|
||||
CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
|
||||
|
||||
nghttp2_session_del(session);
|
||||
}
|
||||
|
@ -8639,7 +8755,7 @@ void test_nghttp2_session_flooding(void) {
|
|||
void test_nghttp2_session_change_stream_priority(void) {
|
||||
nghttp2_session *session;
|
||||
nghttp2_session_callbacks callbacks;
|
||||
nghttp2_stream *stream1, *stream2, *stream3;
|
||||
nghttp2_stream *stream1, *stream2, *stream3, *stream5;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int rv;
|
||||
|
||||
|
@ -8672,6 +8788,41 @@ void test_nghttp2_session_change_stream_priority(void) {
|
|||
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -8681,8 +8832,10 @@ void test_nghttp2_session_create_idle_stream(void) {
|
|||
nghttp2_stream *stream2, *stream4, *stream8, *stream10;
|
||||
nghttp2_priority_spec pri_spec;
|
||||
int rv;
|
||||
int i;
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.send_callback = null_send_callback;
|
||||
|
||||
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);
|
||||
|
||||
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(
|
||||
|
|
|
@ -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_headers_with_priority(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_unknown_frame(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_window_update_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_reply(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_reply_fail(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_defer_data(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_change_stream_priority(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_content_length(void);
|
||||
void test_nghttp2_http_content_length_mismatch(void);
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 81eff20bd84b4d0dce2cbbd1a5ad1384d086423b
|
||||
Subproject commit 5c47587bc2855f2b9577a9bd369ed70088b77fec
|
Loading…
Reference in New Issue