Merge branch 'master' into simple-extensions

This commit is contained in:
Tatsuhiro Tsujikawa 2016-01-09 19:08:28 +09:00
commit 0caefe20ef
70 changed files with 1565 additions and 640 deletions

View File

@ -1,6 +1,6 @@
The MIT License The MIT License
Copyright (c) 2012, 2014, 2015 Tatsuhiro Tsujikawa Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

1
LICENSE Normal file
View File

@ -0,0 +1 @@
See COPYING

View File

@ -274,7 +274,7 @@ for this 24 bytes byte string and updated API.
``NGHTTP2_CLIENT_MAGIC_LEN``. ``NGHTTP2_CLIENT_MAGIC_LEN``.
* ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC`` * ``NGHTTP2_BAD_PREFACE`` was renamed as ``NGHTTP2_BAD_CLIENT_MAGIC``
The alreay deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and The already deprecated ``NGHTTP2_CLIENT_CONNECTION_HEADER`` and
``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed. ``NGHTTP2_CLIENT_CONNECTION_HEADER_LEN`` were removed.
If application uses these macros, just replace old ones with new ones. If application uses these macros, just replace old ones with new ones.

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.5.1-DEV], [t-tujikawa@users.sourceforge.net]) AC_INIT([nghttp2], [1.6.1-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.]) AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
@ -46,9 +46,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule: dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 17) AC_SUBST(LT_CURRENT, 18)
AC_SUBST(LT_REVISION, 0) AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_AGE, 3) AC_SUBST(LT_AGE, 4)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"` minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
@ -104,6 +104,11 @@ AC_ARG_ENABLE([failmalloc],
[Do not build failmalloc test program])], [Do not build failmalloc test program])],
[request_failmalloc=$enableval], [request_failmalloc=yes]) [request_failmalloc=$enableval], [request_failmalloc=yes])
AC_ARG_ENABLE([lib-only],
[AS_HELP_STRING([--enable-lib-only],
[Build libnghttp2 only. This is a short hand for --disable-app --disable-examples --disable-hpack-tools --disable-python-bindings])],
[request_lib_only=$enableval], [request_lib_only=no])
AC_ARG_WITH([libxml2], AC_ARG_WITH([libxml2],
[AS_HELP_STRING([--with-libxml2], [AS_HELP_STRING([--with-libxml2],
[Use libxml2 [default=check]])], [Use libxml2 [default=check]])],
@ -150,6 +155,13 @@ PKG_PROG_PKG_CONFIG([0.20])
AM_PATH_PYTHON([2.7],, [:]) AM_PATH_PYTHON([2.7],, [:])
if [test "x$request_lib_only" = "xyes"]; then
request_app=no
request_hpack_tools=no
request_examples=no
request_python_bindings=no
fi
if [test "x$request_python_bindings" != "xno"]; then if [test "x$request_python_bindings" != "xno"]; then
AX_PYTHON_DEVEL([>= '2.7']) AX_PYTHON_DEVEL([>= '2.7'])
fi fi
@ -243,6 +255,11 @@ if test "x${have_zlib}" = "xno"; then
AC_MSG_NOTICE($ZLIB_PKG_ERRORS) AC_MSG_NOTICE($ZLIB_PKG_ERRORS)
fi fi
# dl: openssl requires libdl when it is statically linked.
LIBS_OLD=$LIBS
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
LIBS=$LIBS_OLD
# cunit # cunit
PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no]) PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
# If pkg-config does not find cunit, check it using AC_CHECK_LIB. We # If pkg-config does not find cunit, check it using AC_CHECK_LIB. We
@ -613,6 +630,10 @@ AC_CHECK_FUNCS([ \
AC_CHECK_FUNC([timerfd_create], AC_CHECK_FUNC([timerfd_create],
[have_timerfd_create=yes], [have_timerfd_create=no]) [have_timerfd_create=yes], [have_timerfd_create=no])
# For cygwin: we can link initgroups, so AC_CHECK_FUNCS succeeds, but
# cygwin disables initgroups due to feature test macro magic with our
# configuration.
AC_CHECK_DECLS([initgroups], [], [], [[#include <grp.h>]])
# Checks for epoll availability, primarily for examples/tiny-nghttpd # Checks for epoll availability, primarily for examples/tiny-nghttpd
AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no]) AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])

View File

@ -66,7 +66,7 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = u'nghttp2' project = u'nghttp2'
copyright = u'2012, 2015, Tatsuhiro Tsujikawa' copyright = u'2012, 2015, 2016, Tatsuhiro Tsujikawa'
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "H2LOAD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2" .TH "H2LOAD" "1" "December 23, 2015" "1.6.0" "nghttp2"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
. .
@ -374,6 +374,28 @@ The fraction of the number of connections within standard
deviation range (mean +/\- sd) against total number of successful deviation range (mean +/\- sd) against total number of successful
connections. connections.
.UNINDENT .UNINDENT
.TP
.B req/s
.INDENT 7.0
.TP
.B min
The minimum request per second among all clients.
.TP
.B max
The maximum request per second among all clients.
.TP
.B mean
The mean request per second among all clients.
.TP
.B sd
The standard deviation of request per second among all clients.
server.
.TP
.B +/\- sd
The fraction of the number of connections within standard
deviation range (mean +/\- sd) against total number of successful
connections.
.UNINDENT
.UNINDENT .UNINDENT
.SH FLOW CONTROL .SH FLOW CONTROL
.sp .sp

View File

@ -213,6 +213,8 @@ is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
(hours, minutes, seconds and milliseconds, respectively). If a unit (hours, minutes, seconds and milliseconds, respectively). If a unit
is omitted, a second is used as unit. is omitted, a second is used as unit.
.. _h2load-1-output:
OUTPUT OUTPUT
------ ------
@ -301,6 +303,21 @@ time for 1st byte (of (decrypted in case of TLS) application data)
deviation range (mean +/- sd) against total number of successful deviation range (mean +/- sd) against total number of successful
connections. connections.
req/s
min
The minimum request per second among all clients.
max
The maximum request per second among all clients.
mean
The mean request per second among all clients.
sd
The standard deviation of request per second among all clients.
server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
FLOW CONTROL FLOW CONTROL
------------ ------------

View File

@ -1,3 +1,5 @@
.. _h2load-1-output:
OUTPUT OUTPUT
------ ------
@ -86,7 +88,7 @@ time for 1st byte (of (decrypted in case of TLS) application data)
deviation range (mean +/- sd) against total number of successful deviation range (mean +/- sd) against total number of successful
connections. connections.
req/s (client) req/s
min min
The minimum request per second among all clients. The minimum request per second among all clients.
max max

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTP" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2" .TH "NGHTTP" "1" "December 23, 2015" "1.6.0" "nghttp2"
.SH NAME .SH NAME
nghttp \- HTTP/2 client nghttp \- HTTP/2 client
. .
@ -152,7 +152,8 @@ Default: \fB16\fP
.B \-M, \-\-peer\-max\-concurrent\-streams=<N> .B \-M, \-\-peer\-max\-concurrent\-streams=<N>
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
remote endpoint as if it is received in SETTINGS frame. remote endpoint as if it is received in SETTINGS frame.
The default is large enough as it is seen as unlimited. .sp
Default: \fB100\fP
.UNINDENT .UNINDENT
.INDENT 0.0 .INDENT 0.0
.TP .TP

View File

@ -116,7 +116,8 @@ OPTIONS
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
remote endpoint as if it is received in SETTINGS frame. remote endpoint as if it is received in SETTINGS frame.
The default is large enough as it is seen as unlimited.
Default: ``100``
.. option:: -c, --header-table-size=<SIZE> .. option:: -c, --header-table-size=<SIZE>

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2" .TH "NGHTTPD" "1" "December 23, 2015" "1.6.0" "nghttp2"
.SH NAME .SH NAME
nghttpd \- HTTP/2 server nghttpd \- HTTP/2 server
. .

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText. .\" Man page generated from reStructuredText.
. .
.TH "NGHTTPX" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2" .TH "NGHTTPX" "1" "December 23, 2015" "1.6.0" "nghttp2"
.SH NAME .SH NAME
nghttpx \- HTTP/2 proxy nghttpx \- HTTP/2 proxy
. .

View File

@ -1,10 +1,10 @@
h2load - HTTP/2 benchmarking tool - HOW-TO h2load - HTTP/2 benchmarking tool - HOW-TO
========================================== ==========================================
h2load is benchmarking tool for HTTP/2. If built with h2load is benchmarking tool for HTTP/2 and HTTP/1.1. If built with
spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also
supports SPDY protocol. It supports SSL/TLS and clear text for both supports SPDY protocol. It supports SSL/TLS and clear text for all
HTTP/2 and SPDY. supported protocols.
Basic Usage Basic Usage
----------- -----------
@ -22,29 +22,37 @@ In order to set benchmark settings, specify following 3 options.
If ``auto`` is given, the number of given URIs is used. If ``auto`` is given, the number of given URIs is used.
Default: ``auto`` Default: ``auto``
For SSL/TLS connection, the protocol will be negotiated via ALPN/NPN.
You can set specific protocols in ``--npn-list`` option. For
cleartext connection, the default protocol is HTTP/2. To change the
protocol in cleartext connection, use ``--no-tls-proto`` option. For
convenience, ``--h1`` option forces HTTP/1.1 for both cleartext and
SSL/TLS connections.
Here is a command-line to perform benchmark to URI \https://localhost Here is a command-line to perform benchmark to URI \https://localhost
using total 100000 requests, 100 concurrent clients and 10 max using total 100000 requests, 100 concurrent clients and 10 max
concurrent streams:: concurrent streams:
.. code-block:: text
$ h2load -n100000 -c100 -m10 https://localhost $ h2load -n100000 -c100 -m10 https://localhost
The benchmarking result looks like this:: The benchmarking result looks like this:
finished in 0 sec, 385 millisec and 851 microsec, 2591 req/s, 1689 kbytes/s .. code-block:: text
requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored
status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 667500 bytes total, 28700 bytes headers, 612000 bytes data
The number of ``failed`` is the number of requests returned with non finished in 7.08s, 141164.80 req/s, 555.33MB/s
2xx status. The number of ``error`` is the number of ``failed`` plus requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
the number of requests which failed with connection error. status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 4125025824 bytes total, 11023424 bytes headers (space savings 93.07%), 4096000000 bytes data
min max mean sd +/- sd
time for request: 15.31ms 146.85ms 69.78ms 9.26ms 92.43%
time for connect: 1.08ms 25.04ms 10.71ms 9.80ms 64.00%
time to 1st byte: 25.36ms 184.96ms 79.11ms 53.97ms 78.00%
req/s (client) : 1412.04 1447.84 1426.52 10.57 63.00%
The number of ``total`` in ``traffic`` is the received application See the h2load manual page :ref:`h2load-1-output` section for the
data. If SSL/TLS is used, this number is calculated after decryption. explanation of the above numbers.
The number of ``headers`` is the sum of payload size of response
HEADERS (or SYN_REPLY for SPDY). This number comes before
decompressing header block. The number of ``data`` is the sum of
response body.
Flow Control Flow Control
------------ ------------

View File

@ -3296,6 +3296,9 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was * No stream ID is available because maximum stream ID was
* reached. * reached.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* Trying to depend on itself (new stream ID equals
* ``pri_spec->stream_id``).
* *
* .. warning:: * .. warning::
* *
@ -3407,7 +3410,7 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
* has already sent and if `nghttp2_submit_trailer()` is called before * has already sent and if `nghttp2_submit_trailer()` is called before
* any response HEADERS submission (usually by * any response HEADERS submission (usually by
* `nghttp2_submit_response()`), the content of |nva| will be sent as * `nghttp2_submit_response()`), the content of |nva| will be sent as
* reponse headers, which will result in error. * response headers, which will result in error.
* *
* This function has the same effect with `nghttp2_submit_headers()`, * This function has the same effect with `nghttp2_submit_headers()`,
* with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and * with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and
@ -3506,7 +3509,8 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
* No stream ID is available because maximum stream ID was * No stream ID is available because maximum stream ID was
* reached. * reached.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0. * The |stream_id| is 0; or trying to depend on itself (stream ID
* equals ``pri_spec->stream_id``).
* :enum:`NGHTTP2_ERR_DATA_EXIST` * :enum:`NGHTTP2_ERR_DATA_EXIST`
* DATA or HEADERS has been already submitted and not fully * DATA or HEADERS has been already submitted and not fully
* processed yet. This happens if stream denoted by |stream_id| * processed yet. This happens if stream denoted by |stream_id|
@ -3549,7 +3553,7 @@ nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0. * The |stream_id| is 0.
* :enum:`NGHTTP2_ERR_STREAM_CLOSED` * :enum:`NGHTTP2_ERR_STREAM_CLOSED`
* The stream was alreay closed; or the |stream_id| is invalid. * The stream was already closed; or the |stream_id| is invalid.
* *
* .. note:: * .. note::
* *
@ -3718,7 +3722,7 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
* The |stream_id| is 0; The |stream_id| does not designate stream * The |stream_id| is 0; The |stream_id| does not designate stream
* that peer initiated. * that peer initiated.
* :enum:`NGHTTP2_ERR_STREAM_CLOSED` * :enum:`NGHTTP2_ERR_STREAM_CLOSED`
* The stream was alreay closed; or the |stream_id| is invalid. * The stream was already closed; or the |stream_id| is invalid.
* *
* .. warning:: * .. warning::
* *
@ -4336,7 +4340,7 @@ typedef enum {
* :enum:`NGHTTP2_ERR_HEADER_COMP` * :enum:`NGHTTP2_ERR_HEADER_COMP`
* Inflation process has failed. * Inflation process has failed.
* :enum:`NGHTTP2_ERR_BUFFER_ERROR` * :enum:`NGHTTP2_ERR_BUFFER_ERROR`
* The heder field name or value is too large. * The header field name or value is too large.
* *
* Example follows:: * Example follows::
* *

View File

@ -43,9 +43,6 @@ typedef struct {
/* nonzero if request HEADERS is canceled. The error code is stored /* nonzero if request HEADERS is canceled. The error code is stored
in |error_code|. */ in |error_code|. */
uint8_t canceled; uint8_t canceled;
/* nonzero if this item should be attached to stream object to make
it under priority control */
uint8_t attach_stream;
} nghttp2_headers_aux_data; } nghttp2_headers_aux_data;
/* struct used for DATA frame */ /* struct used for DATA frame */

View File

@ -44,11 +44,13 @@ void nghttp2_pq_free(nghttp2_pq *pq) {
} }
static void swap(nghttp2_pq *pq, size_t i, size_t j) { static void swap(nghttp2_pq *pq, size_t i, size_t j) {
nghttp2_pq_entry *t = pq->q[i]; nghttp2_pq_entry *a = pq->q[i];
pq->q[i] = pq->q[j]; nghttp2_pq_entry *b = pq->q[j];
pq->q[i]->index = i;
pq->q[j] = t; pq->q[i] = b;
pq->q[j]->index = j; b->index = i;
pq->q[j] = a;
a->index = j;
} }
static void bubble_up(nghttp2_pq *pq, size_t index) { static void bubble_up(nghttp2_pq *pq, size_t index) {

View File

@ -635,7 +635,7 @@ int nghttp2_session_reprioritize_stream(
if (pri_spec->stream_id != 0) { if (pri_spec->stream_id != 0) {
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
if (session->server && !dep_stream && if (!dep_stream &&
session_detect_idle_stream(session, pri_spec->stream_id)) { session_detect_idle_stream(session, pri_spec->stream_id)) {
nghttp2_priority_spec_default_init(&pri_spec_default); nghttp2_priority_spec_default_init(&pri_spec_default);
@ -671,15 +671,9 @@ int nghttp2_session_reprioritize_stream(
assert(dep_stream); assert(dep_stream);
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) { if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
/* This is minor optimization when just weight is changed. /* This is minor optimization when just weight is changed. */
Currently, we don't reschedule stream in this case, since we nghttp2_stream_change_weight(stream, pri_spec->weight);
don't retain enough information to do that
(descendant_last_cycle we used to schedule it). This means new
weight is only applied in the next scheduling, and if weight is
drastically increased, library is not responding very quickly.
If this is really an issue, we will do workaround for this. */
dep_stream->sum_dep_weight += pri_spec->weight - stream->weight;
stream->weight = pri_spec->weight;
return 0; return 0;
} }
@ -729,20 +723,6 @@ int nghttp2_session_add_item(nghttp2_session *session,
break; break;
} }
if (stream && item->aux_data.headers.attach_stream) {
if (stream->item) {
return NGHTTP2_ERR_DATA_EXIST;
}
rv = nghttp2_stream_attach_item(stream, item);
if (rv != 0) {
return rv;
}
break;
}
nghttp2_outbound_queue_push(&session->ob_reg, item); nghttp2_outbound_queue_push(&session->ob_reg, item);
item->queued = 1; item->queued = 1;
break; break;
@ -778,6 +758,10 @@ int nghttp2_session_add_item(nghttp2_session *session,
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
/* We don't have to call nghttp2_session_adjust_closed_stream()
here, since stream->stream_id is local stream_id, and it does
not affect closed stream count. */
nghttp2_outbound_queue_push(&session->ob_reg, item); nghttp2_outbound_queue_push(&session->ob_reg, item);
item->queued = 1; item->queued = 1;
@ -909,15 +893,6 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
return NULL; return NULL;
} }
} else { } else {
if (session->server && initial_state != NGHTTP2_STREAM_IDLE &&
!nghttp2_session_is_my_stream_id(session, stream_id)) {
rv = nghttp2_session_adjust_closed_stream(session, 1);
if (rv != 0) {
return NULL;
}
}
stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream)); stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
if (stream == NULL) { if (stream == NULL) {
return NULL; return NULL;
@ -929,7 +904,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
if (pri_spec->stream_id != 0) { if (pri_spec->stream_id != 0) {
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
if (session->server && !dep_stream && if (!dep_stream &&
session_detect_idle_stream(session, pri_spec->stream_id)) { session_detect_idle_stream(session, pri_spec->stream_id)) {
/* Depends on idle stream, which does not exist in memory. /* Depends on idle stream, which does not exist in memory.
Assign default priority for it. */ Assign default priority for it. */
@ -994,11 +969,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
case NGHTTP2_STREAM_IDLE: case NGHTTP2_STREAM_IDLE:
/* Idle stream does not count toward the concurrent streams limit. /* Idle stream does not count toward the concurrent streams limit.
This is used as anchor node in dependency tree. */ This is used as anchor node in dependency tree. */
assert(session->server); nghttp2_session_keep_idle_stream(session, stream);
rv = nghttp2_session_keep_idle_stream(session, stream);
if (rv != 0) {
return NULL;
}
break; break;
default: default:
if (nghttp2_session_is_my_stream_id(session, stream_id)) { if (nghttp2_session_is_my_stream_id(session, stream_id)) {
@ -1103,7 +1074,9 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
/* On server side, retain stream at most MAX_CONCURRENT_STREAMS /* On server side, retain stream at most MAX_CONCURRENT_STREAMS
combined with the current active incoming streams to make combined with the current active incoming streams to make
dependency tree work better. */ dependency tree work better. */
rv = nghttp2_session_keep_closed_stream(session, stream); nghttp2_session_keep_closed_stream(session, stream);
rv = nghttp2_session_adjust_closed_stream(session);
} else { } else {
rv = nghttp2_session_destroy_stream(session, stream); rv = nghttp2_session_destroy_stream(session, stream);
} }
@ -1138,10 +1111,8 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
return 0; return 0;
} }
int nghttp2_session_keep_closed_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session,
nghttp2_stream *stream) { nghttp2_stream *stream) {
int rv;
DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n", DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
stream, stream->stream_id, stream->state)); stream, stream->stream_id, stream->state));
@ -1154,19 +1125,10 @@ int nghttp2_session_keep_closed_stream(nghttp2_session *session,
session->closed_stream_tail = stream; session->closed_stream_tail = stream;
++session->num_closed_streams; ++session->num_closed_streams;
rv = nghttp2_session_adjust_closed_stream(session, 0);
if (rv != 0) {
return rv;
}
return 0;
} }
int nghttp2_session_keep_idle_stream(nghttp2_session *session, void nghttp2_session_keep_idle_stream(nghttp2_session *session,
nghttp2_stream *stream) { nghttp2_stream *stream) {
int rv;
DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream, DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream,
stream->stream_id, stream->state)); stream->stream_id, stream->state));
@ -1179,13 +1141,6 @@ int nghttp2_session_keep_idle_stream(nghttp2_session *session,
session->idle_stream_tail = stream; session->idle_stream_tail = stream;
++session->num_idle_streams; ++session->num_idle_streams;
rv = nghttp2_session_adjust_idle_stream(session);
if (rv != 0) {
return rv;
}
return 0;
} }
void nghttp2_session_detach_idle_stream(nghttp2_session *session, void nghttp2_session_detach_idle_stream(nghttp2_session *session,
@ -1216,8 +1171,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
--session->num_idle_streams; --session->num_idle_streams;
} }
int nghttp2_session_adjust_closed_stream(nghttp2_session *session, int nghttp2_session_adjust_closed_stream(nghttp2_session *session) {
size_t offset) {
size_t num_stream_max; size_t num_stream_max;
int rv; int rv;
@ -1231,7 +1185,7 @@ int nghttp2_session_adjust_closed_stream(nghttp2_session *session,
num_stream_max)); num_stream_max));
while (session->num_closed_streams > 0 && while (session->num_closed_streams > 0 &&
session->num_closed_streams + session->num_incoming_streams + offset > session->num_closed_streams + session->num_incoming_streams >
num_stream_max) { num_stream_max) {
nghttp2_stream *head_stream; nghttp2_stream *head_stream;
nghttp2_stream *next; nghttp2_stream *next;
@ -1267,12 +1221,10 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
size_t max; size_t max;
int rv; int rv;
/* Make minimum number of idle streams 2 so that allocating 2 /* Make minimum number of idle streams 16, which is arbitrary chosen
streams at once is easy. This happens when PRIORITY frame to number. */
idle stream, which depends on idle stream which does not max = nghttp2_max(16,
exist. */ nghttp2_min(session->local_settings.max_concurrent_streams,
max =
nghttp2_max(2, nghttp2_min(session->local_settings.max_concurrent_streams,
session->pending_local_max_concurrent_stream)); session->pending_local_max_concurrent_stream));
DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams " DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
@ -1331,6 +1283,15 @@ int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
return 0; return 0;
} }
/*
* Returns nonzero if local endpoint allows reception of new stream
* from remote.
*/
static int session_allow_incoming_new_stream(nghttp2_session *session) {
return (session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_SENT)) == 0;
}
/* /*
* This function returns nonzero if session is closing. * This function returns nonzero if session is closing.
*/ */
@ -1850,6 +1811,9 @@ static int session_prep_frame(nghttp2_session *session,
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
/* We don't call nghttp2_session_adjust_closed_stream() here,
since we don't keep closed stream in client side */
estimated_payloadlen = session_estimate_headers_payload( estimated_payloadlen = session_estimate_headers_payload(
session, frame->headers.nva, frame->headers.nvlen, session, frame->headers.nva, frame->headers.nvlen,
NGHTTP2_PRIORITY_SPECLEN); NGHTTP2_PRIORITY_SPECLEN);
@ -1899,7 +1863,7 @@ static int session_prep_frame(nghttp2_session *session,
} }
if (rv != 0) { if (rv != 0) {
// If stream was alreay closed, nghttp2_session_get_stream() // If stream was already closed, nghttp2_session_get_stream()
// returns NULL, but item is still attached to the stream. // returns NULL, but item is still attached to the stream.
// Search stream including closed again. // Search stream including closed again.
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
@ -2061,7 +2025,7 @@ static int session_prep_frame(nghttp2_session *session,
rv = nghttp2_session_predicate_data_send(session, stream); rv = nghttp2_session_predicate_data_send(session, stream);
if (rv != 0) { if (rv != 0) {
// If stream was alreay closed, nghttp2_session_get_stream() // If stream was already closed, nghttp2_session_get_stream()
// returns NULL, but item is still attached to the stream. // returns NULL, but item is still attached to the stream.
// Search stream including closed again. // Search stream including closed again.
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
@ -2442,11 +2406,25 @@ static int session_after_frame_sent1(nghttp2_session *session) {
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if (!stream) { if (!stream) {
if (!session_detect_idle_stream(session, frame->hd.stream_id)) {
break; break;
} }
stream = nghttp2_session_open_stream(
session, frame->hd.stream_id, NGHTTP2_FLAG_NONE,
&frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL);
if (!stream) {
return NGHTTP2_ERR_NOMEM;
}
} else {
rv = nghttp2_session_reprioritize_stream(session, stream, rv = nghttp2_session_reprioritize_stream(session, stream,
&frame->priority.pri_spec); &frame->priority.pri_spec);
if (nghttp2_is_fatal(rv)) {
return rv;
}
}
rv = nghttp2_session_adjust_idle_stream(session);
if (nghttp2_is_fatal(rv)) { if (nghttp2_is_fatal(rv)) {
return rv; return rv;
@ -2612,6 +2590,8 @@ static int session_after_frame_sent2(nghttp2_session *session) {
nghttp2_bufs *framebufs = &aob->framebufs; nghttp2_bufs *framebufs = &aob->framebufs;
nghttp2_frame *frame; nghttp2_frame *frame;
nghttp2_mem *mem; nghttp2_mem *mem;
nghttp2_stream *stream;
nghttp2_data_aux_data *aux_data;
mem = &session->mem; mem = &session->mem;
frame = &item->frame; frame = &item->frame;
@ -2634,9 +2614,9 @@ static int session_after_frame_sent2(nghttp2_session *session) {
active_outbound_item_reset(&session->aob, mem); active_outbound_item_reset(&session->aob, mem);
return 0; return 0;
} else { }
nghttp2_stream *stream;
nghttp2_data_aux_data *aux_data; /* DATA frame */
aux_data = &item->aux_data.data; aux_data = &item->aux_data.data;
@ -2674,10 +2654,6 @@ static int session_after_frame_sent2(nghttp2_session *session) {
aob->item = NULL; aob->item = NULL;
active_outbound_item_reset(&session->aob, mem); active_outbound_item_reset(&session->aob, mem);
return 0;
}
/* Unreachable */
assert(0);
return 0; return 0;
} }
@ -2722,6 +2698,14 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
aob = &session->aob; aob = &session->aob;
framebufs = &aob->framebufs; framebufs = &aob->framebufs;
/* We may have idle streams more than we expect (e.g.,
nghttp2_session_change_stream_priority() or
nghttp2_session_create_idle_stream()). Adjust them here. */
rv = nghttp2_session_adjust_idle_stream(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
*data_ptr = NULL; *data_ptr = NULL;
for (;;) { for (;;) {
switch (aob->state) { switch (aob->state) {
@ -3519,35 +3503,44 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
return NGHTTP2_ERR_IGN_HEADER_BLOCK; return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} }
assert(session->server);
if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) { if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
/* The spec says if an endpoint receives a HEADERS with invalid /* The spec says if an endpoint receives a HEADERS with invalid
stream ID, it MUST issue connection error with error code stream ID, it MUST issue connection error with error code
PROTOCOL_ERROR. But we could get trailer HEADERS after we have PROTOCOL_ERROR. But we could get trailer HEADERS after we have
sent RST_STREAM to this stream and peer have not received it. sent RST_STREAM to this stream and peer have not received it.
Then connection error is too harsh. It means that we only use Then connection error is too harsh. It means that we only use
connection error if stream ID refers idle stream. OTherwise we connection error if stream ID refers idle stream. Therwise we
just ignore HEADERS for now. */ just ignore HEADERS for now. */
if (session_detect_idle_stream(session, frame->hd.stream_id)) { if (frame->hd.stream_id == 0 ||
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, session, frame, NGHTTP2_ERR_PROTO,
"request HEADERS: invalid stream_id"); "request HEADERS: invalid stream_id");
} }
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
}
return NGHTTP2_ERR_IGN_HEADER_BLOCK; return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} }
session->last_recv_stream_id = frame->hd.stream_id; session->last_recv_stream_id = frame->hd.stream_id;
if (session->goaway_flags & NGHTTP2_GOAWAY_SENT) {
/* We just ignore stream after GOAWAY was queued */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
if (session_is_incoming_concurrent_streams_max(session)) { if (session_is_incoming_concurrent_streams_max(session)) {
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, session, frame, NGHTTP2_ERR_PROTO,
"request HEADERS: max concurrent streams exceeded"); "request HEADERS: max concurrent streams exceeded");
} }
if (!session_allow_incoming_new_stream(session)) {
/* We just ignore stream after GOAWAY was sent */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) { if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) {
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself"); session, frame, NGHTTP2_ERR_PROTO, "request HEADERS: depend on itself");
@ -3564,7 +3557,14 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
if (!stream) { if (!stream) {
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
rv = nghttp2_session_adjust_closed_stream(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
session->last_proc_stream_id = session->last_recv_stream_id; session->last_proc_stream_id = session->last_recv_stream_id;
rv = session_call_on_begin_headers(session, frame); rv = session_call_on_begin_headers(session, frame);
if (rv != 0) { if (rv != 0) {
return rv; return rv;
@ -3590,9 +3590,11 @@ int nghttp2_session_on_response_headers_received(nghttp2_session *session,
If an endpoint receives additional frames for a stream that is If an endpoint receives additional frames for a stream that is
in this state it MUST respond with a stream error (Section in this state it MUST respond with a stream error (Section
5.4.2) of type STREAM_CLOSED. 5.4.2) of type STREAM_CLOSED.
We go further, and make it connection error.
*/ */
return session_inflate_handle_invalid_stream(session, frame, return session_inflate_handle_invalid_connection(
NGHTTP2_ERR_STREAM_CLOSED); session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
} }
stream->state = NGHTTP2_STREAM_OPENED; stream->state = NGHTTP2_STREAM_OPENED;
rv = session_call_on_begin_headers(session, frame); rv = session_call_on_begin_headers(session, frame);
@ -3612,9 +3614,11 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
session, frame, NGHTTP2_ERR_PROTO, session, frame, NGHTTP2_ERR_PROTO,
"push response HEADERS: stream_id == 0"); "push response HEADERS: stream_id == 0");
} }
if (session->goaway_flags) {
/* We don't accept new stream after GOAWAY is sent or received. */ if (session->server) {
return NGHTTP2_ERR_IGN_HEADER_BLOCK; return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO,
"HEADERS: no HEADERS allowed from client in reserved state");
} }
if (session_is_incoming_concurrent_streams_max(session)) { if (session_is_incoming_concurrent_streams_max(session)) {
@ -3622,6 +3626,12 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
session, frame, NGHTTP2_ERR_PROTO, session, frame, NGHTTP2_ERR_PROTO,
"push response HEADERS: max concurrent streams exceeded"); "push response HEADERS: max concurrent streams exceeded");
} }
if (!session_allow_incoming_new_stream(session)) {
/* We don't accept new stream after GOAWAY was sent. */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
if (session_is_incoming_concurrent_streams_pending_max(session)) { if (session_is_incoming_concurrent_streams_pending_max(session)) {
return session_inflate_handle_invalid_stream(session, frame, return session_inflate_handle_invalid_stream(session, frame,
NGHTTP2_ERR_REFUSED_STREAM); NGHTTP2_ERR_REFUSED_STREAM);
@ -3647,23 +3657,17 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0"); session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream_id == 0");
} }
if (stream->state == NGHTTP2_STREAM_RESERVED) {
/* reserved. The valid push response HEADERS is processed by
nghttp2_session_on_push_response_headers_received(). This
generic HEADERS is called invalid cases for HEADERS against
reserved state. */
return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "HEADERS: stream in reserved");
}
if ((stream->shut_flags & NGHTTP2_SHUT_RD)) { if ((stream->shut_flags & NGHTTP2_SHUT_RD)) {
/* half closed (remote): from the spec: /* half closed (remote): from the spec:
If an endpoint receives additional frames for a stream that is If an endpoint receives additional frames for a stream that is
in this state it MUST respond with a stream error (Section in this state it MUST respond with a stream error (Section
5.4.2) of type STREAM_CLOSED. 5.4.2) of type STREAM_CLOSED.
we go further, and make it connection error.
*/ */
return session_inflate_handle_invalid_stream(session, frame, return session_inflate_handle_invalid_connection(
NGHTTP2_ERR_STREAM_CLOSED); session, frame, NGHTTP2_ERR_STREAM_CLOSED, "HEADERS: stream closed");
} }
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
if (stream->state == NGHTTP2_STREAM_OPENED) { if (stream->state == NGHTTP2_STREAM_OPENED) {
@ -3672,15 +3676,9 @@ int nghttp2_session_on_headers_received(nghttp2_session *session,
return rv; return rv;
} }
return 0; return 0;
} else if (stream->state == NGHTTP2_STREAM_CLOSING) {
/* This is race condition. NGHTTP2_STREAM_CLOSING indicates
that we queued RST_STREAM but it has not been sent. It will
eventually sent, so we just ignore this frame. */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} else {
return session_inflate_handle_invalid_stream(session, frame,
NGHTTP2_ERR_PROTO);
} }
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} }
/* If this is remote peer initiated stream, it is OK unless it /* If this is remote peer initiated stream, it is OK unless it
has sent END_STREAM frame already. But if stream is in has sent END_STREAM frame already. But if stream is in
@ -3715,20 +3713,18 @@ static int session_process_headers_frame(nghttp2_session *session) {
return nghttp2_session_on_request_headers_received(session, frame); return nghttp2_session_on_request_headers_received(session, frame);
} }
if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
if (stream->state == NGHTTP2_STREAM_OPENING) {
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
return nghttp2_session_on_response_headers_received(session, frame,
stream);
}
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
return nghttp2_session_on_headers_received(session, frame, stream);
}
if (stream->state == NGHTTP2_STREAM_RESERVED) { if (stream->state == NGHTTP2_STREAM_RESERVED) {
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
return nghttp2_session_on_push_response_headers_received(session, frame, return nghttp2_session_on_push_response_headers_received(session, frame,
stream); stream);
} }
if (stream->state == NGHTTP2_STREAM_OPENING &&
nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
return nghttp2_session_on_response_headers_received(session, frame, stream);
}
frame->headers.cat = NGHTTP2_HCAT_HEADERS; frame->headers.cat = NGHTTP2_HCAT_HEADERS;
return nghttp2_session_on_headers_received(session, frame, stream); return nghttp2_session_on_headers_received(session, frame, stream);
} }
@ -3769,6 +3765,11 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
if (stream == NULL) { if (stream == NULL) {
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
rv = nghttp2_session_adjust_idle_stream(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
} else { } else {
rv = nghttp2_session_reprioritize_stream(session, stream, rv = nghttp2_session_reprioritize_stream(session, stream,
&frame->priority.pri_spec); &frame->priority.pri_spec);
@ -3776,6 +3777,11 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
if (nghttp2_is_fatal(rv)) { if (nghttp2_is_fatal(rv)) {
return rv; return rv;
} }
rv = nghttp2_session_adjust_idle_stream(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
} }
return session_call_on_frame_received(session, frame); return session_call_on_frame_received(session, frame);
@ -3805,6 +3811,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
return session_handle_invalid_connection( return session_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "RST_STREAM: stream in idle"); session, frame, NGHTTP2_ERR_PROTO, "RST_STREAM: stream in idle");
} }
} else {
/* We may use stream->shut_flags for strict error checking. */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
} }
rv = session_call_on_frame_received(session, frame); rv = session_call_on_frame_received(session, frame);
@ -4220,17 +4229,17 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled"); session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: push disabled");
} }
if (session->goaway_flags) {
/* We just dicard PUSH_PROMISE after GOAWAY is sent or
received. */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id"); session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: invalid stream_id");
} }
if (!session_allow_incoming_new_stream(session)) {
/* We just discard PUSH_PROMISE after GOAWAY was sent */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
if (!session_is_new_peer_stream_id(session, if (!session_is_new_peer_stream_id(session,
frame->push_promise.promised_stream_id)) { frame->push_promise.promised_stream_id)) {
/* The spec says if an endpoint receives a PUSH_PROMISE with /* The spec says if an endpoint receives a PUSH_PROMISE with
@ -4251,7 +4260,11 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
return session_inflate_handle_invalid_connection( return session_inflate_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle"); session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
} }
/* Currently, client does not retain closed stream, so we don't
check NGHTTP2_SHUT_RD condition here. */
} }
rv = nghttp2_session_add_rst_stream( rv = nghttp2_session_add_rst_stream(
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL); session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
if (rv != 0) { if (rv != 0) {
@ -4259,25 +4272,13 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
} }
return NGHTTP2_ERR_IGN_HEADER_BLOCK; return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} }
if (stream->shut_flags & NGHTTP2_SHUT_RD) { if (stream->shut_flags & NGHTTP2_SHUT_RD) {
if (session->callbacks.on_invalid_frame_recv_callback) { return session_inflate_handle_invalid_connection(
if (session->callbacks.on_invalid_frame_recv_callback( session, frame, NGHTTP2_ERR_STREAM_CLOSED,
session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data) != "PUSH_PROMISE: stream closed");
0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
rv = nghttp2_session_add_rst_stream(session,
frame->push_promise.promised_stream_id,
NGHTTP2_PROTOCOL_ERROR);
if (rv != 0) {
return rv;
}
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
} }
/* TODO It is unclear reserved stream dpeneds on associated
stream with or without exclusive flag set */
nghttp2_priority_spec_init(&pri_spec, stream->stream_id, nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
NGHTTP2_DEFAULT_WEIGHT, 0); NGHTTP2_DEFAULT_WEIGHT, 0);
@ -4289,6 +4290,9 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
/* We don't call nghttp2_session_adjust_closed_stream(), since we
don't keep closed stream in client side */
session->last_proc_stream_id = session->last_recv_stream_id; session->last_proc_stream_id = session->last_recv_stream_id;
rv = session_call_on_begin_headers(session, frame); rv = session_call_on_begin_headers(session, frame);
if (rv != 0) { if (rv != 0) {
@ -4392,8 +4396,9 @@ session_on_connection_window_update_received(nghttp2_session *session,
nghttp2_frame *frame) { nghttp2_frame *frame) {
/* Handle connection-level flow control */ /* Handle connection-level flow control */
if (frame->window_update.window_size_increment == 0) { if (frame->window_update.window_size_increment == 0) {
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, return session_handle_invalid_connection(
NULL); session, frame, NGHTTP2_ERR_PROTO,
"WINDOW_UPDATE: window_size_increment == 0");
} }
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
@ -4423,7 +4428,9 @@ static int session_on_stream_window_update_received(nghttp2_session *session,
session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream"); session, frame, NGHTTP2_ERR_PROTO, "WINDOW_UPADATE to reserved stream");
} }
if (frame->window_update.window_size_increment == 0) { if (frame->window_update.window_size_increment == 0) {
return session_handle_invalid_stream(session, frame, NGHTTP2_ERR_PROTO); return session_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO,
"WINDOW_UPDATE: window_size_increment == 0");
} }
if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment < if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
stream->remote_window_size) { stream->remote_window_size) {
@ -4736,6 +4743,14 @@ static int session_on_data_received_fail_fast(nghttp2_session *session) {
error_code = NGHTTP2_PROTOCOL_ERROR; error_code = NGHTTP2_PROTOCOL_ERROR;
goto fail; goto fail;
} }
stream = nghttp2_session_get_stream_raw(session, stream_id);
if (stream && (stream->shut_flags & NGHTTP2_SHUT_RD)) {
failure_reason = "DATA: stream closed";
error_code = NGHTTP2_STREAM_CLOSED;
goto fail;
}
return NGHTTP2_ERR_IGN_PAYLOAD; return NGHTTP2_ERR_IGN_PAYLOAD;
} }
if (stream->shut_flags & NGHTTP2_SHUT_RD) { if (stream->shut_flags & NGHTTP2_SHUT_RD) {
@ -4920,6 +4935,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
mem = &session->mem; mem = &session->mem;
/* We may have idle streams more than we expect (e.g.,
nghttp2_session_change_stream_priority() or
nghttp2_session_create_idle_stream()). Adjust them here. */
rv = nghttp2_session_adjust_idle_stream(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
for (;;) { for (;;) {
switch (iframe->state) { switch (iframe->state) {
case NGHTTP2_IB_READ_CLIENT_MAGIC: case NGHTTP2_IB_READ_CLIENT_MAGIC:
@ -6671,6 +6694,10 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
if (stream == NULL) { if (stream == NULL) {
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
/* We don't call nghttp2_session_adjust_closed_stream(), since this
should be the first stream open. */
if (session->server) { if (session->server) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
session->last_recv_stream_id = 1; session->last_recv_stream_id = 1;
@ -6885,6 +6912,7 @@ int nghttp2_session_check_server_session(nghttp2_session *session) {
int nghttp2_session_change_stream_priority( int nghttp2_session_change_stream_priority(
nghttp2_session *session, int32_t stream_id, nghttp2_session *session, int32_t stream_id,
const nghttp2_priority_spec *pri_spec) { const nghttp2_priority_spec *pri_spec) {
int rv;
nghttp2_stream *stream; nghttp2_stream *stream;
nghttp2_priority_spec pri_spec_copy; nghttp2_priority_spec pri_spec_copy;
@ -6900,7 +6928,18 @@ int nghttp2_session_change_stream_priority(
pri_spec_copy = *pri_spec; pri_spec_copy = *pri_spec;
nghttp2_priority_spec_normalize_weight(&pri_spec_copy); nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
return nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy); rv = nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
if (nghttp2_is_fatal(rv)) {
return rv;
}
/* We don't intentionally call nghttp2_session_adjust_idle_stream()
so that idle stream created by this function, and existing ones
are kept for application. We will adjust number of idle stream
in nghttp2_session_mem_send or nghttp2_session_mem_recv is
called. */
return 0;
} }
int nghttp2_session_create_idle_stream(nghttp2_session *session, int nghttp2_session_create_idle_stream(nghttp2_session *session,
@ -6929,5 +6968,10 @@ int nghttp2_session_create_idle_stream(nghttp2_session *session,
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
/* We don't intentionally call nghttp2_session_adjust_idle_stream()
so that idle stream created by this function, and existing ones
are kept for application. We will adjust number of idle stream
in nghttp2_session_mem_send or nghttp2_session_mem_recv is
called. */
return 0; return 0;
} }

View File

@ -73,6 +73,10 @@ typedef struct {
/* The default maximum number of incoming reserved streams */ /* The default maximum number of incoming reserved streams */
#define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200 #define NGHTTP2_MAX_INCOMING_RESERVED_STREAMS 200
/* Even if we have less SETTINGS_MAX_CONCURRENT_STREAMS than this
number, we keep NGHTTP2_MIN_IDLE_STREAMS streams in idle state */
#define NGHTTP2_MIN_IDLE_STREAMS 16
/* The maximum number of items in outbound queue, which is considered /* The maximum number of items in outbound queue, which is considered
as flooding caused by peer. All frames are not considered here. as flooding caused by peer. All frames are not considered here.
We only consider PING + ACK and SETTINGS + ACK. This is because We only consider PING + ACK and SETTINGS + ACK. This is because
@ -450,6 +454,11 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
* *
* This function returns a pointer to created new stream object, or * This function returns a pointer to created new stream object, or
* NULL. * NULL.
*
* This function adjusts neither the number of closed streams or idle
* streams. The caller should manually call
* nghttp2_session_adjust_closed_stream() or
* nghttp2_session_adjust_idle_stream() respectively.
*/ */
nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
int32_t stream_id, uint8_t flags, int32_t stream_id, uint8_t flags,
@ -498,28 +507,16 @@ int nghttp2_session_destroy_stream(nghttp2_session *session,
* limitation of maximum number of streams in memory, |stream| is not * limitation of maximum number of streams in memory, |stream| is not
* closed and just deleted from memory (see * closed and just deleted from memory (see
* nghttp2_session_destroy_stream). * nghttp2_session_destroy_stream).
*
* This function returns 0 if it succeeds, or one the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory
*/ */
int nghttp2_session_keep_closed_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session,
nghttp2_stream *stream); nghttp2_stream *stream);
/* /*
* Appends |stream| to linked list |session->idle_stream_head|. We * Appends |stream| to linked list |session->idle_stream_head|. We
* apply fixed limit for list size. To fit into that limit, one or * apply fixed limit for list size. To fit into that limit, one or
* more oldest streams are removed from list as necessary. * more oldest streams are removed from list as necessary.
*
* This function returns 0 if it succeeds, or one the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory
*/ */
int nghttp2_session_keep_idle_stream(nghttp2_session *session, void nghttp2_session_keep_idle_stream(nghttp2_session *session,
nghttp2_stream *stream); nghttp2_stream *stream);
/* /*
@ -531,9 +528,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
/* /*
* Deletes closed stream to ensure that number of incoming streams * Deletes closed stream to ensure that number of incoming streams
* including active and closed is in the maximum number of allowed * including active and closed is in the maximum number of allowed
* stream. If |offset| is nonzero, it is decreased from the maximum * stream.
* number of allowed stream when comparing number of active and closed
* stream and the maximum number.
* *
* This function returns 0 if it succeeds, or one the following * This function returns 0 if it succeeds, or one the following
* negative error codes: * negative error codes:
@ -541,8 +536,7 @@ void nghttp2_session_detach_idle_stream(nghttp2_session *session,
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory * Out of memory
*/ */
int nghttp2_session_adjust_closed_stream(nghttp2_session *session, int nghttp2_session_adjust_closed_stream(nghttp2_session *session);
size_t offset);
/* /*
* Deletes idle stream to ensure that number of idle streams is in * Deletes idle stream to ensure that number of idle streams is in
@ -814,6 +808,9 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
* |pri_spec|. Caller must ensure that stream->hd.stream_id != * |pri_spec|. Caller must ensure that stream->hd.stream_id !=
* pri_spec->stream_id. * pri_spec->stream_id.
* *
* This function does not adjust the number of idle streams. The
* caller should call nghttp2_session_adjust_idle_stream() later.
*
* This function returns 0 if it succeeds, or one of the following * This function returns 0 if it succeeds, or one of the following
* negative error codes: * negative error codes:
* *

View File

@ -80,6 +80,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->queued = 0; stream->queued = 0;
stream->descendant_last_cycle = 0; stream->descendant_last_cycle = 0;
stream->cycle = 0; stream->cycle = 0;
stream->pending_penalty = 0;
stream->descendant_next_seq = 0; stream->descendant_next_seq = 0;
stream->seq = 0; stream->seq = 0;
stream->last_writelen = 0; stream->last_writelen = 0;
@ -115,9 +116,14 @@ static int stream_subtree_active(nghttp2_stream *stream) {
/* /*
* Returns next cycle for |stream|. * Returns next cycle for |stream|.
*/ */
static uint64_t stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) { static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
return last_cycle + size_t penalty;
stream->last_writelen * NGHTTP2_MAX_WEIGHT / (uint32_t)stream->weight;
penalty =
stream->last_writelen * NGHTTP2_MAX_WEIGHT + stream->pending_penalty;
stream->cycle = last_cycle + penalty / (uint32_t)stream->weight;
stream->pending_penalty = (uint32_t)(penalty % (uint32_t)stream->weight);
} }
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) { static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
@ -125,7 +131,6 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
for (; dep_stream && !stream->queued; for (; dep_stream && !stream->queued;
stream = dep_stream, dep_stream = dep_stream->dep_prev) { stream = dep_stream, dep_stream = dep_stream->dep_prev) {
stream->cycle =
stream_next_cycle(stream, dep_stream->descendant_last_cycle); stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++; stream->seq = dep_stream->descendant_next_seq++;
@ -169,6 +174,7 @@ static void stream_obq_remove(nghttp2_stream *stream) {
stream->queued = 0; stream->queued = 0;
stream->cycle = 0; stream->cycle = 0;
stream->pending_penalty = 0;
stream->descendant_last_cycle = 0; stream->descendant_last_cycle = 0;
stream->last_writelen = 0; stream->last_writelen = 0;
@ -207,25 +213,12 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
dep_stream = stream->dep_prev; dep_stream = stream->dep_prev;
for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) { for (; dep_stream; stream = dep_stream, dep_stream = dep_stream->dep_prev) {
if (nghttp2_pq_size(&dep_stream->obq) == 1) {
dep_stream->descendant_last_cycle = 0;
stream->cycle = 0;
} else {
/* We update descendant_last_cycle here, and we don't do it when
no data is written for stream. This effectively means that
we treat these streams as if they are not scheduled at all.
This does not cause disruption in scheduling machinery. It
just makes new streams scheduled a bit early. */
dep_stream->descendant_last_cycle = stream->cycle;
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry); nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
stream->cycle =
stream_next_cycle(stream, dep_stream->descendant_last_cycle); stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++; stream->seq = dep_stream->descendant_next_seq++;
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry); nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
}
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n", DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
stream->stream_id, stream->cycle)); stream->stream_id, stream->cycle));
@ -234,6 +227,61 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
} }
} }
void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
nghttp2_stream *dep_stream;
uint64_t last_cycle;
int32_t old_weight;
size_t wlen_penalty;
if (stream->weight == weight) {
return;
}
old_weight = stream->weight;
stream->weight = weight;
dep_stream = stream->dep_prev;
if (!dep_stream) {
return;
}
dep_stream->sum_dep_weight += weight - old_weight;
if (!stream->queued) {
return;
}
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
wlen_penalty = stream->last_writelen * NGHTTP2_MAX_WEIGHT;
/* Compute old stream->pending_penalty we used to calculate
stream->cycle */
stream->pending_penalty =
(uint32_t)((stream->pending_penalty + (uint32_t)old_weight -
(wlen_penalty % (uint32_t)old_weight)) %
(uint32_t)old_weight);
last_cycle = stream->cycle -
(wlen_penalty + stream->pending_penalty) / (uint32_t)old_weight;
/* Now we have old stream->pending_penalty and new stream->weight in
place */
stream_next_cycle(stream, last_cycle);
if (stream->cycle < dep_stream->descendant_last_cycle) {
stream->cycle = dep_stream->descendant_last_cycle;
}
/* Continue to use same stream->seq */
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
stream->stream_id, stream->cycle));
}
static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) { static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
for (; stream->sib_next; stream = stream->sib_next) for (; stream->sib_next; stream = stream->sib_next)
; ;
@ -861,9 +909,15 @@ int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) {
nghttp2_outbound_item * nghttp2_outbound_item *
nghttp2_stream_next_outbound_item(nghttp2_stream *stream) { nghttp2_stream_next_outbound_item(nghttp2_stream *stream) {
nghttp2_pq_entry *ent; nghttp2_pq_entry *ent;
nghttp2_stream *si;
for (;;) { for (;;) {
if (stream_active(stream)) { if (stream_active(stream)) {
/* Update ascendant's descendant_last_cycle here, so that we can
assure that new stream is scheduled based on it. */
for (si = stream; si->dep_prev; si = si->dep_prev) {
si->dep_prev->descendant_last_cycle = si->cycle;
}
return stream->item; return stream->item;
} }
ent = nghttp2_pq_top(&stream->obq); ent = nghttp2_pq_top(&stream->obq);

View File

@ -197,6 +197,8 @@ struct nghttp2_stream {
int32_t local_window_size; int32_t local_window_size;
/* weight of this stream */ /* weight of this stream */
int32_t weight; int32_t weight;
/* This is unpaid penalty (offset) when calculating cycle. */
uint32_t pending_penalty;
/* sum of weight of direct descendants */ /* sum of weight of direct descendants */
int32_t sum_dep_weight; int32_t sum_dep_weight;
nghttp2_stream_state state; nghttp2_stream_state state;
@ -419,7 +421,14 @@ int nghttp2_stream_in_dep_tree(nghttp2_stream *stream);
void nghttp2_stream_reschedule(nghttp2_stream *stream); void nghttp2_stream_reschedule(nghttp2_stream *stream);
/* /*
* Returns a stream which has highest priority. * Changes |stream|'s weight to |weight|. If |stream| is queued, it
* will be rescheduled based on new weight.
*/
void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight);
/*
* Returns a stream which has highest priority, updating
* descendant_last_cycle of selected stream's ancestors.
*/ */
nghttp2_outbound_item * nghttp2_outbound_item *
nghttp2_stream_next_outbound_item(nghttp2_stream *stream); nghttp2_stream_next_outbound_item(nghttp2_stream *stream);

View File

@ -40,8 +40,7 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
const nghttp2_priority_spec *pri_spec, const nghttp2_priority_spec *pri_spec,
nghttp2_nv *nva_copy, size_t nvlen, nghttp2_nv *nva_copy, size_t nvlen,
const nghttp2_data_provider *data_prd, const nghttp2_data_provider *data_prd,
void *stream_user_data, void *stream_user_data) {
uint8_t attach_stream) {
int rv; int rv;
uint8_t flags_copy; uint8_t flags_copy;
nghttp2_outbound_item *item = NULL; nghttp2_outbound_item *item = NULL;
@ -56,6 +55,16 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
goto fail; goto fail;
} }
if (stream_id == -1) {
if ((int32_t)session->next_stream_id == pri_spec->stream_id) {
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
goto fail;
}
} else if (stream_id == pri_spec->stream_id) {
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
goto fail;
}
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
if (item == NULL) { if (item == NULL) {
rv = NGHTTP2_ERR_NOMEM; rv = NGHTTP2_ERR_NOMEM;
@ -69,7 +78,6 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
} }
item->aux_data.headers.stream_user_data = stream_user_data; item->aux_data.headers.stream_user_data = stream_user_data;
item->aux_data.headers.attach_stream = attach_stream;
flags_copy = flags_copy =
(uint8_t)((flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) | (uint8_t)((flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
@ -122,8 +130,7 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
const nghttp2_priority_spec *pri_spec, const nghttp2_priority_spec *pri_spec,
const nghttp2_nv *nva, size_t nvlen, const nghttp2_nv *nva, size_t nvlen,
const nghttp2_data_provider *data_prd, const nghttp2_data_provider *data_prd,
void *stream_user_data, void *stream_user_data) {
uint8_t attach_stream) {
int rv; int rv;
nghttp2_nv *nva_copy; nghttp2_nv *nva_copy;
nghttp2_priority_spec copy_pri_spec; nghttp2_priority_spec copy_pri_spec;
@ -144,15 +151,14 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
} }
return submit_headers_shared(session, flags, stream_id, &copy_pri_spec, return submit_headers_shared(session, flags, stream_id, &copy_pri_spec,
nva_copy, nvlen, data_prd, stream_user_data, nva_copy, nvlen, data_prd, stream_user_data);
attach_stream);
} }
int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
const nghttp2_nv *nva, size_t nvlen) { const nghttp2_nv *nva, size_t nvlen) {
return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM, return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
stream_id, NULL, nva, nvlen, NULL, NULL, stream_id, NULL, nva, nvlen, NULL,
0); NULL);
} }
int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
@ -169,7 +175,7 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
} }
return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva, return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva,
nvlen, NULL, stream_user_data, 0); nvlen, NULL, stream_user_data);
} }
int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_, int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_,
@ -398,7 +404,7 @@ int32_t nghttp2_submit_request(nghttp2_session *session,
flags = set_request_flags(pri_spec, data_prd); flags = set_request_flags(pri_spec, data_prd);
return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen, return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen,
data_prd, stream_user_data, 0); data_prd, stream_user_data);
} }
static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) { static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) {
@ -414,7 +420,7 @@ int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
const nghttp2_data_provider *data_prd) { const nghttp2_data_provider *data_prd) {
uint8_t flags = set_response_flags(data_prd); uint8_t flags = set_response_flags(data_prd);
return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen, return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
data_prd, NULL, 1); data_prd, NULL);
} }
int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,

View File

@ -53,6 +53,7 @@
#include <deque> #include <deque>
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/dh.h>
#include <zlib.h> #include <zlib.h>
@ -105,7 +106,7 @@ Config::Config()
max_concurrent_streams(100), header_table_size(-1), port(0), max_concurrent_streams(100), header_table_size(-1), port(0),
verbose(false), daemon(false), verify_client(false), no_tls(false), verbose(false), daemon(false), verify_client(false), no_tls(false),
error_gzip(false), early_response(false), hexdump(false), error_gzip(false), early_response(false), hexdump(false),
echo_upload(false) {} echo_upload(false), no_content_length(false) {}
Config::~Config() {} Config::~Config() {}
@ -872,12 +873,14 @@ int Http2Handler::submit_file_response(const std::string &status,
std::string last_modified_str; std::string last_modified_str;
auto nva = make_array(http2::make_nv_ls(":status", status), auto nva = make_array(http2::make_nv_ls(":status", status),
http2::make_nv_ll("server", NGHTTPD_SERVER), http2::make_nv_ll("server", NGHTTPD_SERVER),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ll("cache-control", "max-age=3600"), http2::make_nv_ll("cache-control", "max-age=3600"),
http2::make_nv_ls("date", sessions_->get_cached_date()), http2::make_nv_ls("date", sessions_->get_cached_date()),
http2::make_nv_ll("", ""), http2::make_nv_ll("", ""), http2::make_nv_ll("", ""), http2::make_nv_ll("", ""),
http2::make_nv_ll("", "")); http2::make_nv_ll("", ""), http2::make_nv_ll("", ""));
size_t nvlen = 5; size_t nvlen = 4;
if (!get_config()->no_content_length) {
nva[nvlen++] = http2::make_nv_ls("content-length", content_length);
}
if (last_modified != 0) { if (last_modified != 0) {
last_modified_str = util::http_date(last_modified); last_modified_str = util::http_date(last_modified);
nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str); nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
@ -1087,7 +1090,9 @@ void prepare_echo_response(Stream *stream, Http2Handler *hd) {
Headers headers; Headers headers;
headers.emplace_back("nghttpd-response", "echo"); headers.emplace_back("nghttpd-response", "echo");
if (!hd->get_config()->no_content_length) {
headers.emplace_back("content-length", util::utos(length)); headers.emplace_back("content-length", util::utos(length));
}
hd->submit_response("200", stream->stream_id, headers, &data_prd); hd->submit_response("200", stream->stream_id, headers, &data_prd);
} }

View File

@ -76,6 +76,7 @@ struct Config {
bool early_response; bool early_response;
bool hexdump; bool hexdump;
bool echo_upload; bool echo_upload;
bool no_content_length;
Config(); Config();
~Config(); ~Config();
}; };

View File

@ -39,12 +39,16 @@ using boost::asio::ip::tcp;
session::session(boost::asio::io_service &io_service, const std::string &host, session::session(boost::asio::io_service &io_service, const std::string &host,
const std::string &service) const std::string &service)
: impl_(make_unique<session_tcp_impl>(io_service, host, service)) {} : impl_(std::make_shared<session_tcp_impl>(io_service, host, service)) {
impl_->start_resolve(host, service);
}
session::session(boost::asio::io_service &io_service, session::session(boost::asio::io_service &io_service,
boost::asio::ssl::context &tls_ctx, const std::string &host, boost::asio::ssl::context &tls_ctx, const std::string &host,
const std::string &service) const std::string &service)
: impl_(make_unique<session_tls_impl>(io_service, tls_ctx, host, service)) { : impl_(std::make_shared<session_tls_impl>(io_service, tls_ctx, host,
service)) {
impl_->start_resolve(host, service);
} }
session::~session() {} session::~session() {}
@ -93,6 +97,14 @@ const request *session::submit(boost::system::error_code &ec,
return impl_->submit(ec, method, uri, std::move(cb), std::move(h)); return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
} }
void session::connect_timeout(const boost::posix_time::time_duration &t) {
impl_->connect_timeout(t);
}
void session::read_timeout(const boost::posix_time::time_duration &t) {
impl_->read_timeout(t);
}
} // namespace client } // namespace client
} // namespace asio_http2 } // namespace asio_http2
} // nghttp2 } // nghttp2

View File

@ -40,8 +40,10 @@ namespace client {
session_impl::session_impl(boost::asio::io_service &io_service) session_impl::session_impl(boost::asio::io_service &io_service)
: wblen_(0), io_service_(io_service), resolver_(io_service), : wblen_(0), io_service_(io_service), resolver_(io_service),
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0), deadline_(io_service), connect_timeout_(boost::posix_time::seconds(60)),
writing_(false), inside_callback_(false) {} read_timeout_(boost::posix_time::seconds(60)), session_(nullptr),
data_pending_(nullptr), data_pendinglen_(0), writing_(false),
inside_callback_(false), stopped_(false) {}
session_impl::~session_impl() { session_impl::~session_impl() {
// finish up all active stream // finish up all active stream
@ -56,8 +58,12 @@ session_impl::~session_impl() {
void session_impl::start_resolve(const std::string &host, void session_impl::start_resolve(const std::string &host,
const std::string &service) { const std::string &service) {
deadline_.expires_from_now(connect_timeout_);
auto self = this->shared_from_this();
resolver_.async_resolve({host, service}, resolver_.async_resolve({host, service},
[this](const boost::system::error_code &ec, [this, self](const boost::system::error_code &ec,
tcp::resolver::iterator endpoint_it) { tcp::resolver::iterator endpoint_it) {
if (ec) { if (ec) {
not_connected(ec); not_connected(ec);
@ -66,6 +72,25 @@ void session_impl::start_resolve(const std::string &host,
start_connect(endpoint_it); start_connect(endpoint_it);
}); });
deadline_.async_wait(std::bind(&session_impl::handle_deadline, self));
}
void session_impl::handle_deadline() {
if (stopped_) {
return;
}
if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) {
call_error_cb(boost::asio::error::timed_out);
stop();
deadline_.expires_at(boost::posix_time::pos_infin);
return;
}
deadline_.async_wait(
std::bind(&session_impl::handle_deadline, this->shared_from_this()));
} }
void session_impl::connected(tcp::resolver::iterator endpoint_it) { void session_impl::connected(tcp::resolver::iterator endpoint_it) {
@ -86,6 +111,7 @@ void session_impl::connected(tcp::resolver::iterator endpoint_it) {
void session_impl::not_connected(const boost::system::error_code &ec) { void session_impl::not_connected(const boost::system::error_code &ec) {
call_error_cb(ec); call_error_cb(ec);
stop();
} }
void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); } void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
@ -97,6 +123,9 @@ const connect_cb &session_impl::on_connect() const { return connect_cb_; }
const error_cb &session_impl::on_error() const { return error_cb_; } const error_cb &session_impl::on_error() const { return error_cb_; }
void session_impl::call_error_cb(const boost::system::error_code &ec) { void session_impl::call_error_cb(const boost::system::error_code &ec) {
if (stopped_) {
return;
}
auto &error_cb = on_error(); auto &error_cb = on_error();
if (!error_cb) { if (!error_cb) {
return; return;
@ -350,12 +379,20 @@ int session_impl::write_trailer(stream &strm, header_map h) {
} }
void session_impl::cancel(stream &strm, uint32_t error_code) { void session_impl::cancel(stream &strm, uint32_t error_code) {
if (stopped_) {
return;
}
nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(), nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
error_code); error_code);
signal_write(); signal_write();
} }
void session_impl::resume(stream &strm) { void session_impl::resume(stream &strm) {
if (stopped_) {
return;
}
nghttp2_session_resume_data(session_, strm.stream_id()); nghttp2_session_resume_data(session_, strm.stream_id());
signal_write(); signal_write();
} }
@ -396,6 +433,11 @@ const request *session_impl::submit(boost::system::error_code &ec,
header_map h) { header_map h) {
ec.clear(); ec.clear();
if (stopped_) {
ec = make_error_code(static_cast<nghttp2_error>(NGHTTP2_INTERNAL_ERROR));
return nullptr;
}
http_parser_url u{}; http_parser_url u{};
// TODO Handle CONNECT method // TODO Handle CONNECT method
if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
@ -485,6 +527,10 @@ const request *session_impl::submit(boost::system::error_code &ec,
} }
void session_impl::shutdown() { void session_impl::shutdown() {
if (stopped_) {
return;
}
nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR); nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
signal_write(); signal_write();
} }
@ -522,13 +568,21 @@ void session_impl::leave_callback() {
} }
void session_impl::do_read() { void session_impl::do_read() {
read_socket([this](const boost::system::error_code &ec, if (stopped_) {
return;
}
deadline_.expires_from_now(read_timeout_);
auto self = this->shared_from_this();
read_socket([this, self](const boost::system::error_code &ec,
std::size_t bytes_transferred) { std::size_t bytes_transferred) {
if (ec) { if (ec) {
if (!should_stop()) { if (!should_stop()) {
call_error_cb(ec); call_error_cb(ec);
shutdown_socket();
} }
stop();
return; return;
} }
@ -541,7 +595,7 @@ void session_impl::do_read() {
if (rv != static_cast<ssize_t>(bytes_transferred)) { if (rv != static_cast<ssize_t>(bytes_transferred)) {
call_error_cb(make_error_code( call_error_cb(make_error_code(
static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO))); static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
shutdown_socket(); stop();
return; return;
} }
} }
@ -549,7 +603,7 @@ void session_impl::do_read() {
do_write(); do_write();
if (should_stop()) { if (should_stop()) {
shutdown_socket(); stop();
return; return;
} }
@ -558,6 +612,10 @@ void session_impl::do_read() {
} }
void session_impl::do_write() { void session_impl::do_write() {
if (stopped_) {
return;
}
if (writing_) { if (writing_) {
return; return;
} }
@ -579,7 +637,7 @@ void session_impl::do_write() {
auto n = nghttp2_session_mem_send(session_, &data); auto n = nghttp2_session_mem_send(session_, &data);
if (n < 0) { if (n < 0) {
call_error_cb(make_error_code(static_cast<nghttp2_error>(n))); call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
shutdown_socket(); stop();
return; return;
} }
@ -601,15 +659,25 @@ void session_impl::do_write() {
} }
if (wblen_ == 0) { if (wblen_ == 0) {
if (should_stop()) {
stop();
}
return; return;
} }
writing_ = true; writing_ = true;
write_socket([this](const boost::system::error_code &ec, std::size_t n) { // Reset read deadline here, because normally client is sending
// something, it does not expect timeout while doing it.
deadline_.expires_from_now(read_timeout_);
auto self = this->shared_from_this();
write_socket(
[this, self](const boost::system::error_code &ec, std::size_t n) {
if (ec) { if (ec) {
call_error_cb(ec); call_error_cb(ec);
shutdown_socket(); stop();
return; return;
} }
@ -620,6 +688,24 @@ void session_impl::do_write() {
}); });
} }
void session_impl::stop() {
if (stopped_) {
return;
}
shutdown_socket();
deadline_.cancel();
stopped_ = true;
}
void session_impl::connect_timeout(const boost::posix_time::time_duration &t) {
connect_timeout_ = t;
}
void session_impl::read_timeout(const boost::posix_time::time_duration &t) {
read_timeout_ = t;
}
} // namespace client } // namespace client
} // namespace asio_http2 } // namespace asio_http2
} // nghttp2 } // nghttp2

View File

@ -41,7 +41,7 @@ class stream;
using boost::asio::ip::tcp; using boost::asio::ip::tcp;
class session_impl { class session_impl : public std::enable_shared_from_this<session_impl> {
public: public:
session_impl(boost::asio::io_service &io_service); session_impl(boost::asio::io_service &io_service);
virtual ~session_impl(); virtual ~session_impl();
@ -91,6 +91,11 @@ public:
void do_read(); void do_read();
void do_write(); void do_write();
void connect_timeout(const boost::posix_time::time_duration &t);
void read_timeout(const boost::posix_time::time_duration &t);
void stop();
protected: protected:
boost::array<uint8_t, 8_k> rb_; boost::array<uint8_t, 8_k> rb_;
boost::array<uint8_t, 64_k> wb_; boost::array<uint8_t, 64_k> wb_;
@ -100,6 +105,7 @@ private:
bool should_stop() const; bool should_stop() const;
bool setup_session(); bool setup_session();
void call_error_cb(const boost::system::error_code &ec); void call_error_cb(const boost::system::error_code &ec);
void handle_deadline();
boost::asio::io_service &io_service_; boost::asio::io_service &io_service_;
tcp::resolver resolver_; tcp::resolver resolver_;
@ -109,6 +115,10 @@ private:
connect_cb connect_cb_; connect_cb connect_cb_;
error_cb error_cb_; error_cb error_cb_;
boost::asio::deadline_timer deadline_;
boost::posix_time::time_duration connect_timeout_;
boost::posix_time::time_duration read_timeout_;
nghttp2_session *session_; nghttp2_session *session_;
const uint8_t *data_pending_; const uint8_t *data_pending_;
@ -116,6 +126,7 @@ private:
bool writing_; bool writing_;
bool inside_callback_; bool inside_callback_;
bool stopped_;
}; };
} // namespace client } // namespace client

View File

@ -31,9 +31,7 @@ namespace client {
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service, session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
const std::string &host, const std::string &host,
const std::string &service) const std::string &service)
: session_impl(io_service), socket_(io_service) { : session_impl(io_service), socket_(io_service) {}
start_resolve(host, service);
}
session_tcp_impl::~session_tcp_impl() {} session_tcp_impl::~session_tcp_impl() {}
@ -62,7 +60,10 @@ void session_tcp_impl::write_socket(
boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h); boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h);
} }
void session_tcp_impl::shutdown_socket() { socket_.close(); } void session_tcp_impl::shutdown_socket() {
boost::system::error_code ignored_ec;
socket_.close(ignored_ec);
}
} // namespace client } // namespace client
} // namespace asio_http2 } // namespace asio_http2

View File

@ -38,8 +38,6 @@ session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is // ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
// not used, which is what we want. // not used, which is what we want.
socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host)); socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host));
start_resolve(host, service);
} }
session_tls_impl::~session_tls_impl() {} session_tls_impl::~session_tls_impl() {}
@ -85,7 +83,8 @@ void session_tls_impl::write_socket(
} }
void session_tls_impl::shutdown_socket() { void session_tls_impl::shutdown_socket() {
socket_.async_shutdown([](const boost::system::error_code &ec) {}); boost::system::error_code ignored_ec;
socket_.lowest_layer().close(ignored_ec);
} }
} // namespace client } // namespace client

View File

@ -92,6 +92,11 @@ boost::asio::io_service &io_service_pool::get_io_service() {
return io_service; return io_service;
} }
const std::vector<std::shared_ptr<boost::asio::io_service>> &
io_service_pool::io_services() const {
return io_services_;
}
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -70,6 +70,10 @@ public:
/// Get an io_service to use. /// Get an io_service to use.
boost::asio::io_service &get_io_service(); boost::asio::io_service &get_io_service();
/// Get access to all io_service objects.
const std::vector<std::shared_ptr<boost::asio::io_service>> &
io_services() const;
private: private:
/// The pool of io_services. /// The pool of io_services.
std::vector<std::shared_ptr<boost::asio::io_service>> io_services_; std::vector<std::shared_ptr<boost::asio::io_service>> io_services_;

View File

@ -44,8 +44,12 @@ namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
namespace server { namespace server {
server::server(std::size_t io_service_pool_size) server::server(std::size_t io_service_pool_size,
: io_service_pool_(io_service_pool_size) {} const boost::posix_time::time_duration &tls_handshake_timeout,
const boost::posix_time::time_duration &read_timeout)
: io_service_pool_(io_service_pool_size),
tls_handshake_timeout_(tls_handshake_timeout),
read_timeout_(read_timeout) {}
boost::system::error_code boost::system::error_code
server::listen_and_serve(boost::system::error_code &ec, server::listen_and_serve(boost::system::error_code &ec,
@ -121,7 +125,8 @@ boost::system::error_code server::bind_and_listen(boost::system::error_code &ec,
void server::start_accept(boost::asio::ssl::context &tls_context, void server::start_accept(boost::asio::ssl::context &tls_context,
tcp::acceptor &acceptor, serve_mux &mux) { tcp::acceptor &acceptor, serve_mux &mux) {
auto new_connection = std::make_shared<connection<ssl_socket>>( auto new_connection = std::make_shared<connection<ssl_socket>>(
mux, io_service_pool_.get_io_service(), tls_context); mux, tls_handshake_timeout_, read_timeout_,
io_service_pool_.get_io_service(), tls_context);
acceptor.async_accept( acceptor.async_accept(
new_connection->socket().lowest_layer(), new_connection->socket().lowest_layer(),
@ -130,14 +135,17 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
if (!e) { if (!e) {
new_connection->socket().lowest_layer().set_option( new_connection->socket().lowest_layer().set_option(
tcp::no_delay(true)); tcp::no_delay(true));
new_connection->start_tls_handshake_deadline();
new_connection->socket().async_handshake( new_connection->socket().async_handshake(
boost::asio::ssl::stream_base::server, boost::asio::ssl::stream_base::server,
[new_connection](const boost::system::error_code &e) { [new_connection](const boost::system::error_code &e) {
if (e) { if (e) {
new_connection->stop();
return; return;
} }
if (!tls_h2_negotiated(new_connection->socket())) { if (!tls_h2_negotiated(new_connection->socket())) {
new_connection->stop();
return; return;
} }
@ -151,13 +159,15 @@ void server::start_accept(boost::asio::ssl::context &tls_context,
void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) { void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
auto new_connection = std::make_shared<connection<tcp::socket>>( auto new_connection = std::make_shared<connection<tcp::socket>>(
mux, io_service_pool_.get_io_service()); mux, tls_handshake_timeout_, read_timeout_,
io_service_pool_.get_io_service());
acceptor.async_accept( acceptor.async_accept(
new_connection->socket(), [this, &acceptor, &mux, new_connection]( new_connection->socket(), [this, &acceptor, &mux, new_connection](
const boost::system::error_code &e) { const boost::system::error_code &e) {
if (!e) { if (!e) {
new_connection->socket().set_option(tcp::no_delay(true)); new_connection->socket().set_option(tcp::no_delay(true));
new_connection->start_read_deadline();
new_connection->start(); new_connection->start();
} }
@ -169,6 +179,11 @@ void server::stop() { io_service_pool_.stop(); }
void server::join() { io_service_pool_.join(); } void server::join() { io_service_pool_.join(); }
const std::vector<std::shared_ptr<boost::asio::io_service>> &
server::io_services() const {
return io_service_pool_.io_services();
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -63,7 +63,9 @@ using ssl_socket = boost::asio::ssl::stream<tcp::socket>;
class server : private boost::noncopyable { class server : private boost::noncopyable {
public: public:
explicit server(std::size_t io_service_pool_size); explicit server(std::size_t io_service_pool_size,
const boost::posix_time::time_duration &tls_handshake_timeout,
const boost::posix_time::time_duration &read_timeout);
boost::system::error_code boost::system::error_code
listen_and_serve(boost::system::error_code &ec, listen_and_serve(boost::system::error_code &ec,
@ -73,6 +75,10 @@ public:
void join(); void join();
void stop(); void stop();
/// Get access to all io_service objects.
const std::vector<std::shared_ptr<boost::asio::io_service>> &
io_services() const;
private: private:
/// Initiate an asynchronous accept operation. /// Initiate an asynchronous accept operation.
void start_accept(tcp::acceptor &acceptor, serve_mux &mux); void start_accept(tcp::acceptor &acceptor, serve_mux &mux);
@ -94,6 +100,9 @@ private:
std::vector<tcp::acceptor> acceptors_; std::vector<tcp::acceptor> acceptors_;
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_; std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
boost::posix_time::time_duration tls_handshake_timeout_;
boost::posix_time::time_duration read_timeout_;
}; };
} // namespace server } // namespace server

View File

@ -64,15 +64,23 @@ class connection : public std::enable_shared_from_this<connection<socket_type>>,
public: public:
/// Construct a connection with the given io_service. /// Construct a connection with the given io_service.
template <typename... SocketArgs> template <typename... SocketArgs>
explicit connection(serve_mux &mux, SocketArgs &&... args) explicit connection(
: socket_(std::forward<SocketArgs>(args)...), mux_(mux), writing_(false) { serve_mux &mux,
} const boost::posix_time::time_duration &tls_handshake_timeout,
const boost::posix_time::time_duration &read_timeout,
SocketArgs &&... args)
: socket_(std::forward<SocketArgs>(args)...), mux_(mux),
deadline_(socket_.get_io_service()),
tls_handshake_timeout_(tls_handshake_timeout),
read_timeout_(read_timeout), writing_(false), stopped_(false) {}
/// Start the first asynchronous operation for the connection. /// Start the first asynchronous operation for the connection.
void start() { void start() {
handler_ = std::make_shared<http2_handler>(socket_.get_io_service(), handler_ = std::make_shared<http2_handler>(
socket_.get_io_service(), socket_.lowest_layer().remote_endpoint(),
[this]() { do_write(); }, mux_); [this]() { do_write(); }, mux_);
if (handler_->start() != 0) { if (handler_->start() != 0) {
stop();
return; return;
} }
do_read(); do_read();
@ -80,26 +88,61 @@ public:
socket_type &socket() { return socket_; } socket_type &socket() { return socket_; }
void start_tls_handshake_deadline() {
deadline_.expires_from_now(tls_handshake_timeout_);
deadline_.async_wait(
std::bind(&connection::handle_deadline, this->shared_from_this()));
}
void start_read_deadline() {
deadline_.expires_from_now(read_timeout_);
deadline_.async_wait(
std::bind(&connection::handle_deadline, this->shared_from_this()));
}
void handle_deadline() {
if (stopped_) {
return;
}
if (deadline_.expires_at() <=
boost::asio::deadline_timer::traits_type::now()) {
stop();
deadline_.expires_at(boost::posix_time::pos_infin);
return;
}
deadline_.async_wait(
std::bind(&connection::handle_deadline, this->shared_from_this()));
}
void do_read() { void do_read() {
auto self = this->shared_from_this(); auto self = this->shared_from_this();
deadline_.expires_from_now(read_timeout_);
socket_.async_read_some( socket_.async_read_some(
boost::asio::buffer(buffer_), boost::asio::buffer(buffer_),
[this, self](const boost::system::error_code &e, [this, self](const boost::system::error_code &e,
std::size_t bytes_transferred) { std::size_t bytes_transferred) {
if (!e) { if (e) {
stop();
return;
}
if (handler_->on_read(buffer_, bytes_transferred) != 0) { if (handler_->on_read(buffer_, bytes_transferred) != 0) {
stop();
return; return;
} }
do_write(); do_write();
if (!writing_ && handler_->should_stop()) { if (!writing_ && handler_->should_stop()) {
stop();
return; return;
} }
do_read(); do_read();
}
// If an error occurs then no new asynchronous operations are // If an error occurs then no new asynchronous operations are
// started. This means that all shared_ptr references to the // started. This means that all shared_ptr references to the
@ -122,23 +165,34 @@ public:
rv = handler_->on_write(outbuf_, nwrite); rv = handler_->on_write(outbuf_, nwrite);
if (rv != 0) { if (rv != 0) {
stop();
return; return;
} }
if (nwrite == 0) { if (nwrite == 0) {
if (handler_->should_stop()) {
stop();
}
return; return;
} }
writing_ = true; writing_ = true;
// Reset read deadline here, because normally client is sending
// something, it does not expect timeout while doing it.
deadline_.expires_from_now(read_timeout_);
boost::asio::async_write( boost::asio::async_write(
socket_, boost::asio::buffer(outbuf_, nwrite), socket_, boost::asio::buffer(outbuf_, nwrite),
[this, self](const boost::system::error_code &e, std::size_t) { [this, self](const boost::system::error_code &e, std::size_t) {
if (!e) { if (e) {
stop();
return;
}
writing_ = false; writing_ = false;
do_write(); do_write();
}
}); });
// No new asynchronous operations are started. This means that all // No new asynchronous operations are started. This means that all
@ -147,6 +201,17 @@ public:
// returns. The connection class's destructor closes the socket. // returns. The connection class's destructor closes the socket.
} }
void stop() {
if (stopped_) {
return;
}
stopped_ = true;
boost::system::error_code ignored_ec;
socket_.lowest_layer().close(ignored_ec);
deadline_.cancel();
}
private: private:
socket_type socket_; socket_type socket_;
@ -159,7 +224,12 @@ private:
boost::array<uint8_t, 64_k> outbuf_; boost::array<uint8_t, 64_k> outbuf_;
boost::asio::deadline_timer deadline_;
boost::posix_time::time_duration tls_handshake_timeout_;
boost::posix_time::time_duration read_timeout_;
bool writing_; bool writing_;
bool stopped_;
}; };
} // namespace server } // namespace server

View File

@ -69,6 +69,14 @@ void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
void http2::backlog(int backlog) { impl_->backlog(backlog); } void http2::backlog(int backlog) { impl_->backlog(backlog); }
void http2::tls_handshake_timeout(const boost::posix_time::time_duration &t) {
impl_->tls_handshake_timeout(t);
}
void http2::read_timeout(const boost::posix_time::time_duration &t) {
impl_->read_timeout(t);
}
bool http2::handle(std::string pattern, request_cb cb) { bool http2::handle(std::string pattern, request_cb cb) {
return impl_->handle(std::move(pattern), std::move(cb)); return impl_->handle(std::move(pattern), std::move(cb));
} }
@ -77,6 +85,11 @@ void http2::stop() { impl_->stop(); }
void http2::join() { return impl_->join(); } void http2::join() { return impl_->join(); }
const std::vector<std::shared_ptr<boost::asio::io_service>> &
http2::io_services() const {
return impl_->io_services();
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2

View File

@ -136,6 +136,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
break; break;
} }
auto &req = strm->request().impl();
req.remote_endpoint(handler->remote_endpoint());
handler->call_on_request(*strm); handler->call_on_request(*strm);
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
@ -225,8 +228,9 @@ int on_frame_not_send_callback(nghttp2_session *session,
} // namespace } // namespace
http2_handler::http2_handler(boost::asio::io_service &io_service, http2_handler::http2_handler(boost::asio::io_service &io_service,
boost::asio::ip::tcp::endpoint ep,
connection_write writefun, serve_mux &mux) connection_write writefun, serve_mux &mux)
: writefun_(writefun), mux_(mux), io_service_(io_service), : writefun_(writefun), mux_(mux), io_service_(io_service), remote_ep_(ep),
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false), session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false),
tstamp_cached_(time(nullptr)), tstamp_cached_(time(nullptr)),
formatted_date_(util::http_date(tstamp_cached_)) {} formatted_date_(util::http_date(tstamp_cached_)) {}
@ -449,6 +453,10 @@ response *http2_handler::push_promise(boost::system::error_code &ec,
boost::asio::io_service &http2_handler::io_service() { return io_service_; } boost::asio::io_service &http2_handler::io_service() { return io_service_; }
const boost::asio::ip::tcp::endpoint &http2_handler::remote_endpoint() {
return remote_ep_;
}
callback_guard::callback_guard(http2_handler &h) : handler(h) { callback_guard::callback_guard(http2_handler &h) : handler(h) {
handler.enter_callback(); handler.enter_callback();
} }

View File

@ -53,7 +53,8 @@ using connection_write = std::function<void(void)>;
class http2_handler : public std::enable_shared_from_this<http2_handler> { class http2_handler : public std::enable_shared_from_this<http2_handler> {
public: public:
http2_handler(boost::asio::io_service &io_service, connection_write writefun, http2_handler(boost::asio::io_service &io_service,
boost::asio::ip::tcp::endpoint ep, connection_write writefun,
serve_mux &mux); serve_mux &mux);
~http2_handler(); ~http2_handler();
@ -89,6 +90,8 @@ public:
boost::asio::io_service &io_service(); boost::asio::io_service &io_service();
const boost::asio::ip::tcp::endpoint &remote_endpoint();
const std::string &http_date(); const std::string &http_date();
template <size_t N> template <size_t N>
@ -152,6 +155,7 @@ private:
connection_write writefun_; connection_write writefun_;
serve_mux &mux_; serve_mux &mux_;
boost::asio::io_service &io_service_; boost::asio::io_service &io_service_;
boost::asio::ip::tcp::endpoint remote_ep_;
nghttp2_session *session_; nghttp2_session *session_;
const uint8_t *buf_; const uint8_t *buf_;
std::size_t buflen_; std::size_t buflen_;

View File

@ -37,12 +37,16 @@ namespace asio_http2 {
namespace server { namespace server {
http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {} http2_impl::http2_impl()
: num_threads_(1), backlog_(-1),
tls_handshake_timeout_(boost::posix_time::seconds(60)),
read_timeout_(boost::posix_time::seconds(60)) {}
boost::system::error_code http2_impl::listen_and_serve( boost::system::error_code http2_impl::listen_and_serve(
boost::system::error_code &ec, boost::asio::ssl::context *tls_context, boost::system::error_code &ec, boost::asio::ssl::context *tls_context,
const std::string &address, const std::string &port, bool asynchronous) { const std::string &address, const std::string &port, bool asynchronous) {
server_.reset(new server(num_threads_)); server_.reset(
new server(num_threads_, tls_handshake_timeout_, read_timeout_));
return server_->listen_and_serve(ec, tls_context, address, port, backlog_, return server_->listen_and_serve(ec, tls_context, address, port, backlog_,
mux_, asynchronous); mux_, asynchronous);
} }
@ -51,6 +55,15 @@ void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; }
void http2_impl::backlog(int backlog) { backlog_ = backlog; } void http2_impl::backlog(int backlog) { backlog_ = backlog; }
void http2_impl::tls_handshake_timeout(
const boost::posix_time::time_duration &t) {
tls_handshake_timeout_ = t;
}
void http2_impl::read_timeout(const boost::posix_time::time_duration &t) {
read_timeout_ = t;
}
bool http2_impl::handle(std::string pattern, request_cb cb) { bool http2_impl::handle(std::string pattern, request_cb cb) {
return mux_.handle(std::move(pattern), std::move(cb)); return mux_.handle(std::move(pattern), std::move(cb));
} }
@ -59,6 +72,11 @@ void http2_impl::stop() { return server_->stop(); }
void http2_impl::join() { return server_->join(); } void http2_impl::join() { return server_->join(); }
const std::vector<std::shared_ptr<boost::asio::io_service>> &
http2_impl::io_services() const {
return server_->io_services();
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2

View File

@ -47,15 +47,21 @@ public:
const std::string &address, const std::string &port, bool asynchronous); const std::string &address, const std::string &port, bool asynchronous);
void num_threads(size_t num_threads); void num_threads(size_t num_threads);
void backlog(int backlog); void backlog(int backlog);
void tls_handshake_timeout(const boost::posix_time::time_duration &t);
void read_timeout(const boost::posix_time::time_duration &t);
bool handle(std::string pattern, request_cb cb); bool handle(std::string pattern, request_cb cb);
void stop(); void stop();
void join(); void join();
const std::vector<std::shared_ptr<boost::asio::io_service>> &
io_services() const;
private: private:
std::unique_ptr<server> server_; std::unique_ptr<server> server_;
std::size_t num_threads_; std::size_t num_threads_;
int backlog_; int backlog_;
serve_mux mux_; serve_mux mux_;
boost::posix_time::time_duration tls_handshake_timeout_;
boost::posix_time::time_duration read_timeout_;
}; };
} // namespace server } // namespace server

View File

@ -50,6 +50,10 @@ void request::on_data(data_cb cb) const {
request_impl &request::impl() const { return *impl_; } request_impl &request::impl() const { return *impl_; }
const boost::asio::ip::tcp::endpoint &request::remote_endpoint() const {
return impl_->remote_endpoint();
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -54,6 +54,14 @@ void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
} }
} }
const boost::asio::ip::tcp::endpoint &request_impl::remote_endpoint() const {
return remote_ep_;
}
void request_impl::remote_endpoint(boost::asio::ip::tcp::endpoint ep) {
remote_ep_ = std::move(ep);
}
} // namespace server } // namespace server
} // namespace asio_http2 } // namespace asio_http2
} // namespace nghttp2 } // namespace nghttp2

View File

@ -28,6 +28,7 @@
#include "nghttp2_config.h" #include "nghttp2_config.h"
#include <nghttp2/asio_http2_server.h> #include <nghttp2/asio_http2_server.h>
#include <boost/asio/ip/tcp.hpp>
namespace nghttp2 { namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
@ -54,12 +55,16 @@ public:
void stream(class stream *s); void stream(class stream *s);
void call_on_data(const uint8_t *data, std::size_t len); void call_on_data(const uint8_t *data, std::size_t len);
const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
void remote_endpoint(boost::asio::ip::tcp::endpoint ep);
private: private:
class stream *strm_; class stream *strm_;
header_map header_; header_map header_;
std::string method_; std::string method_;
uri_ref uri_; uri_ref uri_;
data_cb on_data_cb_; data_cb on_data_cb_;
boost::asio::ip::tcp::endpoint remote_ep_;
}; };
} // namespace server } // namespace server

View File

@ -44,6 +44,7 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <future> #include <future>
#include <random>
#ifdef HAVE_SPDYLAY #ifdef HAVE_SPDYLAY
#include <spdylay/spdylay.h> #include <spdylay/spdylay.h>
@ -96,15 +97,53 @@ bool Config::is_rate_mode() const { return (this->rate != 0); }
bool Config::has_base_uri() const { return (!this->base_uri.empty()); } bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
Config config; Config config;
RequestStat::RequestStat() : data_offset(0), completed(false) {} namespace {
constexpr size_t MAX_SAMPLES = 1000000;
} // namespace
Stats::Stats(size_t req_todo, size_t nclients) Stats::Stats(size_t req_todo, size_t nclients)
: req_todo(0), req_started(0), req_done(0), req_success(0), : req_todo(req_todo), req_started(0), req_done(0), req_success(0),
req_status_success(0), req_failed(0), req_error(0), req_timedout(0), req_status_success(0), req_failed(0), req_error(0), req_timedout(0),
bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0), bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
status(), req_stats(req_todo), client_stats(nclients) {} status() {}
Stream::Stream() : status_success(-1) {} Stream::Stream() : req_stat{}, status_success(-1) {}
namespace {
std::random_device rd;
} // namespace
namespace {
std::mt19937 gen(rd());
} // namespace
namespace {
void sampling_init(Sampling &smp, size_t total, size_t max_samples) {
smp.n = 0;
if (total <= max_samples) {
smp.interval = 0.;
smp.point = 0.;
return;
}
smp.interval = static_cast<double>(total) / max_samples;
std::uniform_real_distribution<> dis(0., smp.interval);
smp.point = dis(gen);
}
} // namespace
namespace {
bool sampling_should_pick(Sampling &smp) {
return smp.interval == 0. || smp.n == ceil(smp.point);
}
} // namespace
namespace {
void sampling_advance_point(Sampling &smp) { smp.point += smp.interval; }
} // namespace
namespace { namespace {
void writecb(struct ev_loop *loop, ev_io *w, int revents) { void writecb(struct ev_loop *loop, ev_io *w, int revents) {
@ -118,12 +157,14 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
rv = client->connect(); rv = client->connect();
if (rv != 0) { if (rv != 0) {
client->fail(); client->fail();
delete client;
return; return;
} }
return; return;
} }
if (rv != 0) { if (rv != 0) {
client->fail(); client->fail();
delete client;
} }
} }
} // namespace } // namespace
@ -134,6 +175,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
client->restart_timeout(); client->restart_timeout();
if (client->do_read() != 0) { if (client->do_read() != 0) {
client->fail(); client->fail();
delete client;
return; return;
} }
writecb(loop, &client->wev, revents); writecb(loop, &client->wev, revents);
@ -155,14 +197,18 @@ void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
++req_todo; ++req_todo;
--worker->nreqs_rem; --worker->nreqs_rem;
} }
worker->clients.push_back( auto client =
make_unique<Client>(worker->next_client_id++, worker, req_todo)); make_unique<Client>(worker->next_client_id++, worker, req_todo);
auto &client = worker->clients.back();
++worker->nconns_made;
if (client->connect() != 0) { if (client->connect() != 0) {
std::cerr << "client could not connect to host" << std::endl; std::cerr << "client could not connect to host" << std::endl;
client->fail(); client->fail();
} else {
client.release();
} }
++worker->nconns_made; worker->report_rate_progress();
} }
if (worker->nconns_made >= worker->nclients) { if (worker->nconns_made >= worker->nclients) {
ev_timer_stop(worker->loop, w); ev_timer_stop(worker->loop, w);
@ -240,7 +286,7 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
} // namespace } // namespace
Client::Client(uint32_t id, Worker *worker, size_t req_todo) Client::Client(uint32_t id, Worker *worker, size_t req_todo)
: worker(worker), ssl(nullptr), next_addr(config.addrs), : cstat{}, worker(worker), ssl(nullptr), next_addr(config.addrs),
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo), current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
req_started(0), req_done(0), id(id), fd(-1), req_started(0), req_done(0), id(id), fd(-1),
new_connection_requested(false) { new_connection_requested(false) {
@ -268,6 +314,12 @@ Client::~Client() {
if (ssl) { if (ssl) {
SSL_free(ssl); SSL_free(ssl);
} }
if (sampling_should_pick(worker->client_smp)) {
sampling_advance_point(worker->client_smp);
worker->sample_client_stat(&cstat);
}
++worker->client_smp.n;
} }
int Client::do_read() { return readfn(*this); } int Client::do_read() { return readfn(*this); }
@ -420,9 +472,8 @@ void Client::disconnect() {
} }
int Client::submit_request() { int Client::submit_request() {
auto req_stat = &worker->stats.req_stats[worker->stats.req_started++]; ++worker->stats.req_started;
if (session->submit_request() != 0) {
if (session->submit_request(req_stat) != 0) {
return -1; return -1;
} }
@ -475,15 +526,6 @@ void Client::process_request_failure() {
} }
} }
void Client::report_progress() {
if (!worker->config->is_rate_mode() && worker->id == 0 &&
worker->stats.req_done % worker->progress_interval == 0) {
std::cout << "progress: "
<< worker->stats.req_done * 100 / worker->stats.req_todo
<< "% done" << std::endl;
}
}
namespace { namespace {
void print_server_tmp_key(SSL *ssl) { void print_server_tmp_key(SSL *ssl) {
// libressl does not have SSL_get_server_tmp_key // libressl does not have SSL_get_server_tmp_key
@ -607,28 +649,40 @@ void Client::on_status_code(int32_t stream_id, uint16_t status) {
} }
} }
void Client::on_stream_close(int32_t stream_id, bool success, void Client::on_stream_close(int32_t stream_id, bool success, bool final) {
RequestStat *req_stat, bool final) { auto req_stat = get_req_stat(stream_id);
if (!req_stat) {
return;
}
req_stat->stream_close_time = std::chrono::steady_clock::now(); req_stat->stream_close_time = std::chrono::steady_clock::now();
if (success) { if (success) {
req_stat->completed = true; req_stat->completed = true;
++worker->stats.req_success; ++worker->stats.req_success;
auto &cstat = worker->stats.client_stats[id];
++cstat.req_success; ++cstat.req_success;
}
++worker->stats.req_done;
++req_done;
if (success) {
if (streams[stream_id].status_success == 1) { if (streams[stream_id].status_success == 1) {
++worker->stats.req_status_success; ++worker->stats.req_status_success;
} else { } else {
++worker->stats.req_failed; ++worker->stats.req_failed;
} }
if (sampling_should_pick(worker->request_times_smp)) {
sampling_advance_point(worker->request_times_smp);
worker->sample_req_stat(req_stat);
}
// Count up in successful cases only
++worker->request_times_smp.n;
} else { } else {
++worker->stats.req_failed; ++worker->stats.req_failed;
++worker->stats.req_error; ++worker->stats.req_error;
} }
report_progress();
++worker->stats.req_done;
++req_done;
worker->report_progress();
streams.erase(stream_id); streams.erase(stream_id);
if (req_done == req_todo) { if (req_done == req_todo) {
terminate_session(); terminate_session();
@ -645,6 +699,15 @@ void Client::on_stream_close(int32_t stream_id, bool success,
} }
} }
RequestStat *Client::get_req_stat(int32_t stream_id) {
auto it = streams.find(stream_id);
if (it == std::end(streams)) {
return nullptr;
}
return &(*it).second.req_stat;
}
int Client::connection_made() { int Client::connection_made() {
if (ssl) { if (ssl) {
report_tls_info(); report_tls_info();
@ -750,7 +813,6 @@ int Client::connection_made() {
if (!config.timing_script) { if (!config.timing_script) {
auto nreq = auto nreq =
std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
for (; nreq > 0; --nreq) { for (; nreq > 0; --nreq) {
if (submit_request() != 0) { if (submit_request() != 0) {
process_request_failure(); process_request_failure();
@ -985,17 +1047,14 @@ void Client::record_request_time(RequestStat *req_stat) {
} }
void Client::record_connect_start_time() { void Client::record_connect_start_time() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_start_time = std::chrono::steady_clock::now(); cstat.connect_start_time = std::chrono::steady_clock::now();
} }
void Client::record_connect_time() { void Client::record_connect_time() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_time = std::chrono::steady_clock::now(); cstat.connect_time = std::chrono::steady_clock::now();
} }
void Client::record_ttfb() { void Client::record_ttfb() {
auto &cstat = worker->stats.client_stats[id];
if (recorded(cstat.ttfb)) { if (recorded(cstat.ttfb)) {
return; return;
} }
@ -1004,16 +1063,12 @@ void Client::record_ttfb() {
} }
void Client::clear_connect_times() { void Client::clear_connect_times() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_start_time = std::chrono::steady_clock::time_point(); cstat.connect_start_time = std::chrono::steady_clock::time_point();
cstat.connect_time = std::chrono::steady_clock::time_point(); cstat.connect_time = std::chrono::steady_clock::time_point();
cstat.ttfb = std::chrono::steady_clock::time_point(); cstat.ttfb = std::chrono::steady_clock::time_point();
} }
void Client::record_client_start_time() { void Client::record_client_start_time() {
auto &cstat = worker->stats.client_stats[id];
// Record start time only once at the very first connection is going // Record start time only once at the very first connection is going
// to be made. // to be made.
if (recorded(cstat.client_start_time)) { if (recorded(cstat.client_start_time)) {
@ -1024,8 +1079,6 @@ void Client::record_client_start_time() {
} }
void Client::record_client_end_time() { void Client::record_client_end_time() {
auto &cstat = worker->stats.client_stats[id];
// Unlike client_start_time, we overwrite client_end_time. This // Unlike client_start_time, we overwrite client_end_time. This
// handles multiple connect/disconnect for HTTP/1.1 benchmark. // handles multiple connect/disconnect for HTTP/1.1 benchmark.
cstat.client_end_time = std::chrono::steady_clock::now(); cstat.client_end_time = std::chrono::steady_clock::now();
@ -1036,20 +1089,36 @@ void Client::signal_write() { ev_io_start(worker->loop, &wev); }
void Client::try_new_connection() { new_connection_requested = true; } void Client::try_new_connection() { new_connection_requested = true; }
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
size_t rate, Config *config) size_t rate, size_t max_samples, Config *config)
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), : stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
config(config), id(id), tls_info_report_done(false), config(config), id(id), tls_info_report_done(false),
app_info_report_done(false), nconns_made(0), nclients(nclients), app_info_report_done(false), nconns_made(0), nclients(nclients),
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients), nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
rate(rate), next_client_id(0) { rate(rate), max_samples(max_samples), next_client_id(0) {
stats.req_todo = req_todo; if (!config->is_rate_mode()) {
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10); progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
} else {
progress_interval = std::max(static_cast<size_t>(1), nclients / 10);
}
// create timer that will go off every rate_period // create timer that will go off every rate_period
ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0., ev_timer_init(&timeout_watcher, rate_period_timeout_w_cb, 0.,
config->rate_period); config->rate_period);
timeout_watcher.data = this; timeout_watcher.data = this;
stats.req_stats.reserve(std::min(req_todo, max_samples));
stats.client_stats.reserve(std::min(nclients, max_samples));
sampling_init(request_times_smp, req_todo, max_samples);
sampling_init(client_smp, nclients, max_samples);
}
Worker::~Worker() {
ev_timer_stop(loop, &timeout_watcher);
ev_loop_destroy(loop);
}
void Worker::run() {
if (!config->is_rate_mode()) { if (!config->is_rate_mode()) {
for (size_t i = 0; i < nclients; ++i) { for (size_t i = 0; i < nclients; ++i) {
auto req_todo = nreqs_per_client; auto req_todo = nreqs_per_client;
@ -1057,26 +1126,12 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
++req_todo; ++req_todo;
--nreqs_rem; --nreqs_rem;
} }
clients.push_back(make_unique<Client>(next_client_id++, this, req_todo)); auto client = make_unique<Client>(next_client_id++, this, req_todo);
}
}
}
Worker::~Worker() {
ev_timer_stop(loop, &timeout_watcher);
// first clear clients so that io watchers are stopped before
// destructing ev_loop.
clients.clear();
ev_loop_destroy(loop);
}
void Worker::run() {
if (!config->is_rate_mode()) {
for (auto &client : clients) {
if (client->connect() != 0) { if (client->connect() != 0) {
std::cerr << "client could not connect to host" << std::endl; std::cerr << "client could not connect to host" << std::endl;
client->fail(); client->fail();
} else {
client.release();
} }
} }
} else { } else {
@ -1088,6 +1143,34 @@ void Worker::run() {
ev_run(loop, 0); ev_run(loop, 0);
} }
void Worker::sample_req_stat(RequestStat *req_stat) {
stats.req_stats.push_back(*req_stat);
assert(stats.req_stats.size() <= max_samples);
}
void Worker::sample_client_stat(ClientStat *cstat) {
stats.client_stats.push_back(*cstat);
assert(stats.client_stats.size() <= max_samples);
}
void Worker::report_progress() {
if (id != 0 || config->is_rate_mode() || stats.req_done % progress_interval) {
return;
}
std::cout << "progress: " << stats.req_done * 100 / stats.req_todo << "% done"
<< std::endl;
}
void Worker::report_rate_progress() {
if (id != 0 || nconns_made % progress_interval) {
return;
}
std::cout << "progress: " << nconns_made * 100 / nclients
<< "% of clients started" << std::endl;
}
namespace { namespace {
// Returns percentage of number of samples within mean +/- sd. // Returns percentage of number of samples within mean +/- sd.
double within_sd(const std::vector<double> &samples, double mean, double sd) { double within_sd(const std::vector<double> &samples, double mean, double sd) {
@ -1106,12 +1189,15 @@ double within_sd(const std::vector<double> &samples, double mean, double sd) {
namespace { namespace {
// Computes statistics using |samples|. The min, max, mean, sd, and // Computes statistics using |samples|. The min, max, mean, sd, and
// percentage of number of samples within mean +/- sd are computed. // percentage of number of samples within mean +/- sd are computed.
SDStat compute_time_stat(const std::vector<double> &samples) { // If |sampling| is true, this computes sample variance. Otherwise,
// population variance.
SDStat compute_time_stat(const std::vector<double> &samples,
bool sampling = false) {
if (samples.empty()) { if (samples.empty()) {
return {0.0, 0.0, 0.0, 0.0, 0.0}; return {0.0, 0.0, 0.0, 0.0, 0.0};
} }
// standard deviation calculated using Rapid calculation method: // standard deviation calculated using Rapid calculation method:
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods // https://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
double a = 0, q = 0; double a = 0, q = 0;
size_t n = 0; size_t n = 0;
double sum = 0; double sum = 0;
@ -1130,7 +1216,7 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
assert(n > 0); assert(n > 0);
res.mean = sum / n; res.mean = sum / n;
res.sd = sqrt(q / n); res.sd = sqrt(q / (sampling && n > 1 ? n - 1 : n));
res.within_sd = within_sd(samples, res.mean, res.sd); res.within_sd = within_sd(samples, res.mean, res.sd);
return res; return res;
@ -1140,18 +1226,29 @@ SDStat compute_time_stat(const std::vector<double> &samples) {
namespace { namespace {
SDStats SDStats
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) { process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
auto request_times_sampling = false;
auto client_times_sampling = false;
size_t nrequest_times = 0; size_t nrequest_times = 0;
size_t nclient_times = 0;
for (const auto &w : workers) { for (const auto &w : workers) {
nrequest_times += w->stats.req_stats.size(); nrequest_times += w->stats.req_stats.size();
if (w->request_times_smp.interval != 0.) {
request_times_sampling = true;
}
nclient_times += w->stats.client_stats.size();
if (w->client_smp.interval != 0.) {
client_times_sampling = true;
}
} }
std::vector<double> request_times; std::vector<double> request_times;
request_times.reserve(nrequest_times); request_times.reserve(nrequest_times);
std::vector<double> connect_times, ttfb_times, rps_values; std::vector<double> connect_times, ttfb_times, rps_values;
connect_times.reserve(config.nclients); connect_times.reserve(nclient_times);
ttfb_times.reserve(config.nclients); ttfb_times.reserve(nclient_times);
rps_values.reserve(config.nclients); rps_values.reserve(nclient_times);
for (const auto &w : workers) { for (const auto &w : workers) {
for (const auto &req_stat : w->stats.req_stats) { for (const auto &req_stat : w->stats.req_stats) {
@ -1195,8 +1292,10 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
} }
} }
return {compute_time_stat(request_times), compute_time_stat(connect_times), return {compute_time_stat(request_times, request_times_sampling),
compute_time_stat(ttfb_times), compute_time_stat(rps_values)}; compute_time_stat(connect_times, client_times_sampling),
compute_time_stat(ttfb_times, client_times_sampling),
compute_time_stat(rps_values, client_times_sampling)};
} }
} // namespace } // namespace
@ -1374,7 +1473,7 @@ void read_script_from_file(std::istream &infile,
namespace { namespace {
std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx, std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
size_t nreqs, size_t nclients, size_t nreqs, size_t nclients,
size_t rate) { size_t rate, size_t max_samples) {
std::stringstream rate_report; std::stringstream rate_report;
if (config.is_rate_mode() && nclients > rate) { if (config.is_rate_mode() && nclients > rate) {
rate_report << "Up to " << rate << " client(s) will be created every " rate_report << "Up to " << rate << " client(s) will be created every "
@ -1385,7 +1484,8 @@ std::unique_ptr<Worker> create_worker(uint32_t id, SSL_CTX *ssl_ctx,
<< " total client(s). " << rate_report.str() << nreqs << " total client(s). " << rate_report.str() << nreqs
<< " total requests" << std::endl; << " total requests" << std::endl;
return make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate, &config); return make_unique<Worker>(id, ssl_ctx, nreqs, nclients, rate, max_samples,
&config);
} }
} // namespace } // namespace
@ -1448,10 +1548,11 @@ Options:
URIs, if present, are ignored. Those in the first URI URIs, if present, are ignored. Those in the first URI
are used solely. Definition of a base URI overrides all are used solely. Definition of a base URI overrides all
scheme, host or port values. scheme, host or port values.
-m, --max-concurrent-streams=(auto|<N>) -m, --max-concurrent-streams=<N>
Max concurrent streams to issue per session. If "auto" Max concurrent streams to issue per session. When
is given, the number of given URIs is used. http/1.1 is used, this specifies the number of HTTP
Default: auto pipelining requests in-flight.
Default: 1
-w, --window-bits=<N> -w, --window-bits=<N>
Sets the stream level initial window size to (2**<N>)-1. Sets the stream level initial window size to (2**<N>)-1.
For SPDY, 2**<N> is used instead. For SPDY, 2**<N> is used instead.
@ -1626,11 +1727,7 @@ int main(int argc, char **argv) {
#endif // NOTHREADS #endif // NOTHREADS
break; break;
case 'm': case 'm':
if (util::strieq("auto", optarg)) {
config.max_concurrent_streams = -1;
} else {
config.max_concurrent_streams = strtoul(optarg, nullptr, 10); config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
}
break; break;
case 'w': case 'w':
case 'W': { case 'W': {
@ -1853,11 +1950,6 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (config.max_concurrent_streams == -1) {
config.max_concurrent_streams = reqlines.size();
}
assert(config.max_concurrent_streams > 0);
if (config.nreqs == 0) { if (config.nreqs == 0) {
std::cerr << "-n: the number of requests must be strictly greater than 0." std::cerr << "-n: the number of requests must be strictly greater than 0."
<< std::endl; << std::endl;
@ -2099,6 +2191,9 @@ int main(int argc, char **argv) {
size_t rate_per_thread = config.rate / config.nthreads; size_t rate_per_thread = config.rate / config.nthreads;
ssize_t rate_per_thread_rem = config.rate % config.nthreads; ssize_t rate_per_thread_rem = config.rate % config.nthreads;
size_t max_samples_per_thread =
std::max(static_cast<size_t>(256), MAX_SAMPLES / config.nthreads);
std::mutex mu; std::mutex mu;
std::condition_variable cv; std::condition_variable cv;
auto ready = false; auto ready = false;
@ -2131,7 +2226,8 @@ int main(int argc, char **argv) {
} }
} }
workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate)); workers.push_back(create_worker(i, ssl_ctx, nreqs, nclients, rate,
max_samples_per_thread));
auto &worker = workers.back(); auto &worker = workers.back();
futures.push_back( futures.push_back(
std::async(std::launch::async, [&worker, &mu, &cv, &ready]() { std::async(std::launch::async, [&worker, &mu, &cv, &ready]() {
@ -2161,7 +2257,8 @@ int main(int argc, char **argv) {
auto nreqs = auto nreqs =
config.timing_script ? config.nreqs * config.nclients : config.nreqs; config.timing_script ? config.nreqs * config.nclients : config.nreqs;
workers.push_back(create_worker(0, ssl_ctx, nreqs, nclients, rate)); workers.push_back(
create_worker(0, ssl_ctx, nreqs, nclients, rate, MAX_SAMPLES));
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
@ -2253,7 +2350,7 @@ time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10) << util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.sd) << std::setw(9) << util::format_duration(ts.ttfb.sd) << std::setw(9)
<< util::dtos(ts.ttfb.within_sd) << "%" << util::dtos(ts.ttfb.within_sd) << "%"
<< "\nreq/s (client) : " << std::setw(10) << ts.rps.min << " " << "\nreq/s : " << std::setw(10) << ts.rps.min << " "
<< std::setw(10) << ts.rps.max << " " << std::setw(10) << std::setw(10) << ts.rps.max << " " << std::setw(10)
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9) << ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
<< util::dtos(ts.rps.within_sd) << "%" << std::endl; << util::dtos(ts.rps.within_sd) << "%" << std::endl;

View File

@ -112,7 +112,6 @@ struct Config {
}; };
struct RequestStat { struct RequestStat {
RequestStat();
// time point when request was sent // time point when request was sent
std::chrono::steady_clock::time_point request_time; std::chrono::steady_clock::time_point request_time;
// time point when stream was closed // time point when stream was closed
@ -208,9 +207,21 @@ enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
struct Client; struct Client;
// We use systematic sampling method
struct Sampling {
// sampling interval
double interval;
// cumulative value of interval, and the next point is the integer
// rounded up from this value.
double point;
// number of samples seen, including discarded samples.
size_t n;
};
struct Worker { struct Worker {
std::vector<std::unique_ptr<Client>> clients;
Stats stats; Stats stats;
Sampling request_times_smp;
Sampling client_smp;
struct ev_loop *loop; struct ev_loop *loop;
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
Config *config; Config *config;
@ -226,24 +237,32 @@ struct Worker {
// at most nreqs_rem clients get an extra request // at most nreqs_rem clients get an extra request
size_t nreqs_rem; size_t nreqs_rem;
size_t rate; size_t rate;
// maximum number of samples in this worker thread
size_t max_samples;
ev_timer timeout_watcher; ev_timer timeout_watcher;
// The next client ID this worker assigns // The next client ID this worker assigns
uint32_t next_client_id; uint32_t next_client_id;
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients, Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
size_t rate, Config *config); size_t rate, size_t max_samples, Config *config);
~Worker(); ~Worker();
Worker(Worker &&o) = default; Worker(Worker &&o) = default;
void run(); void run();
void sample_req_stat(RequestStat *req_stat);
void sample_client_stat(ClientStat *cstat);
void report_progress();
void report_rate_progress();
}; };
struct Stream { struct Stream {
RequestStat req_stat;
int status_success; int status_success;
Stream(); Stream();
}; };
struct Client { struct Client {
std::unordered_map<int32_t, Stream> streams; std::unordered_map<int32_t, Stream> streams;
ClientStat cstat;
std::unique_ptr<Session> session; std::unique_ptr<Session> session;
ev_io wev; ev_io wev;
ev_io rev; ev_io rev;
@ -288,7 +307,6 @@ struct Client {
void process_request_failure(); void process_request_failure();
void process_timedout_streams(); void process_timedout_streams();
void process_abandoned_streams(); void process_abandoned_streams();
void report_progress();
void report_tls_info(); void report_tls_info();
void report_app_info(); void report_app_info();
void terminate_session(); void terminate_session();
@ -318,8 +336,12 @@ struct Client {
// |success| == true means that the request/response was exchanged // |success| == true means that the request/response was exchanged
// |successfully, but it does not mean response carried successful // |successfully, but it does not mean response carried successful
// |HTTP status code. // |HTTP status code.
void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat, void on_stream_close(int32_t stream_id, bool success, bool final = false);
bool final = false); // Returns RequestStat for |stream_id|. This function must be
// called after on_request(stream_id), and before
// on_stream_close(stream_id, ...). Otherwise, this will return
// nullptr.
RequestStat *get_req_stat(int32_t stream_id);
void record_request_time(RequestStat *req_stat); void record_request_time(RequestStat *req_stat);
void record_connect_start_time(); void record_connect_start_time();

View File

@ -80,9 +80,11 @@ int htp_msg_completecb(http_parser *htp) {
auto client = session->get_client(); auto client = session->get_client();
auto final = http_should_keep_alive(htp) == 0; auto final = http_should_keep_alive(htp) == 0;
client->on_stream_close(session->stream_resp_counter_, true, auto req_stat = client->get_req_stat(session->stream_resp_counter_);
session->req_stats_[session->stream_resp_counter_],
final); assert(req_stat);
client->on_stream_close(session->stream_resp_counter_, true, final);
session->stream_resp_counter_ += 2; session->stream_resp_counter_ += 2;
@ -150,7 +152,7 @@ http_parser_settings htp_hooks = {
void Http1Session::on_connect() { client_->signal_write(); } void Http1Session::on_connect() { client_->signal_write(); }
int Http1Session::submit_request(RequestStat *req_stat) { int Http1Session::submit_request() {
auto config = client_->worker->config; auto config = client_->worker->config;
const auto &req = config->h1reqs[client_->reqidx]; const auto &req = config->h1reqs[client_->reqidx];
client_->reqidx++; client_->reqidx++;
@ -159,13 +161,13 @@ int Http1Session::submit_request(RequestStat *req_stat) {
client_->reqidx = 0; client_->reqidx = 0;
} }
assert(req_stat); client_->on_request(stream_req_counter_);
auto req_stat = client_->get_req_stat(stream_req_counter_);
client_->record_request_time(req_stat); client_->record_request_time(req_stat);
client_->wb.write(req.c_str(), req.size()); client_->wb.write(req.c_str(), req.size());
client_->on_request(stream_req_counter_);
req_stats_[stream_req_counter_] = req_stat;
// increment for next request // increment for next request
stream_req_counter_ += 2; stream_req_counter_ += 2;

View File

@ -38,14 +38,13 @@ public:
Http1Session(Client *client); Http1Session(Client *client);
virtual ~Http1Session(); virtual ~Http1Session();
virtual void on_connect(); virtual void on_connect();
virtual int submit_request(RequestStat *req_stat); virtual int submit_request();
virtual int on_read(const uint8_t *data, size_t len); virtual int on_read(const uint8_t *data, size_t len);
virtual int on_write(); virtual int on_write();
virtual void terminate(); virtual void terminate();
Client *get_client(); Client *get_client();
int32_t stream_req_counter_; int32_t stream_req_counter_;
int32_t stream_resp_counter_; int32_t stream_resp_counter_;
std::unordered_map<int32_t, RequestStat *> req_stats_;
private: private:
Client *client_; Client *client_;

View File

@ -89,12 +89,25 @@ namespace {
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data) { uint32_t error_code, void *user_data) {
auto client = static_cast<Client *>(user_data); auto client = static_cast<Client *>(user_data);
auto req_stat = static_cast<RequestStat *>( client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR);
nghttp2_session_get_stream_user_data(session, stream_id));
if (!req_stat) { return 0;
}
} // 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; 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; return 0;
} }
} // namespace } // namespace
@ -108,9 +121,7 @@ int before_frame_send_callback(nghttp2_session *session,
} }
auto client = static_cast<Client *>(user_data); auto client = static_cast<Client *>(user_data);
client->on_request(frame->hd.stream_id); auto req_stat = client->get_req_stat(frame->hd.stream_id);
auto req_stat = static_cast<RequestStat *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
assert(req_stat); assert(req_stat);
client->record_request_time(req_stat); client->record_request_time(req_stat);
@ -124,8 +135,7 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
nghttp2_data_source *source, void *user_data) { nghttp2_data_source *source, void *user_data) {
auto client = static_cast<Client *>(user_data); auto client = static_cast<Client *>(user_data);
auto config = client->worker->config; auto config = client->worker->config;
auto req_stat = static_cast<RequestStat *>( auto req_stat = client->get_req_stat(stream_id);
nghttp2_session_get_stream_user_data(session, stream_id));
assert(req_stat); assert(req_stat);
ssize_t nread; ssize_t nread;
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) == while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
@ -183,6 +193,9 @@ void Http2Session::on_connect() {
nghttp2_session_callbacks_set_on_header_callback(callbacks, nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback); on_header_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_before_frame_send_callback( nghttp2_session_callbacks_set_before_frame_send_callback(
callbacks, before_frame_send_callback); callbacks, before_frame_send_callback);
@ -212,7 +225,7 @@ void Http2Session::on_connect() {
client_->signal_write(); client_->signal_write();
} }
int Http2Session::submit_request(RequestStat *req_stat) { int Http2Session::submit_request() {
if (nghttp2_session_check_request_allowed(session_) == 0) { if (nghttp2_session_check_request_allowed(session_) == 0) {
return -1; return -1;
} }
@ -228,11 +241,13 @@ int Http2Session::submit_request(RequestStat *req_stat) {
auto stream_id = auto stream_id =
nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(), nghttp2_submit_request(session_, nullptr, nva.data(), nva.size(),
config->data_fd == -1 ? nullptr : &prd, req_stat); config->data_fd == -1 ? nullptr : &prd, nullptr);
if (stream_id < 0) { if (stream_id < 0) {
return -1; return -1;
} }
client_->on_request(stream_id);
return 0; return 0;
} }

View File

@ -38,7 +38,7 @@ public:
Http2Session(Client *client); Http2Session(Client *client);
virtual ~Http2Session(); virtual ~Http2Session();
virtual void on_connect(); virtual void on_connect();
virtual int submit_request(RequestStat *req_stat); virtual int submit_request();
virtual int on_read(const uint8_t *data, size_t len); virtual int on_read(const uint8_t *data, size_t len);
virtual int on_write(); virtual int on_write();
virtual void terminate(); virtual void terminate();

View File

@ -41,7 +41,7 @@ public:
// Called when the connection was made. // Called when the connection was made.
virtual void on_connect() = 0; virtual void on_connect() = 0;
// Called when one request must be issued. // Called when one request must be issued.
virtual int submit_request(RequestStat *req_stat) = 0; virtual int submit_request() = 0;
// Called when incoming bytes are available. The subclass has to // Called when incoming bytes are available. The subclass has to
// return the number of bytes read. // return the number of bytes read.
virtual int on_read(const uint8_t *data, size_t len) = 0; virtual int on_read(const uint8_t *data, size_t len) = 0;

View File

@ -48,9 +48,7 @@ void before_ctrl_send_callback(spdylay_session *session,
return; return;
} }
client->on_request(frame->syn_stream.stream_id); client->on_request(frame->syn_stream.stream_id);
auto req_stat = auto req_stat = client->get_req_stat(frame->syn_stream.stream_id);
static_cast<RequestStat *>(spdylay_session_get_stream_user_data(
session, frame->syn_stream.stream_id));
client->record_request_time(req_stat); client->record_request_time(req_stat);
} }
} // namespace } // namespace
@ -104,9 +102,7 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
spdylay_status_code status_code, spdylay_status_code status_code,
void *user_data) { void *user_data) {
auto client = static_cast<Client *>(user_data); auto client = static_cast<Client *>(user_data);
auto req_stat = static_cast<RequestStat *>( client->on_stream_close(stream_id, status_code == SPDYLAY_OK);
spdylay_session_get_stream_user_data(session, stream_id));
client->on_stream_close(stream_id, status_code == SPDYLAY_OK, req_stat);
} }
} // namespace } // namespace
@ -130,8 +126,7 @@ ssize_t file_read_callback(spdylay_session *session, int32_t stream_id,
spdylay_data_source *source, void *user_data) { spdylay_data_source *source, void *user_data) {
auto client = static_cast<Client *>(user_data); auto client = static_cast<Client *>(user_data);
auto config = client->worker->config; auto config = client->worker->config;
auto req_stat = static_cast<RequestStat *>( auto req_stat = client->get_req_stat(stream_id);
spdylay_session_get_stream_user_data(session, stream_id));
ssize_t nread; ssize_t nread;
while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) == while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) ==
@ -185,7 +180,7 @@ void SpdySession::on_connect() {
client_->signal_write(); client_->signal_write();
} }
int SpdySession::submit_request(RequestStat *req_stat) { int SpdySession::submit_request() {
int rv; int rv;
auto config = client_->worker->config; auto config = client_->worker->config;
auto &nv = config->nv[client_->reqidx++]; auto &nv = config->nv[client_->reqidx++];
@ -197,7 +192,7 @@ int SpdySession::submit_request(RequestStat *req_stat) {
spdylay_data_provider prd{{0}, file_read_callback}; spdylay_data_provider prd{{0}, file_read_callback};
rv = spdylay_submit_request(session_, 0, nv.data(), rv = spdylay_submit_request(session_, 0, nv.data(),
config->data_fd == -1 ? nullptr : &prd, req_stat); config->data_fd == -1 ? nullptr : &prd, nullptr);
if (rv != 0) { if (rv != 0) {
return -1; return -1;

View File

@ -40,7 +40,7 @@ public:
SpdySession(Client *client, uint16_t spdy_version); SpdySession(Client *client, uint16_t spdy_version);
virtual ~SpdySession(); virtual ~SpdySession();
virtual void on_connect(); virtual void on_connect();
virtual int submit_request(RequestStat *req_stat); virtual int submit_request();
virtual int on_read(const uint8_t *data, size_t len); virtual int on_read(const uint8_t *data, size_t len);
virtual int on_write(); virtual int on_write();
virtual void terminate(); virtual void terminate();

View File

@ -113,6 +113,8 @@ std::string get_status_string(unsigned int status_code) {
return "429 Too Many Requests"; return "429 Too Many Requests";
case 431: case 431:
return "431 Request Header Fields Too Large"; return "431 Request Header Fields Too Large";
case 451:
return "451 Unavailable For Legal Reasons";
case 500: case 500:
return "500 Internal Server Error"; return "500 Internal Server Error";
case 501: case 501:
@ -215,6 +217,8 @@ const char *stringify_status(unsigned int status_code) {
return "429"; return "429";
case 431: case 431:
return "431"; return "431";
case 451:
return "451";
case 500: case 500:
return "500"; return "500";
case 501: case 501:

View File

@ -144,6 +144,12 @@ public:
// and session is terminated. // and session is terminated.
void on_error(error_cb cb) const; void on_error(error_cb cb) const;
// Sets connect timeout, which defaults to 60 seconds.
void connect_timeout(const boost::posix_time::time_duration &t);
// Sets read timeout, which defaults to 60 seconds.
void read_timeout(const boost::posix_time::time_duration &t);
// Shutdowns connection. // Shutdowns connection.
void shutdown() const; void shutdown() const;
@ -177,7 +183,7 @@ public:
generator_cb cb, header_map h = header_map{}) const; generator_cb cb, header_map h = header_map{}) const;
private: private:
std::unique_ptr<session_impl> impl_; std::shared_ptr<session_impl> impl_;
}; };
// configure |tls_ctx| for client use. Currently, we just set NPN // configure |tls_ctx| for client use. Currently, we just set NPN

View File

@ -59,6 +59,9 @@ public:
// Application must not call this directly. // Application must not call this directly.
request_impl &impl() const; request_impl &impl() const;
// Returns the remote endpoint of the request
const boost::asio::ip::tcp::endpoint &remote_endpoint() const;
private: private:
std::unique_ptr<request_impl> impl_; std::unique_ptr<request_impl> impl_;
}; };
@ -195,12 +198,22 @@ public:
// connections. // connections.
void backlog(int backlog); void backlog(int backlog);
// Sets TLS handshake timeout, which defaults to 60 seconds.
void tls_handshake_timeout(const boost::posix_time::time_duration &t);
// Sets read timeout, which defaults to 60 seconds.
void read_timeout(const boost::posix_time::time_duration &t);
// Gracefully stop http2 server // Gracefully stop http2 server
void stop(); void stop();
// Join on http2 server and wait for it to fully stop // Join on http2 server and wait for it to fully stop
void join(); void join();
// Get access to the io_service objects.
const std::vector<std::shared_ptr<boost::asio::io_service>> &
io_services() const;
private: private:
std::unique_ptr<http2_impl> impl_; std::unique_ptr<http2_impl> impl_;
}; };

View File

@ -164,6 +164,8 @@ Options:
Path to file that contains MIME media types and the Path to file that contains MIME media types and the
extensions that represent them. extensions that represent them.
Default: )" << config.mime_types_file << R"( Default: )" << config.mime_types_file << R"(
--no-content-length
Don't send content-length header field.
--version Display version information and exit. --version Display version information and exit.
-h, --help Display this help and exit. -h, --help Display this help and exit.
@ -209,6 +211,7 @@ int main(int argc, char **argv) {
{"hexdump", no_argument, &flag, 7}, {"hexdump", no_argument, &flag, 7},
{"echo-upload", no_argument, &flag, 8}, {"echo-upload", no_argument, &flag, 8},
{"mime-types-file", required_argument, &flag, 9}, {"mime-types-file", required_argument, &flag, 9},
{"no-content-length", no_argument, &flag, 10},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options, int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
@ -340,6 +343,10 @@ int main(int argc, char **argv) {
mime_types_file_set_manually = true; mime_types_file_set_manually = true;
config.mime_types_file = optarg; config.mime_types_file = optarg;
break; break;
case 10:
// no-content-length option
config.no_content_length = true;
break;
} }
break; break;
default: default:

View File

@ -1610,14 +1610,15 @@ HTTP:
used several times to specify multiple header fields. used several times to specify multiple header fields.
Example: --add-response-header="foo: bar" Example: --add-response-header="foo: bar"
--header-field-buffer=<SIZE> --header-field-buffer=<SIZE>
Set maximum buffer size for incoming HTTP header field Set maximum buffer size for incoming HTTP request header
list. This is the sum of header name and value in field list. This is the sum of header name and value in
bytes. bytes.
Default: )" Default: )"
<< util::utos_with_unit(get_config()->header_field_buffer) << R"( << util::utos_with_unit(get_config()->header_field_buffer) << R"(
--max-header-fields=<N> --max-header-fields=<N>
Set maximum number of incoming HTTP header fields, which Set maximum number of incoming HTTP request header
appear in one request or response header field list. fields, which appear in one request or response header
field list.
Default: )" << get_config()->max_header_fields << R"( Default: )" << get_config()->max_header_fields << R"(
Debug: Debug:

View File

@ -44,4 +44,8 @@
#define DIE() _Exit(EXIT_FAILURE) #define DIE() _Exit(EXIT_FAILURE)
#if defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
inline int initgroups(const char *user, gid_t group) { return 0; }
#endif // defined(HAVE_DECL_INITGROUPS) && !HAVE_DECL_INITGROUPS
#endif // SHRPX_H #endif // SHRPX_H

View File

@ -718,6 +718,34 @@ void ClientHandler::direct_http2_upgrade() {
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) { int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
auto upstream = make_unique<Http2Upstream>(this); auto upstream = make_unique<Http2Upstream>(this);
auto output = upstream->get_response_buf();
// We might have written non-final header in response_buf, in this
// case, response_state is still INITIAL. If this non-final header
// and upgrade header fit in output buffer, do upgrade. Otherwise,
// to avoid to send this non-final header as response body in HTTP/2
// upstream, fail upgrade.
auto downstream = http->get_downstream();
auto input = downstream->get_response_buf();
static constexpr char res[] =
"HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
"\r\n";
auto required_size = str_size(res) + input->rleft();
if (output->wleft() < required_size) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this)
<< "HTTP Upgrade failed because of insufficient buffer space: need "
<< required_size << ", available " << output->wleft();
}
return -1;
}
if (upstream->upgrade_upstream(http) != 0) { if (upstream->upgrade_upstream(http) != 0) {
return -1; return -1;
} }
@ -729,11 +757,11 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
on_read_ = &ClientHandler::upstream_http2_connhd_read; on_read_ = &ClientHandler::upstream_http2_connhd_read;
write_ = &ClientHandler::write_clear; write_ = &ClientHandler::write_clear;
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n" auto nread =
"Connection: Upgrade\r\n" downstream->get_response_buf()->remove(output->last, output->wleft());
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n" output->write(nread);
"\r\n";
upstream->get_response_buf()->write(res, sizeof(res) - 1); output->write(res, str_size(res));
upstream_ = std::move(upstream); upstream_ = std::move(upstream);
signal_write(); signal_write();

View File

@ -276,11 +276,9 @@ std::string read_passwd_from_file(const char *filename) {
} }
std::pair<std::string, std::string> parse_header(const char *optarg) { std::pair<std::string, std::string> parse_header(const char *optarg) {
// We skip possible ":" at the start of optarg. const auto *colon = strchr(optarg, ':');
const auto *colon = strchr(optarg + 1, ':');
// name = ":" is not allowed if (colon == nullptr || colon == optarg) {
if (colon == nullptr || (optarg[0] == ':' && colon == optarg + 1)) {
return {"", ""}; return {"", ""};
} }
@ -291,7 +289,14 @@ std::pair<std::string, std::string> parse_header(const char *optarg) {
auto p = std::make_pair(std::string(optarg, colon), auto p = std::make_pair(std::string(optarg, colon),
std::string(value, strlen(value))); std::string(value, strlen(value)));
util::inp_strlower(p.first); util::inp_strlower(p.first);
util::inp_strlower(p.second);
if (!nghttp2_check_header_name(
reinterpret_cast<const uint8_t *>(p.first.c_str()), p.first.size()) ||
!nghttp2_check_header_value(
reinterpret_cast<const uint8_t *>(p.second.c_str()),
p.second.size())) {
return {"", ""};
}
return p; return p;
} }
@ -1800,7 +1805,7 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_ADD_RESPONSE_HEADER: { case SHRPX_OPTID_ADD_RESPONSE_HEADER: {
auto p = parse_header(optarg); auto p = parse_header(optarg);
if (p.first.empty()) { if (p.first.empty()) {
LOG(ERROR) << opt << ": header field name is empty: " << optarg; LOG(ERROR) << opt << ": invalid header field: " << optarg;
return -1; return -1;
} }
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) { if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {

View File

@ -46,8 +46,7 @@ void test_shrpx_config_parse_header(void) {
CU_ASSERT("b" == p.second); CU_ASSERT("b" == p.second);
p = parse_header(":a: b"); p = parse_header(":a: b");
CU_ASSERT(":a" == p.first); CU_ASSERT(p.first.empty());
CU_ASSERT("b" == p.second);
p = parse_header("a: :b"); p = parse_header("a: :b");
CU_ASSERT("a" == p.first); CU_ASSERT("a" == p.first);
@ -59,6 +58,12 @@ void test_shrpx_config_parse_header(void) {
p = parse_header("alpha: bravo charlie"); p = parse_header("alpha: bravo charlie");
CU_ASSERT("alpha" == p.first); CU_ASSERT("alpha" == p.first);
CU_ASSERT("bravo charlie" == p.second); CU_ASSERT("bravo charlie" == p.second);
p = parse_header("a,: b");
CU_ASSERT(p.first.empty());
p = parse_header("a: b\x0a");
CU_ASSERT(p.first.empty());
} }
void test_shrpx_config_parse_log_format(void) { void test_shrpx_config_parse_log_format(void) {

View File

@ -757,26 +757,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS && auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!downstream->get_expect_final_response(); !downstream->get_expect_final_response();
if (downstream->get_response_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< downstream->get_response_headers_sum() + namelen + valuelen
<< ", num=" << downstream->get_response_headers().size() + 1;
}
if (trailer) {
// we don't care trailer part exceeds header size limit; just
// discard it.
return 0;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (trailer) { if (trailer) {
// just store header fields for trailer part // just store header fields for trailer part
downstream->add_response_trailer(name, namelen, value, valuelen, downstream->add_response_trailer(name, namelen, value, valuelen,
@ -803,21 +783,6 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
assert(promised_downstream); assert(promised_downstream);
if (promised_downstream->get_request_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
promised_downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, promised_downstream)
<< "Too large or many header field size="
<< promised_downstream->get_request_headers_sum() + namelen +
valuelen << ", num="
<< promised_downstream->get_request_headers().size() + 1;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(name, namelen); auto token = http2::lookup_token(name, namelen);
promised_downstream->add_request_header(name, namelen, value, valuelen, promised_downstream->add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, flags & NGHTTP2_NV_FLAG_NO_INDEX,

View File

@ -169,7 +169,8 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
if (downstream->get_request_headers_sum() + namelen + valuelen > if (downstream->get_request_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer || get_config()->header_field_buffer ||
downstream->get_request_headers().size() >= downstream->get_request_headers().size() +
downstream->get_request_trailers().size() >=
get_config()->max_header_fields) { get_config()->max_header_fields) {
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
return 0; return 0;

View File

@ -562,29 +562,10 @@ int htp_hdrs_completecb(http_parser *htp) {
namespace { namespace {
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data); auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum() + len;
}
return -1;
}
if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) { if (downstream->get_response_header_key_prev()) {
downstream->append_last_response_header_key(data, len); downstream->append_last_response_header_key(data, len);
} else { } else {
if (downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too many header field num="
<< downstream->get_response_headers().size() + 1;
}
return -1;
}
downstream->add_response_header(std::string(data, len), ""); downstream->add_response_header(std::string(data, len), "");
} }
} else { } else {
@ -592,15 +573,6 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (downstream->get_response_trailer_key_prev()) { if (downstream->get_response_trailer_key_prev()) {
downstream->append_last_response_trailer_key(data, len); downstream->append_last_response_trailer_key(data, len);
} else { } else {
if (downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too many header field num="
<< downstream->get_response_headers().size() + 1;
}
return -1;
}
downstream->add_response_trailer(std::string(data, len), ""); downstream->add_response_trailer(std::string(data, len), "");
} }
} }
@ -611,14 +583,6 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
namespace { namespace {
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data); auto downstream = static_cast<Downstream *>(htp->data);
if (downstream->get_response_headers_sum() + len >
get_config()->header_field_buffer) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large header block size="
<< downstream->get_response_headers_sum() + len;
}
return -1;
}
if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_header_key_prev()) { if (downstream->get_response_header_key_prev()) {
downstream->set_last_response_header_value(data, len); downstream->set_last_response_header_value(data, len);

View File

@ -146,7 +146,8 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (downstream->get_request_trailer_key_prev()) { if (downstream->get_request_trailer_key_prev()) {
downstream->append_last_request_trailer_key(data, len); downstream->append_last_request_trailer_key(data, len);
} else { } else {
if (downstream->get_request_headers().size() >= if (downstream->get_request_headers().size() +
downstream->get_request_trailers().size() >=
get_config()->max_header_fields) { get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too many header field num=" ULOG(INFO, upstream) << "Too many header field num="
@ -409,11 +410,6 @@ int htp_msg_completecb(http_parser *htp) {
if (handler->get_http2_upgrade_allowed() && if (handler->get_http2_upgrade_allowed() &&
downstream->get_http2_upgrade_request() && downstream->get_http2_upgrade_request() &&
// we may write non-final header in response_buf, in this case,
// response_state is still INITIAL. So don't upgrade in this
// case, otherwise we end up send this non-final header as
// response body in HTTP/2 upstream.
downstream->get_response_buf()->rleft() == 0 &&
handler->perform_http2_upgrade(upstream) != 0) { handler->perform_http2_upgrade(upstream) != 0) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed"; ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";

View File

@ -169,6 +169,8 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]); header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
} }
// spdy does not define usage of trailer fields, and we ignores
// them.
if (header_buffer > get_config()->header_field_buffer || if (header_buffer > get_config()->header_field_buffer ||
num_headers > get_config()->max_header_fields) { num_headers > get_config()->max_header_fields) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);

View File

@ -41,6 +41,7 @@
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/dh.h>
#include <nghttp2/nghttp2.h> #include <nghttp2/nghttp2.h>

View File

@ -80,12 +80,7 @@ LibsslGlobalLock::LibsslGlobalLock() {
LibsslGlobalLock::~LibsslGlobalLock() { ssl_global_locks.clear(); } LibsslGlobalLock::~LibsslGlobalLock() { ssl_global_locks.clear(); }
const char *get_tls_protocol(SSL *ssl) { const char *get_tls_protocol(SSL *ssl) {
auto session = SSL_get_session(ssl); switch (SSL_version(ssl)) {
if (!session) {
return "unknown";
}
switch (session->ssl_version) {
case SSL2_VERSION: case SSL2_VERSION:
return "SSLv2"; return "SSLv2";
case SSL3_VERSION: case SSL3_VERSION:
@ -113,10 +108,12 @@ TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl) {
tls_info->cipher = SSL_get_cipher_name(ssl); tls_info->cipher = SSL_get_cipher_name(ssl);
tls_info->protocol = get_tls_protocol(ssl); tls_info->protocol = get_tls_protocol(ssl);
tls_info->session_id = session->session_id;
tls_info->session_id_length = session->session_id_length;
tls_info->session_reused = SSL_session_reused(ssl); tls_info->session_reused = SSL_session_reused(ssl);
unsigned int session_id_length;
tls_info->session_id = SSL_SESSION_get_id(session, &session_id_length);
tls_info->session_id_length = session_id_length;
return tls_info; return tls_info;
} }

View File

@ -89,6 +89,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_recv_headers_with_priority) || test_nghttp2_session_recv_headers_with_priority) ||
!CU_add_test(pSuite, "session_recv_headers_early_response", !CU_add_test(pSuite, "session_recv_headers_early_response",
test_nghttp2_session_recv_headers_early_response) || test_nghttp2_session_recv_headers_early_response) ||
!CU_add_test(pSuite, "session_server_recv_push_response",
test_nghttp2_session_server_recv_push_response) ||
!CU_add_test(pSuite, "session_recv_premature_headers", !CU_add_test(pSuite, "session_recv_premature_headers",
test_nghttp2_session_recv_premature_headers) || test_nghttp2_session_recv_premature_headers) ||
!CU_add_test(pSuite, "session_recv_unknown_frame", !CU_add_test(pSuite, "session_recv_unknown_frame",
@ -128,6 +130,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_on_window_update_received) || test_nghttp2_session_on_window_update_received) ||
!CU_add_test(pSuite, "session_on_data_received", !CU_add_test(pSuite, "session_on_data_received",
test_nghttp2_session_on_data_received) || test_nghttp2_session_on_data_received) ||
!CU_add_test(pSuite, "session_on_data_received_fail_fast",
test_nghttp2_session_on_data_received_fail_fast) ||
!CU_add_test(pSuite, "session_send_headers_start_stream", !CU_add_test(pSuite, "session_send_headers_start_stream",
test_nghttp2_session_send_headers_start_stream) || test_nghttp2_session_send_headers_start_stream) ||
!CU_add_test(pSuite, "session_send_headers_reply", !CU_add_test(pSuite, "session_send_headers_reply",
@ -205,8 +209,6 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_reply_fail) || test_nghttp2_session_reply_fail) ||
!CU_add_test(pSuite, "session_max_concurrent_streams", !CU_add_test(pSuite, "session_max_concurrent_streams",
test_nghttp2_session_max_concurrent_streams) || test_nghttp2_session_max_concurrent_streams) ||
!CU_add_test(pSuite, "session_stream_close_on_headers_push",
test_nghttp2_session_stream_close_on_headers_push) ||
!CU_add_test(pSuite, "session_stop_data_with_rst_stream", !CU_add_test(pSuite, "session_stop_data_with_rst_stream",
test_nghttp2_session_stop_data_with_rst_stream) || test_nghttp2_session_stop_data_with_rst_stream) ||
!CU_add_test(pSuite, "session_defer_data", !CU_add_test(pSuite, "session_defer_data",
@ -291,6 +293,10 @@ int main(int argc _U_, char *argv[] _U_) {
!CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) || !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
!CU_add_test(pSuite, "session_change_stream_priority", !CU_add_test(pSuite, "session_change_stream_priority",
test_nghttp2_session_change_stream_priority) || test_nghttp2_session_change_stream_priority) ||
!CU_add_test(pSuite, "session_repeated_priority_change",
test_nghttp2_session_repeated_priority_change) ||
!CU_add_test(pSuite, "session_repeated_priority_submission",
test_nghttp2_session_repeated_priority_submission) ||
!CU_add_test(pSuite, "http_mandatory_headers", !CU_add_test(pSuite, "http_mandatory_headers",
test_nghttp2_http_mandatory_headers) || test_nghttp2_http_mandatory_headers) ||
!CU_add_test(pSuite, "http_content_length", !CU_add_test(pSuite, "http_content_length",

View File

@ -342,16 +342,6 @@ static int send_data_callback(nghttp2_session *session _U_,
return 0; return 0;
} }
/* static void no_stream_user_data_stream_close_callback */
/* (nghttp2_session *session, */
/* int32_t stream_id, */
/* nghttp2_error_code error_code, */
/* void *user_data) */
/* { */
/* my_user_data* my_data = (my_user_data*)user_data; */
/* ++my_data->stream_close_cb_called; */
/* } */
static ssize_t block_count_send_callback(nghttp2_session *session _U_, static ssize_t block_count_send_callback(nghttp2_session *session _U_,
const uint8_t *data _U_, size_t len, const uint8_t *data _U_, size_t len,
int flags _U_, void *user_data) { int flags _U_, void *user_data) {
@ -1487,6 +1477,58 @@ void test_nghttp2_session_recv_headers_early_response(void) {
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
} }
void test_nghttp2_session_server_recv_push_response(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_bufs bufs;
nghttp2_buf *buf;
ssize_t rv;
my_user_data ud;
nghttp2_mem *mem;
nghttp2_frame frame;
nghttp2_hd_deflater deflater;
nghttp2_nv *nva;
size_t nvlen;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_hd_deflate_init(&deflater, mem);
nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL);
nvlen = ARRLEN(resnv);
nghttp2_nv_array_copy(&nva, resnv, nvlen, mem);
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
NGHTTP2_HCAT_HEADERS, &pri_spec_default, nva,
nvlen);
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv);
CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
nghttp2_frame_headers_free(&frame.headers, mem);
buf = &bufs.head->buf;
ud.invalid_frame_recv_cb_called = 0;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
CU_ASSERT(1 == ud.invalid_frame_recv_cb_called);
nghttp2_bufs_free(&bufs);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
}
void test_nghttp2_session_recv_premature_headers(void) { void test_nghttp2_session_recv_premature_headers(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
@ -2329,6 +2371,25 @@ void test_nghttp2_session_on_request_headers_received(void) {
nghttp2_frame_headers_free(&frame.headers, mem); nghttp2_frame_headers_free(&frame.headers, mem);
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_session_server_new(&session, &callbacks, &user_data);
/* HEADERS to closed stream */
stream = open_stream(session, 1);
session->last_recv_stream_id = 1;
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
nghttp2_session_on_request_headers_received(session, &frame));
CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
nghttp2_frame_headers_free(&frame.headers, mem);
nghttp2_session_del(session);
} }
void test_nghttp2_session_on_response_headers_received(void) { void test_nghttp2_session_on_response_headers_received(void) {
@ -2822,14 +2883,20 @@ void test_nghttp2_session_on_push_promise_received(void) {
CU_ASSERT(1 == session->num_incoming_reserved_streams); CU_ASSERT(1 == session->num_incoming_reserved_streams);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4)); CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
CU_ASSERT(4 == item->frame.hd.stream_id); CU_ASSERT(NGHTTP2_STREAM_CLOSED == item->frame.goaway.error_code);
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(4 == session->last_recv_stream_id); CU_ASSERT(4 == session->last_recv_stream_id);
nghttp2_session_del(session);
nghttp2_session_client_new(&session, &callbacks, &user_data);
stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default,
NGHTTP2_STREAM_OPENING, NULL);
/* Attempt to PUSH_PROMISE against stream in closing state */ /* Attempt to PUSH_PROMISE against stream in closing state */
stream->shut_flags = NGHTTP2_SHUT_NONE;
stream->state = NGHTTP2_STREAM_CLOSING; stream->state = NGHTTP2_STREAM_CLOSING;
frame.push_promise.promised_stream_id = 6; frame.push_promise.promised_stream_id = 6;
@ -2839,7 +2906,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
nghttp2_session_on_push_promise_received(session, &frame)); nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.begin_headers_cb_called); CU_ASSERT(0 == user_data.begin_headers_cb_called);
CU_ASSERT(1 == session->num_incoming_reserved_streams); CU_ASSERT(0 == session->num_incoming_reserved_streams);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6)); CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type); CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
@ -2847,7 +2914,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code); CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(0 == nghttp2_session_send(session));
/* Attempt to PUSH_PROMISE against non-existent stream */ /* Attempt to PUSH_PROMISE against idle stream */
frame.hd.stream_id = 3; frame.hd.stream_id = 3;
frame.push_promise.promised_stream_id = 8; frame.push_promise.promised_stream_id = 8;
@ -2857,7 +2924,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
nghttp2_session_on_push_promise_received(session, &frame)); nghttp2_session_on_push_promise_received(session, &frame));
CU_ASSERT(0 == user_data.begin_headers_cb_called); CU_ASSERT(0 == user_data.begin_headers_cb_called);
CU_ASSERT(1 == session->num_incoming_reserved_streams); CU_ASSERT(0 == session->num_incoming_reserved_streams);
CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8)); CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
item = nghttp2_session_get_next_ob_item(session); item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
@ -3241,6 +3308,53 @@ void test_nghttp2_session_on_data_received(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
} }
void test_nghttp2_session_on_data_received_fail_fast(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
uint8_t buf[9];
nghttp2_stream *stream;
nghttp2_frame_hd hd;
nghttp2_outbound_item *item;
memset(&callbacks, 0, sizeof(callbacks));
nghttp2_frame_hd_init(&hd, 0, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1);
nghttp2_frame_pack_frame_hd(buf, &hd);
nghttp2_session_server_new(&session, &callbacks, NULL);
/* DATA to closed (remote) */
stream = open_stream(session, 1);
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
CU_ASSERT((ssize_t)sizeof(buf) ==
nghttp2_session_mem_recv(session, buf, sizeof(buf)));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NULL != item);
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
nghttp2_session_del(session);
nghttp2_session_server_new(&session, &callbacks, NULL);
/* DATA to closed stream with explicit closed (remote) */
stream = open_stream(session, 1);
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
CU_ASSERT((ssize_t)sizeof(buf) ==
nghttp2_session_mem_recv(session, buf, sizeof(buf)));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NULL != item);
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
nghttp2_session_del(session);
}
void test_nghttp2_session_send_headers_start_stream(void) { void test_nghttp2_session_send_headers_start_stream(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
@ -3984,6 +4098,7 @@ void test_nghttp2_submit_request_without_data(void) {
nva_out out; nva_out out;
nghttp2_bufs bufs; nghttp2_bufs bufs;
nghttp2_mem *mem; nghttp2_mem *mem;
nghttp2_priority_spec pri_spec;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs); frame_pack_bufs_init(&bufs);
@ -4017,6 +4132,15 @@ void test_nghttp2_submit_request_without_data(void) {
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
nghttp2_hd_inflate_free(&inflater); nghttp2_hd_inflate_free(&inflater);
/* Try to depend on itself is error */
nghttp2_priority_spec_init(&pri_spec, (int32_t)session->next_stream_id, 16,
0);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_request(session, &pri_spec, reqnv, ARRLEN(reqnv),
NULL, NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -4308,6 +4432,7 @@ void test_nghttp2_submit_headers(void) {
nva_out out; nva_out out;
nghttp2_bufs bufs; nghttp2_bufs bufs;
nghttp2_mem *mem; nghttp2_mem *mem;
nghttp2_priority_spec pri_spec;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs); frame_pack_bufs_init(&bufs);
@ -4362,6 +4487,21 @@ void test_nghttp2_submit_headers(void) {
nghttp2_frame_headers_free(&frame.headers, mem); nghttp2_frame_headers_free(&frame.headers, mem);
nghttp2_hd_inflate_free(&inflater); nghttp2_hd_inflate_free(&inflater);
/* Try to depend on itself */
nghttp2_priority_spec_init(&pri_spec, 3, 16, 0);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, &pri_spec,
reqnv, ARRLEN(reqnv), NULL));
session->next_stream_id = 5;
nghttp2_priority_spec_init(&pri_spec, 5, 16, 0);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, &pri_spec,
reqnv, ARRLEN(reqnv), NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -5318,38 +5458,6 @@ void test_nghttp2_session_max_concurrent_streams(void) {
nghttp2_session_del(session); nghttp2_session_del(session);
} }
/*
* Check that on_stream_close_callback is called when server pushed
* HEADERS have NGHTTP2_FLAG_END_STREAM.
*/
void test_nghttp2_session_stream_close_on_headers_push(void) {
/* nghttp2_session *session; */
/* nghttp2_session_callbacks callbacks; */
/* const char *nv[] = { NULL }; */
/* my_user_data ud; */
/* nghttp2_frame frame; */
/* memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); */
/* callbacks.on_stream_close_callback = */
/* no_stream_user_data_stream_close_callback; */
/* ud.stream_close_cb_called = 0; */
/* nghttp2_session_client_new(&session, NGHTTP2_PROTO_SPDY2, &callbacks, &ud);
*/
/* nghttp2_session_open_stream(session, 1, NGHTTP2_CTRL_FLAG_NONE, 3, */
/* NGHTTP2_STREAM_OPENING, NULL); */
/* nghttp2_frame_syn_stream_init(&frame.syn_stream, NGHTTP2_PROTO_SPDY2, */
/* NGHTTP2_CTRL_FLAG_FIN | */
/* NGHTTP2_CTRL_FLAG_UNIDIRECTIONAL, */
/* 2, 1, 3, dup_nv(nv)); */
/* CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session,
* &frame)); */
/* nghttp2_frame_syn_stream_free(&frame.syn_stream); */
/* nghttp2_session_del(session); */
}
void test_nghttp2_session_stop_data_with_rst_stream(void) { void test_nghttp2_session_stop_data_with_rst_stream(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
@ -7746,7 +7854,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
const size_t max_concurrent_streams = 5; const size_t max_concurrent_streams = 5;
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
max_concurrent_streams}; (uint32_t)max_concurrent_streams};
size_t i; size_t i;
memset(&callbacks, 0, sizeof(callbacks)); memset(&callbacks, 0, sizeof(callbacks));
@ -7781,6 +7889,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
CU_ASSERT(NULL == session->closed_stream_head->closed_prev); CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
open_stream(session, 11); open_stream(session, 11);
nghttp2_session_adjust_closed_stream(session);
CU_ASSERT(1 == session->num_closed_streams); CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(5 == session->closed_stream_tail->stream_id); CU_ASSERT(5 == session->closed_stream_tail->stream_id);
@ -7789,6 +7898,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
CU_ASSERT(NULL == session->closed_stream_head->closed_next); CU_ASSERT(NULL == session->closed_stream_head->closed_next);
open_stream(session, 13); open_stream(session, 13);
nghttp2_session_adjust_closed_stream(session);
CU_ASSERT(0 == session->num_closed_streams); CU_ASSERT(0 == session->num_closed_streams);
CU_ASSERT(NULL == session->closed_stream_tail); CU_ASSERT(NULL == session->closed_stream_tail);
@ -7801,6 +7911,7 @@ void test_nghttp2_session_keep_closed_stream(void) {
/* server initiated stream is not counted to max concurrent limit */ /* server initiated stream is not counted to max concurrent limit */
open_stream(session, 2); open_stream(session, 2);
nghttp2_session_adjust_closed_stream(session);
CU_ASSERT(1 == session->num_closed_streams); CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(3 == session->closed_stream_head->stream_id); CU_ASSERT(3 == session->closed_stream_head->stream_id);
@ -7818,8 +7929,9 @@ void test_nghttp2_session_keep_idle_stream(void) {
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
const size_t max_concurrent_streams = 1; const size_t max_concurrent_streams = 1;
nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
max_concurrent_streams}; (uint32_t)max_concurrent_streams};
int i; int i;
int32_t stream_id;
memset(&callbacks, 0, sizeof(callbacks)); memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = null_send_callback; callbacks.send_callback = null_send_callback;
@ -7828,25 +7940,29 @@ void test_nghttp2_session_keep_idle_stream(void) {
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1); nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
/* We at least allow 2 idle streams even if max concurrent streams /* We at least allow NGHTTP2_MIN_IDLE_STREAM idle streams even if
is very low. */ max concurrent streams is very low. */
for (i = 0; i < 2; ++i) { for (i = 0; i < NGHTTP2_MIN_IDLE_STREAMS; ++i) {
nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE, nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_default, NGHTTP2_STREAM_IDLE, NULL); &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
nghttp2_session_adjust_idle_stream(session);
} }
CU_ASSERT(2 == session->num_idle_streams); CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
stream_id = (NGHTTP2_MIN_IDLE_STREAMS - 1) * 2 + 1;
CU_ASSERT(1 == session->idle_stream_head->stream_id); CU_ASSERT(1 == session->idle_stream_head->stream_id);
CU_ASSERT(3 == session->idle_stream_tail->stream_id); CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
nghttp2_session_open_stream(session, 5, NGHTTP2_FLAG_NONE, &pri_spec_default, stream_id += 2;
NGHTTP2_STREAM_IDLE, NULL);
CU_ASSERT(2 == session->num_idle_streams); nghttp2_session_open_stream(session, stream_id, NGHTTP2_FLAG_NONE,
&pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
nghttp2_session_adjust_idle_stream(session);
CU_ASSERT(NGHTTP2_MIN_IDLE_STREAMS == session->num_idle_streams);
CU_ASSERT(3 == session->idle_stream_head->stream_id); CU_ASSERT(3 == session->idle_stream_head->stream_id);
CU_ASSERT(5 == session->idle_stream_tail->stream_id); CU_ASSERT(stream_id == session->idle_stream_tail->stream_id);
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -8639,7 +8755,7 @@ void test_nghttp2_session_flooding(void) {
void test_nghttp2_session_change_stream_priority(void) { void test_nghttp2_session_change_stream_priority(void) {
nghttp2_session *session; nghttp2_session *session;
nghttp2_session_callbacks callbacks; nghttp2_session_callbacks callbacks;
nghttp2_stream *stream1, *stream2, *stream3; nghttp2_stream *stream1, *stream2, *stream3, *stream5;
nghttp2_priority_spec pri_spec; nghttp2_priority_spec pri_spec;
int rv; int rv;
@ -8672,6 +8788,41 @@ void test_nghttp2_session_change_stream_priority(void) {
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec); rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* Depends on the non-existing idle stream. This creates that idle
stream. */
nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
CU_ASSERT(0 == rv);
stream5 = nghttp2_session_get_stream_raw(session, 5);
CU_ASSERT(NULL != stream5);
CU_ASSERT(&session->root == stream5->dep_prev);
CU_ASSERT(stream5 == stream2->dep_prev);
CU_ASSERT(9 == stream2->weight);
nghttp2_session_del(session);
/* Check that this works in client session too */
nghttp2_session_client_new(&session, &callbacks, NULL);
stream1 = open_stream(session, 1);
nghttp2_priority_spec_init(&pri_spec, 5, 9, 1);
rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
CU_ASSERT(0 == rv);
stream5 = nghttp2_session_get_stream_raw(session, 5);
CU_ASSERT(NULL != stream5);
CU_ASSERT(&session->root == stream5->dep_prev);
CU_ASSERT(stream5 == stream1->dep_prev);
CU_ASSERT(9 == stream1->weight);
nghttp2_session_del(session); nghttp2_session_del(session);
} }
@ -8681,8 +8832,10 @@ void test_nghttp2_session_create_idle_stream(void) {
nghttp2_stream *stream2, *stream4, *stream8, *stream10; nghttp2_stream *stream2, *stream4, *stream8, *stream10;
nghttp2_priority_spec pri_spec; nghttp2_priority_spec pri_spec;
int rv; int rv;
int i;
memset(&callbacks, 0, sizeof(callbacks)); memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_server_new(&session, &callbacks, NULL); nghttp2_session_server_new(&session, &callbacks, NULL);
@ -8744,6 +8897,170 @@ void test_nghttp2_session_create_idle_stream(void) {
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv); CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
nghttp2_session_del(session); nghttp2_session_del(session);
/* Check that this works in client session too */
nghttp2_session_client_new(&session, &callbacks, NULL);
nghttp2_priority_spec_init(&pri_spec, 4, 99, 1);
rv = nghttp2_session_create_idle_stream(session, 2, &pri_spec);
CU_ASSERT(0 == rv);
stream4 = nghttp2_session_get_stream_raw(session, 4);
stream2 = nghttp2_session_get_stream_raw(session, 2);
CU_ASSERT(NULL != stream4);
CU_ASSERT(NULL != stream2);
CU_ASSERT(&session->root == stream4->dep_prev);
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream4->weight);
CU_ASSERT(stream4 == stream2->dep_prev);
CU_ASSERT(99 == stream2->weight);
nghttp2_session_del(session);
/* Check that idle stream is reduced when nghttp2_session_send() is
called. */
nghttp2_session_server_new(&session, &callbacks, NULL);
session->local_settings.max_concurrent_streams = 30;
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
for (i = 0; i < 100; ++i) {
rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
CU_ASSERT(0 == rv);
nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
}
CU_ASSERT(100 == session->num_idle_streams);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(30 == session->num_idle_streams);
CU_ASSERT(141 == session->idle_stream_head->stream_id);
nghttp2_session_del(session);
/* Check that idle stream is reduced when nghttp2_session_mem_recv() is
called. */
nghttp2_session_client_new(&session, &callbacks, NULL);
session->local_settings.max_concurrent_streams = 30;
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
for (i = 0; i < 100; ++i) {
rv = nghttp2_session_create_idle_stream(session, i * 2 + 1, &pri_spec);
CU_ASSERT(0 == rv);
nghttp2_priority_spec_init(&pri_spec, i * 2 + 1, 16, 0);
}
CU_ASSERT(100 == session->num_idle_streams);
CU_ASSERT(0 == nghttp2_session_mem_recv(session, NULL, 0));
CU_ASSERT(30 == session->num_idle_streams);
CU_ASSERT(141 == session->idle_stream_head->stream_id);
nghttp2_session_del(session);
}
void test_nghttp2_session_repeated_priority_change(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_frame frame;
nghttp2_priority_spec pri_spec;
int32_t stream_id, last_stream_id;
int32_t max_streams = 20;
memset(&callbacks, 0, sizeof(callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
session->local_settings.max_concurrent_streams = (uint32_t)max_streams;
/* 1 -> 0 */
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
nghttp2_frame_priority_free(&frame.priority);
last_stream_id = max_streams * 2 + 1;
for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
/* 1 -> stream_id */
nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
nghttp2_frame_priority_free(&frame.priority);
}
CU_ASSERT(20 == session->num_idle_streams);
CU_ASSERT(1 == session->idle_stream_head->stream_id);
/* 1 -> last_stream_id */
nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
nghttp2_frame_priority_free(&frame.priority);
CU_ASSERT(20 == session->num_idle_streams);
CU_ASSERT(3 == session->idle_stream_head->stream_id);
nghttp2_session_del(session);
}
void test_nghttp2_session_repeated_priority_submission(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_priority_spec pri_spec;
int32_t stream_id, last_stream_id;
uint32_t max_streams = NGHTTP2_MIN_IDLE_STREAMS;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.send_callback = null_send_callback;
nghttp2_session_client_new(&session, &callbacks, NULL);
session->local_settings.max_concurrent_streams = max_streams;
/* 1 -> 0 */
nghttp2_priority_spec_init(&pri_spec, 0, 16, 0);
CU_ASSERT(0 ==
nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
last_stream_id = (int32_t)(max_streams * 2 + 1);
for (stream_id = 3; stream_id < last_stream_id; stream_id += 2) {
/* 1 -> stream_id */
nghttp2_priority_spec_init(&pri_spec, stream_id, 16, 0);
CU_ASSERT(
0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
}
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(max_streams == session->num_idle_streams);
CU_ASSERT(1 == session->idle_stream_head->stream_id);
/* 1 -> last_stream_id */
nghttp2_priority_spec_init(&pri_spec, last_stream_id, 16, 0);
CU_ASSERT(0 ==
nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(max_streams == session->num_idle_streams);
CU_ASSERT(3 == session->idle_stream_head->stream_id);
nghttp2_session_del(session);
} }
static void check_nghttp2_http_recv_headers_fail( static void check_nghttp2_http_recv_headers_fail(

View File

@ -34,6 +34,7 @@ void test_nghttp2_session_recv_data_no_auto_flow_control(void);
void test_nghttp2_session_recv_continuation(void); void test_nghttp2_session_recv_continuation(void);
void test_nghttp2_session_recv_headers_with_priority(void); void test_nghttp2_session_recv_headers_with_priority(void);
void test_nghttp2_session_recv_headers_early_response(void); void test_nghttp2_session_recv_headers_early_response(void);
void test_nghttp2_session_server_recv_push_response(void);
void test_nghttp2_session_recv_premature_headers(void); void test_nghttp2_session_recv_premature_headers(void);
void test_nghttp2_session_recv_unknown_frame(void); void test_nghttp2_session_recv_unknown_frame(void);
void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_unexpected_continuation(void);
@ -54,6 +55,7 @@ void test_nghttp2_session_on_ping_received(void);
void test_nghttp2_session_on_goaway_received(void); void test_nghttp2_session_on_goaway_received(void);
void test_nghttp2_session_on_window_update_received(void); void test_nghttp2_session_on_window_update_received(void);
void test_nghttp2_session_on_data_received(void); void test_nghttp2_session_on_data_received(void);
void test_nghttp2_session_on_data_received_fail_fast(void);
void test_nghttp2_session_send_headers_start_stream(void); void test_nghttp2_session_send_headers_start_stream(void);
void test_nghttp2_session_send_headers_reply(void); void test_nghttp2_session_send_headers_reply(void);
void test_nghttp2_session_send_headers_frame_size_error(void); void test_nghttp2_session_send_headers_frame_size_error(void);
@ -95,7 +97,6 @@ void test_nghttp2_session_get_next_ob_item(void);
void test_nghttp2_session_pop_next_ob_item(void); void test_nghttp2_session_pop_next_ob_item(void);
void test_nghttp2_session_reply_fail(void); void test_nghttp2_session_reply_fail(void);
void test_nghttp2_session_max_concurrent_streams(void); void test_nghttp2_session_max_concurrent_streams(void);
void test_nghttp2_session_stream_close_on_headers_push(void);
void test_nghttp2_session_stop_data_with_rst_stream(void); void test_nghttp2_session_stop_data_with_rst_stream(void);
void test_nghttp2_session_defer_data(void); void test_nghttp2_session_defer_data(void);
void test_nghttp2_session_flow_control(void); void test_nghttp2_session_flow_control(void);
@ -139,6 +140,8 @@ void test_nghttp2_session_detach_item_from_closed_stream(void);
void test_nghttp2_session_flooding(void); void test_nghttp2_session_flooding(void);
void test_nghttp2_session_change_stream_priority(void); void test_nghttp2_session_change_stream_priority(void);
void test_nghttp2_session_create_idle_stream(void); void test_nghttp2_session_create_idle_stream(void);
void test_nghttp2_session_repeated_priority_change(void);
void test_nghttp2_session_repeated_priority_submission(void);
void test_nghttp2_http_mandatory_headers(void); void test_nghttp2_http_mandatory_headers(void);
void test_nghttp2_http_content_length(void); void test_nghttp2_http_content_length(void);
void test_nghttp2_http_content_length_mismatch(void); void test_nghttp2_http_content_length_mismatch(void);

@ -1 +1 @@
Subproject commit 81eff20bd84b4d0dce2cbbd1a5ad1384d086423b Subproject commit 5c47587bc2855f2b9577a9bd369ed70088b77fec