Merge branch 'master' into simple-extensions

This commit is contained in:
Tatsuhiro Tsujikawa 2015-12-04 23:48:40 +09:00
commit 9c84f60ba0
60 changed files with 1784 additions and 684 deletions

View File

@ -27,12 +27,26 @@ before_install:
- $CC --version
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.9" CC="gcc-4.9"; fi
- $CC --version
- go version
before_script:
# First build spdylay, since integration tests require it.
# spdylay is going to be built under third-party/spdylay
- cd third-party
- git clone https://github.com/tatsuhiro-t/spdylay.git
- cd spdylay
- autoreconf -i
- ./configure --disable-src --disable-examples
- make check
- export SPDYLAY_HOME=$PWD
- cd ../..
# Now build nghttp2
- autoreconf -i
- automake
- autoconf
- git submodule update --init
- ./configure --enable-werror --with-mruby
- ./configure --enable-werror --with-mruby --with-neverbleed LIBSPDYLAY_CFLAGS="-I$SPDYLAY_HOME/lib/includes" LIBSPDYLAY_LIBS="-L$SPDYLAY_HOME/lib/.libs -lspdylay"
script:
- make
- make check
- cd integration-tests
- export GOPATH="$PWD/integration-tests/golang"
- make itprep-local
- make it-local

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
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.4.1-DEV], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.5.1-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
@ -46,9 +46,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 16)
AC_SUBST(LT_CURRENT, 17)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_AGE, 2)
AC_SUBST(LT_AGE, 3)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`

View File

@ -89,6 +89,7 @@ APIDOCS= \
nghttp2_session_consume.rst \
nghttp2_session_consume_connection.rst \
nghttp2_session_consume_stream.rst \
nghttp2_session_create_idle_stream.rst \
nghttp2_session_del.rst \
nghttp2_session_find_stream.rst \
nghttp2_session_get_effective_local_window_size.rst \
@ -108,7 +109,9 @@ APIDOCS= \
nghttp2_session_mem_recv.rst \
nghttp2_session_mem_send.rst \
nghttp2_session_recv.rst \
nghttp2_session_change_stream_priority.rst \
nghttp2_session_check_request_allowed.rst \
nghttp2_session_check_server_session.rst \
nghttp2_session_resume_data.rst \
nghttp2_session_send.rst \
nghttp2_session_server_new.rst \

View File

@ -8,7 +8,7 @@ _h2load()
_get_comp_words_by_ref cur prev
case $cur in
-*)
COMPREPLY=( $( compgen -W '--threads --connection-window-bits --rate-period --input-file --npn-list --help --requests --timing-script-file --data --verbose --base-uri --ciphers --connection-active-timeout --rate --connection-inactivity-timeout --window-bits --clients --no-tls-proto --version --header --max-concurrent-streams ' -- "$cur" ) )
COMPREPLY=( $( compgen -W '--connection-window-bits --clients --verbose --ciphers --rate --no-tls-proto --requests --base-uri --h1 --threads --npn-list --rate-period --data --version --connection-inactivity-timeout --timing-script-file --max-concurrent-streams --connection-active-timeout --input-file --header --window-bits --help ' -- "$cur" ) )
;;
*)
_filedir

View File

@ -8,7 +8,7 @@ _nghttpd()
_get_comp_words_by_ref cur prev
case $cur in
-*)
COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
COMPREPLY=( $( compgen -W '--mime-types-file --error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
;;
*)
_filedir

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "H2LOAD" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.TH "H2LOAD" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
.SH NAME
h2load \- HTTP/2 benchmarking tool
.
@ -227,6 +227,13 @@ Default: \fBh2,h2\-16,h2\-14,spdy/3.1,spdy/3,spdy/2,http/1.1\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-h1
Short hand for \fI\%\-\-npn\-list\fP=http/1.1
\fI\%\-\-no\-tls\-proto\fP=http/1.1, which effectively force
http/1.1 for both http and https URI.
.UNINDENT
.INDENT 0.0
.TP
.B \-v, \-\-verbose
Output debug information.
.UNINDENT

View File

@ -188,6 +188,12 @@ OPTIONS
Default: ``h2,h2-16,h2-14,spdy/3.1,spdy/3,spdy/2,http/1.1``
.. option:: --h1
Short hand for :option:`--npn-list`\=http/1.1
:option:`--no-tls-proto`\=http/1.1, which effectively force
http/1.1 for both http and https URI.
.. option:: -v, --verbose
Output debug information.

View File

@ -86,6 +86,21 @@ time for 1st byte (of (decrypted in case of TLS) application data)
deviation range (mean +/- sd) against total number of successful
connections.
req/s (client)
min
The minimum request per second among all clients.
max
The maximum request per second among all clients.
mean
The mean request per second among all clients.
sd
The standard deviation of request per second among all clients.
server.
+/- sd
The fraction of the number of connections within standard
deviation range (mean +/- sd) against total number of successful
connections.
FLOW CONTROL
------------

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTP" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.TH "NGHTTP" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
.SH NAME
nghttp \- HTTP/2 client
.

View File

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

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPX" "1" "November 12, 2015" "1.4.1-DEV" "nghttp2"
.TH "NGHTTPX" "1" "November 29, 2015" "1.5.1-DEV" "nghttp2"
.SH NAME
nghttpx \- HTTP/2 proxy
.
@ -685,9 +685,14 @@ protocol security.
.INDENT 0.0
.TP
.B \-\-no\-server\-push
Disable HTTP/2 server push. Server push is only
supported by default mode and HTTP/2 frontend. SPDY
frontend does not support server push.
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
.UNINDENT
.SS Mode
.INDENT 0.0
@ -1053,8 +1058,8 @@ than master process.
.UNINDENT
.SH SERVER PUSH
.sp
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
for Link header field (\fI\%RFC 5988\fP) in response headers from
nghttpx supports HTTP/2 server push in default mode with Link header
field. nghttpx looks for Link header field (\fI\%RFC 5988\fP) in response headers from
backend server and extracts URI\-reference with parameter
\fBrel=preload\fP (see \fI\%preload\fP)
and pushes those URIs to the frontend client. Here is a sample Link
@ -1079,6 +1084,14 @@ associated stream\(aqs status code must be 200.
.UNINDENT
.sp
This limitation may be loosened in the future release.
.sp
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
.sp
HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP or
\fI\%\-\-client\-proxy\fP is used.
.SH UNIX DOMAIN SOCKET
.sp
nghttpx supports UNIX domain socket with a filename for both frontend

View File

@ -613,9 +613,14 @@ HTTP/2 and SPDY
.. option:: --no-server-push
Disable HTTP/2 server push. Server push is only
supported by default mode and HTTP/2 frontend. SPDY
frontend does not support server push.
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`\--client` mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
Mode
@ -954,8 +959,8 @@ SIGUSR2
SERVER PUSH
-----------
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
for Link header field (`RFC 5988
nghttpx supports HTTP/2 server push in default mode with Link header
field. nghttpx looks for Link header field (`RFC 5988
<http://tools.ietf.org/html/rfc5988>`_) in response headers from
backend server and extracts URI-reference with parameter
``rel=preload`` (see `preload
@ -975,6 +980,14 @@ Currently, the following restriction is applied for server push:
This limitation may be loosened in the future release.
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
:option:`--client-proxy` is used.
UNIX DOMAIN SOCKET
------------------
@ -1035,7 +1048,9 @@ has to deploy key generator program to update keys frequently (e.g.,
every 1 hour). The example key generator tlsticketupdate.go is
available under contrib directory in nghttp2 archive. The memcached
entry key is ``nghttpx:tls-ticket-key``. The data format stored in
memcached is the binary format described below::
memcached is the binary format described below:
.. code-block:: text
+--------------+-------+----------------+
| VERSION (4) |LEN (2)|KEY(48 or 80) ...

View File

@ -60,8 +60,8 @@ SIGUSR2
SERVER PUSH
-----------
nghttpx supports HTTP/2 server push in default mode. nghttpx looks
for Link header field (`RFC 5988
nghttpx supports HTTP/2 server push in default mode with Link header
field. nghttpx looks for Link header field (`RFC 5988
<http://tools.ietf.org/html/rfc5988>`_) in response headers from
backend server and extracts URI-reference with parameter
``rel=preload`` (see `preload
@ -81,6 +81,14 @@ Currently, the following restriction is applied for server push:
This limitation may be loosened in the future release.
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
:option:`--client-proxy` is used.
UNIX DOMAIN SOCKET
------------------
@ -141,7 +149,9 @@ has to deploy key generator program to update keys frequently (e.g.,
every 1 hour). The example key generator tlsticketupdate.go is
available under contrib directory in nghttp2 archive. The memcached
entry key is ``nghttpx:tls-ticket-key``. The data format stored in
memcached is the binary format described below::
memcached is the binary format described below:
.. code-block:: text
+--------------+-------+----------------+
| VERSION (4) |LEN (2)|KEY(48 or 80) ...

View File

@ -613,7 +613,16 @@ typedef enum {
/**
* @macro
* Default maximum concurrent streams.
*
* Default maximum number of incoming concurrent streams. Use
* `nghttp2_submit_settings()` with
* :enum:`NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS` to change the
* maximum number of incoming concurrent streams.
*
* .. note::
*
* The maximum number of outgoing concurrent streams is 100 by
* default.
*/
#define NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1)
@ -2988,6 +2997,85 @@ NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session,
int32_t stream_id,
size_t size);
/**
* @function
*
* Changes priority of existing stream denoted by |stream_id|. The
* new priority specification is |pri_spec|.
*
* The priority is changed silently and instantly, and no PRIORITY
* frame will be sent to notify the peer of this change. This
* function may be useful for server to change the priority of pushed
* stream.
*
* If |session| is initialized as server, and ``pri_spec->stream_id``
* points to the idle stream, the idle stream is created if it does
* not exist. The created idle stream will depend on root stream
* (stream 0) with weight 16.
*
* Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
* found, we use default priority instead of given |pri_spec|. That
* is make stream depend on root stream with weight 16.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* Attempted to depend on itself; or no stream exist for the given
* |stream_id|; or |stream_id| is 0
*/
NGHTTP2_EXTERN int
nghttp2_session_change_stream_priority(nghttp2_session *session,
int32_t stream_id,
const nghttp2_priority_spec *pri_spec);
/**
* @function
*
* Creates idle stream with the given |stream_id|, and priority
* |pri_spec|.
*
* The stream creation is done without sending PRIORITY frame, which
* means that peer does not know about the existence of this idle
* stream in the local endpoint.
*
* RFC 7540 does not disallow the use of creation of idle stream with
* odd or even stream ID regardless of client or server. So this
* function can create odd or even stream ID regardless of client or
* server. But probably it is a bit safer to use the stream ID the
* local endpoint can initiate (in other words, use odd stream ID for
* client, and even stream ID for server), to avoid potential
* collision from peer's instruction. Also we can use
* `nghttp2_session_set_next_stream_id()` to avoid to open created
* idle streams accidentally if we follow this recommendation.
*
* If |session| is initialized as server, and ``pri_spec->stream_id``
* points to the idle stream, the idle stream is created if it does
* not exist. The created idle stream will depend on root stream
* (stream 0) with weight 16.
*
* Otherwise, if stream denoted by ``pri_spec->stream_id`` is not
* found, we use default priority instead of given |pri_spec|. That
* is make stream depend on root stream with weight 16.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* Attempted to depend on itself; or stream denoted by |stream_id|
* already exists; or |stream_id| cannot be used to create idle
* stream (in other words, local endpoint has already opened
* stream ID greater than or equal to the given stream ID; or
* |stream_id| is 0
*/
NGHTTP2_EXTERN int
nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id,
const nghttp2_priority_spec *pri_spec);
/**
* @function
*
@ -3629,14 +3717,20 @@ NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session,
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* The |stream_id| is 0; The |stream_id| does not designate stream
* that peer initiated.
* :enum:`NGHTTP2_ERR_STREAM_CLOSED`
* The stream was alreay closed; or the |stream_id| is invalid.
*
* .. warning::
*
* This function returns assigned promised stream ID if it succeeds.
* But that stream is not opened yet. The application must not
* submit frame to that stream ID before
* :type:`nghttp2_before_frame_send_callback` is called for this
* frame.
* As of 1.16.0, stream object for pushed resource is created when
* this function succeeds. In that case, the application can submit
* push response for the promised frame.
*
* In 1.15.0 or prior versions, pushed stream is not opened yet when
* this function succeeds. The application must not submit frame to
* that stream ID before :type:`nghttp2_before_frame_send_callback`
* is called for this frame.
*
*/
NGHTTP2_EXTERN int32_t
@ -3751,6 +3845,14 @@ nghttp2_session_get_last_proc_stream_id(nghttp2_session *session);
NGHTTP2_EXTERN int
nghttp2_session_check_request_allowed(nghttp2_session *session);
/**
* @function
*
* Returns nonzero if |session| is initialized as server side session.
*/
NGHTTP2_EXTERN int
nghttp2_session_check_server_session(nghttp2_session *session);
/**
* @function
*

View File

@ -42,3 +42,11 @@ int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec) {
return pri_spec->stream_id == 0 &&
pri_spec->weight == NGHTTP2_DEFAULT_WEIGHT && pri_spec->exclusive == 0;
}
void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec) {
if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) {
pri_spec->weight = NGHTTP2_MIN_WEIGHT;
} else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) {
pri_spec->weight = NGHTTP2_MAX_WEIGHT;
}
}

View File

@ -31,4 +31,12 @@
#include <nghttp2/nghttp2.h>
/*
* This function normalizes pri_spec->weight if it is out of range.
* If pri_spec->weight is less than NGHTTP2_MIN_WEIGHT, it is set to
* NGHTTP2_MIN_WEIGHT. If pri_spec->weight is larger than
* NGHTTP2_MAX_WEIGHT, it is set to NGHTTP2_MAX_WEIGHT.
*/
void nghttp2_priority_spec_normalize_weight(nghttp2_priority_spec *pri_spec);
#endif /* NGHTTP2_PRIORITY_SPEC_H */

View File

@ -372,6 +372,9 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->max_incoming_reserved_streams =
NGHTTP2_MAX_INCOMING_RESERVED_STREAMS;
/* Limit max outgoing concurrent streams to sensible value */
(*session_ptr)->remote_settings.max_concurrent_streams = 100;
if (option) {
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
option->no_auto_window_update) {
@ -665,6 +668,21 @@ int nghttp2_session_reprioritize_stream(
}
}
assert(dep_stream);
if (dep_stream == stream->dep_prev && !pri_spec->exclusive) {
/* This is minor optimization when just weight is changed.
Currently, we don't reschedule stream in this case, since we
don't retain enough information to do that
(descendant_last_cycle we used to schedule it). This means new
weight is only applied in the next scheduling, and if weight is
drastically increased, library is not responding very quickly.
If this is really an issue, we will do workaround for this. */
dep_stream->sum_dep_weight += pri_spec->weight - stream->weight;
stream->weight = pri_spec->weight;
return 0;
}
nghttp2_stream_dep_remove_subtree(stream);
/* We have to update weight after removing stream from tree */
@ -740,6 +758,31 @@ int nghttp2_session_add_item(nghttp2_session *session,
nghttp2_outbound_queue_push(&session->ob_reg, item);
item->queued = 1;
break;
case NGHTTP2_PUSH_PROMISE: {
nghttp2_headers_aux_data *aux_data;
nghttp2_priority_spec pri_spec;
aux_data = &item->aux_data.headers;
if (!stream) {
return NGHTTP2_ERR_STREAM_CLOSED;
}
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
NGHTTP2_DEFAULT_WEIGHT, 0);
if (!nghttp2_session_open_stream(
session, frame->push_promise.promised_stream_id,
NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED,
aux_data->stream_user_data)) {
return NGHTTP2_ERR_NOMEM;
}
nghttp2_outbound_queue_push(&session->ob_reg, item);
item->queued = 1;
break;
}
case NGHTTP2_WINDOW_UPDATE:
if (stream) {
stream->window_update_queued = 1;
@ -965,15 +1008,6 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
}
}
/* We don't have to track dependency of received reserved stream */
if (stream->shut_flags & NGHTTP2_SHUT_WR) {
return stream;
}
/* TODO Client does not have to track dependencies of streams except
for those which have upload data. Currently, we just track
everything. */
if (pri_spec->stream_id == 0) {
dep_stream = &session->root;
}
@ -997,6 +1031,7 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
int rv;
nghttp2_stream *stream;
nghttp2_mem *mem;
int is_my_stream_id;
mem = &session->mem;
stream = nghttp2_session_get_stream(session, stream_id);
@ -1044,14 +1079,16 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
}
}
is_my_stream_id = nghttp2_session_is_my_stream_id(session, stream_id);
/* pushed streams which is not opened yet is not counted toward max
concurrent limits */
if ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH)) {
if (!nghttp2_session_is_my_stream_id(session, stream_id)) {
if (!is_my_stream_id) {
--session->num_incoming_reserved_streams;
}
} else {
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
if (is_my_stream_id) {
--session->num_outgoing_streams;
} else {
--session->num_incoming_streams;
@ -1061,7 +1098,8 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
/* Closes both directions just in case they are not closed yet */
stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED;
if (session->server && nghttp2_stream_in_dep_tree(stream)) {
if (session->server && !is_my_stream_id &&
nghttp2_stream_in_dep_tree(stream)) {
/* On server side, retain stream at most MAX_CONCURRENT_STREAMS
combined with the current active incoming streams to make
dependency tree work better. */
@ -1420,6 +1458,9 @@ static int session_predicate_response_headers_send(nghttp2_session *session,
* RST_STREAM was queued for this stream.
* NGHTTP2_ERR_SESSION_CLOSING
* This session is closing.
* NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
* New stream cannot be created because GOAWAY is already sent or
* received.
*/
static int
session_predicate_push_response_headers_send(nghttp2_session *session,
@ -1437,6 +1478,9 @@ session_predicate_push_response_headers_send(nghttp2_session *session,
if (stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) {
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
return 0;
}
@ -1835,40 +1879,41 @@ static int session_prep_frame(nghttp2_session *session,
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
if (session_predicate_push_response_headers_send(session, stream) ==
0) {
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
if (stream && stream->state == NGHTTP2_STREAM_RESERVED) {
rv = session_predicate_push_response_headers_send(session, stream);
if (rv == 0) {
frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
if (aux_data->stream_user_data) {
stream->stream_user_data = aux_data->stream_user_data;
if (aux_data->stream_user_data) {
stream->stream_user_data = aux_data->stream_user_data;
}
}
} else if (session_predicate_response_headers_send(session, stream) ==
0) {
frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
rv = 0;
} else {
frame->headers.cat = NGHTTP2_HCAT_HEADERS;
rv = session_predicate_headers_send(session, stream);
}
if (rv != 0) {
// If stream was alreay closed,
// nghttp2_session_get_stream() returns NULL, but item is
// still attached to the stream. Search stream including
// closed again.
stream =
nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if (stream && stream->item == item) {
int rv2;
if (rv != 0) {
// If stream was alreay closed, nghttp2_session_get_stream()
// returns NULL, but item is still attached to the stream.
// Search stream including closed again.
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
if (stream && stream->item == item) {
int rv2;
rv2 = nghttp2_stream_detach_item(stream);
rv2 = nghttp2_stream_detach_item(stream);
if (nghttp2_is_fatal(rv2)) {
return rv2;
}
if (nghttp2_is_fatal(rv2)) {
return rv2;
}
return rv;
}
return rv;
}
}
@ -1930,30 +1975,8 @@ static int session_prep_frame(nghttp2_session *session,
}
case NGHTTP2_PUSH_PROMISE: {
nghttp2_stream *stream;
nghttp2_headers_aux_data *aux_data;
nghttp2_priority_spec pri_spec;
size_t estimated_payloadlen;
aux_data = &item->aux_data.headers;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
/* stream could be NULL if associated stream was already
closed. */
if (stream) {
nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
NGHTTP2_DEFAULT_WEIGHT, 0);
} else {
nghttp2_priority_spec_default_init(&pri_spec);
}
if (!nghttp2_session_open_stream(
session, frame->push_promise.promised_stream_id,
NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_RESERVED,
aux_data->stream_user_data)) {
return NGHTTP2_ERR_NOMEM;
}
estimated_payloadlen = session_estimate_headers_payload(
session, frame->push_promise.nva, frame->push_promise.nvlen, 0);
@ -1961,6 +1984,10 @@ static int session_prep_frame(nghttp2_session *session,
return NGHTTP2_ERR_FRAME_SIZE_ERROR;
}
/* stream could be NULL if associated stream was already
closed. */
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
/* predicte should fail if stream is NULL. */
rv = session_predicate_push_promise_send(session, stream);
if (rv != 0) {
@ -2243,16 +2270,19 @@ static int session_close_stream_on_goaway(nghttp2_session *session,
nghttp2_stream *stream, *next_stream;
nghttp2_close_stream_on_goaway_arg arg = {session, NULL, last_stream_id,
incoming};
uint32_t error_code;
rv = nghttp2_map_each(&session->streams, find_stream_on_goaway_func, &arg);
assert(rv == 0);
error_code =
session->server && incoming ? NGHTTP2_REFUSED_STREAM : NGHTTP2_CANCEL;
stream = arg.head;
while (stream) {
next_stream = stream->closed_next;
stream->closed_next = NULL;
rv = nghttp2_session_close_stream(session, stream->stream_id,
NGHTTP2_REFUSED_STREAM);
rv = nghttp2_session_close_stream(session, stream->stream_id, error_code);
/* stream may be deleted here */
@ -3360,9 +3390,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
}
/*
* Decompress header blocks of incoming request HEADERS and also call
* additional callbacks. This function can be called again if this
* function returns NGHTTP2_ERR_PAUSE.
* Call this function when HEADERS frame was completely received.
*
* This function returns 0 if it succeeds, or one of negative error
* codes:
@ -3372,71 +3400,20 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
int nghttp2_session_end_request_headers_received(nghttp2_session *session _U_,
nghttp2_frame *frame,
nghttp2_stream *stream) {
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
}
/* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */
return 0;
}
/*
* Decompress header blocks of incoming (push-)response HEADERS and
* also call additional callbacks. This function can be called again
* if this function returns NGHTTP2_ERR_PAUSE.
*
* This function returns 0 if it succeeds, or one of negative error
* codes:
*
* NGHTTP2_ERR_CALLBACK_FAILURE
* The callback function failed.
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
int nghttp2_session_end_response_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream) {
static int session_end_stream_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream) {
int rv;
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
/* This is the last frame of this stream, so disallow
further receptions. */
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
}
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
return 0;
}
return 0;
}
/*
* Decompress header blocks of incoming HEADERS and also call
* additional callbacks. This function can be called again if this
* function returns NGHTTP2_ERR_PAUSE.
*
* This function returns 0 if it succeeds, or one of negative error
* codes:
*
* NGHTTP2_ERR_CALLBACK_FAILURE
* The callback function failed.
* NGHTTP2_ERR_NOMEM
* Out of memory.
*/
int nghttp2_session_end_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream) {
int rv;
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
}
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
}
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
}
return 0;
}
@ -3517,19 +3494,7 @@ static int session_after_header_block_received(nghttp2_session *session) {
return 0;
}
switch (frame->headers.cat) {
case NGHTTP2_HCAT_REQUEST:
return nghttp2_session_end_request_headers_received(session, frame, stream);
case NGHTTP2_HCAT_RESPONSE:
case NGHTTP2_HCAT_PUSH_RESPONSE:
return nghttp2_session_end_response_headers_received(session, frame,
stream);
case NGHTTP2_HCAT_HEADERS:
return nghttp2_session_end_headers_received(session, frame, stream);
default:
assert(0);
}
return 0;
return session_end_stream_headers_received(session, frame, stream);
}
int nghttp2_session_on_request_headers_received(nghttp2_session *session,
@ -4287,9 +4252,8 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
session, frame, NGHTTP2_ERR_PROTO, "PUSH_PROMISE: stream in idle");
}
}
rv = nghttp2_session_add_rst_stream(session,
frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
rv = nghttp2_session_add_rst_stream(
session, frame->push_promise.promised_stream_id, NGHTTP2_CANCEL);
if (rv != 0) {
return rv;
}
@ -6903,9 +6867,67 @@ int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) {
nghttp2_stream *nghttp2_session_find_stream(nghttp2_session *session,
int32_t stream_id) {
if (stream_id == 0) {
return &session->root;
}
return nghttp2_session_get_stream_raw(session, stream_id);
}
nghttp2_stream *nghttp2_session_get_root_stream(nghttp2_session *session) {
return &session->root;
}
int nghttp2_session_check_server_session(nghttp2_session *session) {
return session->server;
}
int nghttp2_session_change_stream_priority(
nghttp2_session *session, int32_t stream_id,
const nghttp2_priority_spec *pri_spec) {
nghttp2_stream *stream;
nghttp2_priority_spec pri_spec_copy;
if (stream_id == 0 || stream_id == pri_spec->stream_id) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
stream = nghttp2_session_get_stream_raw(session, stream_id);
if (!stream) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
pri_spec_copy = *pri_spec;
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
return nghttp2_session_reprioritize_stream(session, stream, &pri_spec_copy);
}
int nghttp2_session_create_idle_stream(nghttp2_session *session,
int32_t stream_id,
const nghttp2_priority_spec *pri_spec) {
nghttp2_stream *stream;
nghttp2_priority_spec pri_spec_copy;
if (stream_id == 0 || stream_id == pri_spec->stream_id ||
!session_detect_idle_stream(session, stream_id)) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
stream = nghttp2_session_get_stream_raw(session, stream_id);
if (stream) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
pri_spec_copy = *pri_spec;
nghttp2_priority_spec_normalize_weight(&pri_spec_copy);
stream =
nghttp2_session_open_stream(session, stream_id, NGHTTP2_STREAM_FLAG_NONE,
&pri_spec_copy, NGHTTP2_STREAM_IDLE, NULL);
if (!stream) {
return NGHTTP2_ERR_NOMEM;
}
return 0;
}

View File

@ -191,11 +191,13 @@ struct nghttp2_session {
updated when one frame was written. */
uint64_t last_cycle;
void *user_data;
/* Points to the latest closed stream. NULL if there is no closed
stream. Only used when session is initialized as server. */
/* Points to the latest incoming closed stream. NULL if there is no
closed stream. Only used when session is initialized as
server. */
nghttp2_stream *closed_stream_head;
/* Points to the oldest closed stream. NULL if there is no closed
stream. Only used when session is initialized as server. */
/* Points to the oldest incoming closed stream. NULL if there is no
closed stream. Only used when session is initialized as
server. */
nghttp2_stream *closed_stream_tail;
/* Points to the latest idle stream. NULL if there is no idle
stream. Only used when session is initialized as server .*/
@ -341,7 +343,7 @@ int nghttp2_session_is_my_stream_id(nghttp2_session *session,
* NGHTTP2_ERR_NOMEM
* Out of memory.
* NGHTTP2_ERR_STREAM_CLOSED
* Stream already closed (DATA frame only)
* Stream already closed (DATA and PUSH_PROMISE frame only)
*/
int nghttp2_session_add_item(nghttp2_session *session,
nghttp2_outbound_item *item);
@ -567,18 +569,6 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session);
int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
nghttp2_stream *stream);
int nghttp2_session_end_request_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream);
int nghttp2_session_end_response_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream);
int nghttp2_session_end_headers_received(nghttp2_session *session,
nghttp2_frame *frame,
nghttp2_stream *stream);
int nghttp2_session_on_request_headers_received(nghttp2_session *session,
nghttp2_frame *frame);

View File

@ -30,13 +30,14 @@
#include "nghttp2_session.h"
#include "nghttp2_helper.h"
static int stream_weight_less(const void *lhsx, const void *rhsx) {
static int stream_less(const void *lhsx, const void *rhsx) {
const nghttp2_stream *lhs, *rhs;
lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
return lhs->cycle < rhs->cycle;
return lhs->cycle < rhs->cycle ||
(lhs->cycle == rhs->cycle && lhs->seq < rhs->seq);
}
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
@ -45,7 +46,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
int32_t local_initial_window_size,
void *stream_user_data, nghttp2_mem *mem) {
nghttp2_map_entry_init(&stream->map_entry, (key_type)stream_id);
nghttp2_pq_init(&stream->obq, stream_weight_less, mem);
nghttp2_pq_init(&stream->obq, stream_less, mem);
stream->stream_id = stream_id;
stream->flags = flags;
@ -79,6 +80,8 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
stream->queued = 0;
stream->descendant_last_cycle = 0;
stream->cycle = 0;
stream->descendant_next_seq = 0;
stream->seq = 0;
stream->last_writelen = 0;
}
@ -124,6 +127,7 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
stream = dep_stream, dep_stream = dep_stream->dep_prev) {
stream->cycle =
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++;
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%ld\n",
stream->stream_id, stream->cycle));
@ -166,6 +170,7 @@ static void stream_obq_remove(nghttp2_stream *stream) {
stream->queued = 0;
stream->cycle = 0;
stream->descendant_last_cycle = 0;
stream->last_writelen = 0;
if (stream_subtree_active(dep_stream)) {
return;
@ -206,12 +211,19 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
dep_stream->descendant_last_cycle = 0;
stream->cycle = 0;
} else {
/* We update descendant_last_cycle here, and we don't do it when
no data is written for stream. This effectively means that
we treat these streams as if they are not scheduled at all.
This does not cause disruption in scheduling machinery. It
just makes new streams scheduled a bit early. */
dep_stream->descendant_last_cycle = stream->cycle;
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
stream->cycle =
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++;
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
}

View File

@ -146,6 +146,15 @@ struct nghttp2_stream {
int64_t content_length;
/* Received body so far */
int64_t recv_content_length;
/* Base last_cycle for direct descendent streams. */
uint64_t descendant_last_cycle;
/* Next scheduled time to sent item */
uint64_t cycle;
/* Next seq used for direct descendant streams */
uint64_t descendant_next_seq;
/* Secondary key for prioritization to break a tie for cycle. This
value is monotonically increased for single parent stream. */
uint64_t seq;
/* pointers to form dependency tree. If multiple streams depend on
a stream, only one stream (left most) has non-NULL dep_prev which
points to the stream it depends on. The remaining streams are
@ -164,6 +173,8 @@ struct nghttp2_stream {
void *stream_user_data;
/* Item to send */
nghttp2_outbound_item *item;
/* Last written length of frame payload */
size_t last_writelen;
/* stream ID */
int32_t stream_id;
/* Current remote window size. This value is computed against the
@ -202,12 +213,6 @@ struct nghttp2_stream {
then its ancestors, except for root, are also queued. This
invariant may break in fatal error condition. */
uint8_t queued;
/* Base last_cycle for direct descendent streams. */
uint64_t descendant_last_cycle;
/* Next scheduled time to sent item */
uint64_t cycle;
/* Last written length of frame payload */
size_t last_writelen;
/* This flag is used to reduce excessive queuing of WINDOW_UPDATE to
this stream. The nonzero does not necessarily mean WINDOW_UPDATE
is not queued. */

View File

@ -117,14 +117,6 @@ fail2:
return rv;
}
static void adjust_priority_spec_weight(nghttp2_priority_spec *pri_spec) {
if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) {
pri_spec->weight = NGHTTP2_MIN_WEIGHT;
} else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) {
pri_spec->weight = NGHTTP2_MAX_WEIGHT;
}
}
static int32_t submit_headers_shared_nva(nghttp2_session *session,
uint8_t flags, int32_t stream_id,
const nghttp2_priority_spec *pri_spec,
@ -141,7 +133,7 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
if (pri_spec) {
copy_pri_spec = *pri_spec;
adjust_priority_spec_weight(&copy_pri_spec);
nghttp2_priority_spec_normalize_weight(&copy_pri_spec);
} else {
nghttp2_priority_spec_default_init(&copy_pri_spec);
}
@ -206,7 +198,7 @@ int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags _U_,
copy_pri_spec = *pri_spec;
adjust_priority_spec_weight(&copy_pri_spec);
nghttp2_priority_spec_normalize_weight(&copy_pri_spec);
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));

View File

@ -288,6 +288,10 @@ cdef extern from 'nghttp2/nghttp2.h':
const char* nghttp2_strerror(int lib_error_code)
int nghttp2_session_check_server_session(nghttp2_session *session)
int nghttp2_session_get_stream_remote_close(nghttp2_session *session, int32_t stream_id)
int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
size_t deflate_hd_table_bufsize_max)

View File

@ -473,6 +473,13 @@ cdef int server_on_frame_send(cnghttp2.nghttp2_session *session,
if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0:
return 0
http2._start_settings_timer()
elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM) and \
cnghttp2.nghttp2_session_check_server_session(session):
# Send RST_STREAM if remote is not closed yet
if cnghttp2.nghttp2_session_get_stream_remote_close(
session, frame.hd.stream_id) == 0:
http2._rst_stream(frame.hd.stream_id, cnghttp2.NGHTTP2_NO_ERROR)
cdef int server_on_frame_not_send(cnghttp2.nghttp2_session *session,
const cnghttp2.nghttp2_frame *frame,
@ -539,6 +546,11 @@ cdef ssize_t data_source_read(cnghttp2.nghttp2_session *session,
if flag == DATA_EOF:
data_flags[0] = cnghttp2.NGHTTP2_DATA_FLAG_EOF
if cnghttp2.nghttp2_session_check_server_session(session):
# Send RST_STREAM if remote is not closed yet
if cnghttp2.nghttp2_session_get_stream_remote_close(
session, stream_id) == 0:
http2._rst_stream(stream_id, cnghttp2.NGHTTP2_NO_ERROR)
elif flag != DATA_OK:
return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE

View File

@ -175,6 +175,51 @@ namespace {
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
} // namespace
namespace {
constexpr ev_tstamp RELEASE_FD_TIMEOUT = 2.;
} // namespace
namespace {
void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents);
} // namespace
namespace {
constexpr ev_tstamp FILE_ENTRY_MAX_AGE = 10.;
} // namespace
namespace {
constexpr size_t FILE_ENTRY_EVICT_THRES = 2048;
} // namespace
namespace {
bool need_validation_file_entry(const FileEntry *ent, ev_tstamp now) {
return ent->last_valid + FILE_ENTRY_MAX_AGE < now;
}
} // namespace
namespace {
bool validate_file_entry(FileEntry *ent, ev_tstamp now) {
struct stat stbuf;
int rv;
rv = fstat(ent->fd, &stbuf);
if (rv != 0) {
ent->stale = true;
return false;
}
if (stbuf.st_nlink == 0 || ent->mtime != stbuf.st_mtime) {
ent->stale = true;
return false;
}
ent->mtime = stbuf.st_mtime;
ent->last_valid = now;
return true;
}
} // namespace
class Sessions {
public:
Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
@ -185,15 +230,24 @@ public:
nghttp2_session_callbacks_new(&callbacks_);
fill_callback(callbacks_, config_);
ev_timer_init(&release_fd_timer_, release_fd_cb, 0., RELEASE_FD_TIMEOUT);
release_fd_timer_.data = this;
}
~Sessions() {
ev_timer_stop(loop_, &release_fd_timer_);
for (auto handler : handlers_) {
delete handler;
}
nghttp2_session_callbacks_del(callbacks_);
}
void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
void remove_handler(Http2Handler *handler) { handlers_.erase(handler); }
void remove_handler(Http2Handler *handler) {
handlers_.erase(handler);
if (handlers_.empty() && !fd_cache_.empty()) {
ev_timer_again(loop_, &release_fd_timer_);
}
}
SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }
SSL *ssl_session_new(int fd) {
SSL *ssl = SSL_new(ssl_ctx_);
@ -252,50 +306,122 @@ public:
return cached_date_;
}
FileEntry *get_cached_fd(const std::string &path) {
auto i = fd_cache_.find(path);
if (i == std::end(fd_cache_)) {
auto range = fd_cache_.equal_range(path);
if (range.first == range.second) {
return nullptr;
}
auto &ent = (*i).second;
++ent.usecount;
return &ent;
auto now = ev_now(loop_);
for (auto it = range.first; it != range.second;) {
auto &ent = (*it).second;
if (ent->stale) {
++it;
continue;
}
if (need_validation_file_entry(ent.get(), now) &&
!validate_file_entry(ent.get(), now)) {
if (ent->usecount == 0) {
fd_cache_lru_.remove(ent.get());
close(ent->fd);
it = fd_cache_.erase(it);
continue;
}
++it;
continue;
}
fd_cache_lru_.remove(ent.get());
fd_cache_lru_.append(ent.get());
++ent->usecount;
return ent.get();
}
return nullptr;
}
FileEntry *cache_fd(const std::string &path, const FileEntry &ent) {
#ifdef HAVE_STD_MAP_EMPLACE
auto rv = fd_cache_.emplace(path, ent);
auto rv = fd_cache_.emplace(path, make_unique<FileEntry>(ent));
#else // !HAVE_STD_MAP_EMPLACE
// for gcc-4.7
auto rv = fd_cache_.insert(std::make_pair(path, ent));
auto rv =
fd_cache_.insert(std::make_pair(path, make_unique<FileEntry>(ent)));
#endif // !HAVE_STD_MAP_EMPLACE
return &(*rv.first).second;
auto &res = (*rv).second;
res->it = rv;
fd_cache_lru_.append(res.get());
while (fd_cache_.size() > FILE_ENTRY_EVICT_THRES) {
auto ent = fd_cache_lru_.head;
if (ent->usecount) {
break;
}
fd_cache_lru_.remove(ent);
close(ent->fd);
fd_cache_.erase(ent->it);
}
return res.get();
}
void release_fd(const std::string &path) {
auto i = fd_cache_.find(path);
if (i == std::end(fd_cache_)) {
void release_fd(FileEntry *target) {
--target->usecount;
if (target->usecount == 0 && target->stale) {
fd_cache_lru_.remove(target);
close(target->fd);
fd_cache_.erase(target->it);
return;
}
auto &ent = (*i).second;
if (--ent.usecount == 0) {
close(ent.fd);
fd_cache_.erase(i);
// We use timer to close file descriptor and delete the entry from
// cache. The timer will be started when there is no handler.
}
void release_unused_fd() {
for (auto i = std::begin(fd_cache_); i != std::end(fd_cache_);) {
auto &ent = (*i).second;
if (ent->usecount != 0) {
++i;
continue;
}
fd_cache_lru_.remove(ent.get());
close(ent->fd);
i = fd_cache_.erase(i);
}
}
const HttpServer *get_server() const { return sv_; }
bool handlers_empty() const { return handlers_.empty(); }
private:
std::set<Http2Handler *> handlers_;
// cache for file descriptors to read file.
std::map<std::string, FileEntry> fd_cache_;
std::multimap<std::string, std::unique_ptr<FileEntry>> fd_cache_;
DList<FileEntry> fd_cache_lru_;
HttpServer *sv_;
struct ev_loop *loop_;
const Config *config_;
SSL_CTX *ssl_ctx_;
nghttp2_session_callbacks *callbacks_;
ev_timer release_fd_timer_;
int64_t next_session_id_;
ev_tstamp tstamp_cached_;
std::string cached_date_;
};
namespace {
void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto sessions = static_cast<Sessions *>(w->data);
ev_timer_stop(loop, w);
if (!sessions->handlers_empty()) {
return;
}
sessions->release_unused_fd();
}
} // namespace
Stream::Stream(Http2Handler *handler, int32_t stream_id)
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
stream_id(stream_id), echo_upload(false) {
@ -313,7 +439,7 @@ Stream::Stream(Http2Handler *handler, int32_t stream_id)
Stream::~Stream() {
if (file_ent != nullptr) {
auto sessions = handler->get_sessions();
sessions->release_fd(file_ent->path);
sessions->release_fd(file_ent);
}
auto loop = handler->get_loop();
@ -979,8 +1105,9 @@ bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) {
unlink(tempfn);
// Ordinary request never start with "echo:". The length is 0 for
// now. We will update it when we get whole request body.
stream->file_ent = sessions->cache_fd(std::string("echo:") + tempfn,
FileEntry(tempfn, 0, 0, fd, nullptr));
auto path = std::string("echo:") + tempfn;
stream->file_ent =
sessions->cache_fd(path, FileEntry(path, 0, 0, fd, nullptr, 0, true));
stream->echo_upload = true;
return true;
}
@ -1016,8 +1143,13 @@ namespace {
void prepare_response(Stream *stream, Http2Handler *hd,
bool allow_push = true) {
int rv;
auto reqpath =
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value;
auto pathhdr =
http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers);
if (!pathhdr) {
prepare_status_response(stream, hd, 405);
return;
}
auto reqpath = pathhdr->value;
auto ims =
get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers);
@ -1042,10 +1174,10 @@ void prepare_response(Stream *stream, Http2Handler *hd,
auto sessions = hd->get_sessions();
url = util::percentDecode(std::begin(url), std::end(url));
url = util::percent_decode(std::begin(url), std::end(url));
if (!util::check_path(url)) {
if (stream->file_ent) {
sessions->release_fd(stream->file_ent->path);
sessions->release_fd(stream->file_ent);
stream->file_ent = nullptr;
}
prepare_status_response(stream, hd, 404);
@ -1096,7 +1228,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
close(file);
if (query_pos == std::string::npos) {
reqpath += "/";
reqpath += '/';
} else {
reqpath.insert(query_pos, "/");
}
@ -1127,7 +1259,8 @@ void prepare_response(Stream *stream, Http2Handler *hd,
}
file_ent = sessions->cache_fd(
path, FileEntry(path, buf.st_size, buf.st_mtime, file, content_type));
path, FileEntry(path, buf.st_size, buf.st_mtime, file, content_type,
ev_now(sessions->get_loop())));
}
stream->file_ent = file_ent;
@ -1651,7 +1784,7 @@ FileEntry make_status_body(int status, uint16_t port) {
assert(0);
}
return FileEntry(util::utos(status), nwrite, 0, fd, nullptr);
return FileEntry(util::utos(status), nwrite, 0, fd, nullptr, 0);
}
} // namespace
@ -1661,6 +1794,7 @@ enum {
IDX_301,
IDX_400,
IDX_404,
IDX_405,
};
HttpServer::HttpServer(const Config *config) : config_(config) {
@ -1669,6 +1803,7 @@ HttpServer::HttpServer(const Config *config) : config_(config) {
{"301", make_status_body(301, config_->port)},
{"400", make_status_body(400, config_->port)},
{"404", make_status_body(404, config_->port)},
{"405", make_status_body(405, config_->port)},
};
}
@ -1921,6 +2056,8 @@ const StatusPage *HttpServer::get_status_page(int status) const {
return &status_pages_[IDX_400];
case 404:
return &status_pages_[IDX_404];
case 405:
return &status_pages_[IDX_405];
default:
assert(0);
}

View File

@ -84,16 +84,21 @@ class Http2Handler;
struct FileEntry {
FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
const std::string *content_type)
: path(std::move(path)), length(length), mtime(mtime), dlprev(nullptr),
dlnext(nullptr), content_type(content_type), fd(fd), usecount(1) {}
const std::string *content_type, ev_tstamp last_valid,
bool stale = false)
: path(std::move(path)), length(length), mtime(mtime),
last_valid(last_valid), content_type(content_type), dlnext(nullptr),
dlprev(nullptr), fd(fd), usecount(1), stale(stale) {}
std::string path;
std::multimap<std::string, std::unique_ptr<FileEntry>>::iterator it;
int64_t length;
int64_t mtime;
FileEntry *dlprev, *dlnext;
ev_tstamp last_valid;
const std::string *content_type;
FileEntry *dlnext, *dlprev;
int fd;
int usecount;
bool stale;
};
struct Stream {

View File

@ -420,10 +420,10 @@ const request *session_impl::submit(boost::system::error_code &ec,
if (util::ipv6_numeric_addr(uref.host.c_str())) {
uref.host = "[" + uref.host;
uref.host += "]";
uref.host += ']';
}
if (u.field_set & (1 << UF_PORT)) {
uref.host += ":";
uref.host += ':';
uref.host += util::utos(u.port);
}
@ -435,7 +435,7 @@ const request *session_impl::submit(boost::system::error_code &ec,
auto path = uref.raw_path;
if (u.field_set & (1 << UF_QUERY)) {
path += "?";
path += '?';
path += uref.raw_query;
}
@ -525,7 +525,7 @@ void session_impl::do_read() {
read_socket([this](const boost::system::error_code &ec,
std::size_t bytes_transferred) {
if (ec) {
if (ec.value() == boost::asio::error::operation_aborted) {
if (!should_stop()) {
call_error_cb(ec);
shutdown_socket();
}

View File

@ -134,7 +134,7 @@ generator_cb file_generator_from_fd(int fd) {
bool check_path(const std::string &path) { return util::check_path(path); }
std::string percent_decode(const std::string &s) {
return util::percentDecode(std::begin(s), std::end(s));
return util::percent_decode(std::begin(s), std::end(s));
}
std::string http_date(int64_t t) { return util::http_date(t); }
@ -177,9 +177,11 @@ bool tls_h2_negotiated(ssl_socket &socket) {
unsigned int next_proto_len = 0;
SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (next_proto == nullptr) {
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
}
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
if (next_proto == nullptr) {
return false;

View File

@ -55,7 +55,7 @@ void split_path(uri_ref &dst, InputIt first, InputIt last) {
} else {
query_first = path_last + 1;
}
dst.path = util::percentDecode(first, path_last);
dst.path = util::percent_decode(first, path_last);
dst.raw_path.assign(first, path_last);
dst.raw_query.assign(query_first, last);
}

View File

@ -83,7 +83,7 @@ request_cb serve_mux::handler(request_impl &req) const {
auto new_uri = util::percent_encode_path(clean_path);
auto &uref = req.uri();
if (!uref.raw_query.empty()) {
new_uri += "?";
new_uri += '?';
new_uri += uref.raw_query;
}
@ -108,7 +108,7 @@ bool path_match(const std::string &pattern, const std::string &path) {
if (pattern.back() != '/') {
return pattern == path;
}
return util::startsWith(path, pattern);
return util::starts_with(path, pattern);
}
} // namespace

View File

@ -71,6 +71,12 @@ using namespace nghttp2;
namespace h2load {
namespace {
bool recorded(const std::chrono::steady_clock::time_point &t) {
return std::chrono::steady_clock::duration::zero() != t.time_since_epoch();
}
} // namespace
Config::Config()
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
@ -92,11 +98,11 @@ Config config;
RequestStat::RequestStat() : data_offset(0), completed(false) {}
Stats::Stats(size_t req_todo)
Stats::Stats(size_t req_todo, size_t nclients)
: req_todo(0), req_started(0), req_done(0), req_success(0),
req_status_success(0), req_failed(0), req_error(0), req_timedout(0),
bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
status(), req_stats(req_todo) {}
status(), req_stats(req_todo), client_stats(nclients) {}
Stream::Stream() : status_success(-1) {}
@ -149,7 +155,8 @@ void rate_period_timeout_w_cb(struct ev_loop *loop, ev_timer *w, int revents) {
++req_todo;
--worker->nreqs_rem;
}
worker->clients.push_back(make_unique<Client>(worker, req_todo));
worker->clients.push_back(
make_unique<Client>(worker->next_client_id++, worker, req_todo));
auto &client = worker->clients.back();
if (client->connect() != 0) {
std::cerr << "client could not connect to host" << std::endl;
@ -232,11 +239,11 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
}
} // namespace
Client::Client(Worker *worker, size_t req_todo)
Client::Client(uint32_t id, Worker *worker, size_t req_todo)
: worker(worker), ssl(nullptr), next_addr(config.addrs),
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE),
first_byte_received(false), req_todo(req_todo), req_started(0),
req_done(0), fd(-1), new_connection_requested(false) {
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
req_started(0), req_done(0), id(id), fd(-1),
new_connection_requested(false) {
ev_io_init(&wev, writecb, 0, EV_WRITE);
ev_io_init(&rev, readcb, 0, EV_READ);
@ -302,7 +309,9 @@ int Client::make_socket(addrinfo *addr) {
int Client::connect() {
int rv;
record_start_time(&worker->stats);
record_client_start_time();
clear_connect_times();
record_connect_start_time();
if (worker->config->conn_inactivity_timeout > 0.) {
ev_timer_again(worker->loop, &conn_inactivity_watcher);
@ -360,23 +369,37 @@ void Client::fail() {
if (new_connection_requested) {
new_connection_requested = false;
if (req_started < req_todo) {
// At the moment, we don't have a facility to re-start request
// already in in-flight. Make them fail.
auto req_abandoned = req_started - req_done;
// Keep using current address
if (connect() == 0) {
return;
worker->stats.req_failed += req_abandoned;
worker->stats.req_error += req_abandoned;
worker->stats.req_done += req_abandoned;
req_done = req_started;
// Keep using current address
if (connect() == 0) {
return;
}
std::cerr << "client could not connect to host" << std::endl;
}
std::cerr << "client could not connect to host" << std::endl;
}
process_abandoned_streams();
}
void Client::disconnect() {
record_client_end_time();
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
ev_timer_stop(worker->loop, &conn_active_watcher);
ev_timer_stop(worker->loop, &request_timeout_watcher);
streams.clear();
session.reset();
wb.reset();
state = CLIENT_IDLE;
ev_io_stop(worker->loop, &wev);
ev_io_stop(worker->loop, &rev);
@ -590,6 +613,8 @@ void Client::on_stream_close(int32_t stream_id, bool success,
if (success) {
req_stat->completed = true;
++worker->stats.req_success;
auto &cstat = worker->stats.client_stats[id];
++cstat.req_success;
}
++worker->stats.req_done;
++req_done;
@ -720,7 +745,7 @@ int Client::connection_made() {
session->on_connect();
record_connect_time(&worker->stats);
record_connect_time();
if (!config.timing_script) {
auto nreq =
@ -742,10 +767,19 @@ int Client::connection_made() {
break;
}
duration = config.timings[reqidx];
if (reqidx == 0) {
// if reqidx wraps around back to 0, we uses up all lines and
// should break
break;
}
}
request_timeout_watcher.repeat = duration;
ev_timer_again(worker->loop, &request_timeout_watcher);
if (duration >= 1e-9) {
// double check since we may have break due to reqidx wraps
// around back to 0
request_timeout_watcher.repeat = duration;
ev_timer_again(worker->loop, &request_timeout_watcher);
}
}
signal_write();
@ -950,21 +984,51 @@ void Client::record_request_time(RequestStat *req_stat) {
req_stat->request_time = std::chrono::steady_clock::now();
}
void Client::record_start_time(Stats *stat) {
stat->start_times.push_back(std::chrono::steady_clock::now());
void Client::record_connect_start_time() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_start_time = std::chrono::steady_clock::now();
}
void Client::record_connect_time(Stats *stat) {
stat->connect_times.push_back(std::chrono::steady_clock::now());
void Client::record_connect_time() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_time = std::chrono::steady_clock::now();
}
void Client::record_ttfb() {
if (first_byte_received) {
auto &cstat = worker->stats.client_stats[id];
if (recorded(cstat.ttfb)) {
return;
}
first_byte_received = true;
worker->stats.ttfbs.push_back(std::chrono::steady_clock::now());
cstat.ttfb = std::chrono::steady_clock::now();
}
void Client::clear_connect_times() {
auto &cstat = worker->stats.client_stats[id];
cstat.connect_start_time = std::chrono::steady_clock::time_point();
cstat.connect_time = std::chrono::steady_clock::time_point();
cstat.ttfb = std::chrono::steady_clock::time_point();
}
void Client::record_client_start_time() {
auto &cstat = worker->stats.client_stats[id];
// Record start time only once at the very first connection is going
// to be made.
if (recorded(cstat.client_start_time)) {
return;
}
cstat.client_start_time = std::chrono::steady_clock::now();
}
void Client::record_client_end_time() {
auto &cstat = worker->stats.client_stats[id];
// Unlike client_start_time, we overwrite client_end_time. This
// handles multiple connect/disconnect for HTTP/1.1 benchmark.
cstat.client_end_time = std::chrono::steady_clock::now();
}
void Client::signal_write() { ev_io_start(worker->loop, &wev); }
@ -973,10 +1037,11 @@ void Client::try_new_connection() { new_connection_requested = true; }
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
size_t rate, Config *config)
: stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
id(id), tls_info_report_done(false), app_info_report_done(false),
nconns_made(0), nclients(nclients), nreqs_per_client(req_todo / nclients),
nreqs_rem(req_todo % nclients), rate(rate) {
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
config(config), id(id), tls_info_report_done(false),
app_info_report_done(false), nconns_made(0), nclients(nclients),
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
rate(rate), next_client_id(0) {
stats.req_todo = req_todo;
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
@ -992,7 +1057,7 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
++req_todo;
--nreqs_rem;
}
clients.push_back(make_unique<Client>(this, req_todo));
clients.push_back(make_unique<Client>(next_client_id++, this, req_todo));
}
}
}
@ -1025,9 +1090,7 @@ void Worker::run() {
namespace {
// Returns percentage of number of samples within mean +/- sd.
template <typename Duration>
double within_sd(const std::vector<Duration> &samples, const Duration &mean,
const Duration &sd) {
double within_sd(const std::vector<double> &samples, double mean, double sd) {
if (samples.size() == 0) {
return 0.0;
}
@ -1035,7 +1098,7 @@ double within_sd(const std::vector<Duration> &samples, const Duration &mean,
auto upper = mean + sd;
auto m = std::count_if(
std::begin(samples), std::end(samples),
[&lower, &upper](const Duration &t) { return lower <= t && t <= upper; });
[&lower, &upper](double t) { return lower <= t && t <= upper; });
return (m / static_cast<double>(samples.size())) * 100;
}
} // namespace
@ -1043,32 +1106,31 @@ double within_sd(const std::vector<Duration> &samples, const Duration &mean,
namespace {
// Computes statistics using |samples|. The min, max, mean, sd, and
// percentage of number of samples within mean +/- sd are computed.
template <typename Duration>
TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
SDStat compute_time_stat(const std::vector<double> &samples) {
if (samples.empty()) {
return {Duration::zero(), Duration::zero(), Duration::zero(),
Duration::zero(), 0.0};
return {0.0, 0.0, 0.0, 0.0, 0.0};
}
// standard deviation calculated using Rapid calculation method:
// http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
double a = 0, q = 0;
size_t n = 0;
int64_t sum = 0;
auto res = TimeStat<Duration>{Duration::max(), Duration::min()};
double sum = 0;
auto res = SDStat{std::numeric_limits<double>::max(),
std::numeric_limits<double>::min()};
for (const auto &t : samples) {
++n;
res.min = std::min(res.min, t);
res.max = std::max(res.max, t);
sum += t.count();
sum += t;
auto na = a + (t.count() - a) / n;
q += (t.count() - a) * (t.count() - na);
auto na = a + (t - a) / n;
q += (t - a) * (t - na);
a = na;
}
assert(n > 0);
res.mean = Duration(sum / n);
res.sd = Duration(static_cast<typename Duration::rep>(sqrt(q / n)));
res.mean = sum / n;
res.sd = sqrt(q / n);
res.within_sd = within_sd(samples, res.mean, res.sd);
return res;
@ -1076,19 +1138,20 @@ TimeStat<Duration> compute_time_stat(const std::vector<Duration> &samples) {
} // namespace
namespace {
TimeStats
SDStats
process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
size_t nrequest_times = 0, nttfb_times = 0;
size_t nrequest_times = 0;
for (const auto &w : workers) {
nrequest_times += w->stats.req_stats.size();
nttfb_times += w->stats.ttfbs.size();
}
std::vector<std::chrono::microseconds> request_times;
std::vector<double> request_times;
request_times.reserve(nrequest_times);
std::vector<std::chrono::microseconds> connect_times, ttfb_times;
connect_times.reserve(nttfb_times);
ttfb_times.reserve(nttfb_times);
std::vector<double> connect_times, ttfb_times, rps_values;
connect_times.reserve(config.nclients);
ttfb_times.reserve(config.nclients);
rps_values.reserve(config.nclients);
for (const auto &w : workers) {
for (const auto &req_stat : w->stats.req_stats) {
@ -1096,28 +1159,44 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
continue;
}
request_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
req_stat.stream_close_time - req_stat.request_time));
std::chrono::duration_cast<std::chrono::duration<double>>(
req_stat.stream_close_time - req_stat.request_time).count());
}
const auto &stat = w->stats;
// rule out cases where we started but didn't connect or get the
// first byte (errors). We will get connect event before FFTB.
assert(stat.start_times.size() >= stat.ttfbs.size());
assert(stat.connect_times.size() >= stat.ttfbs.size());
for (size_t i = 0; i < stat.ttfbs.size(); ++i) {
for (const auto &cstat : stat.client_stats) {
if (recorded(cstat.client_start_time) &&
recorded(cstat.client_end_time)) {
auto t = std::chrono::duration_cast<std::chrono::duration<double>>(
cstat.client_end_time - cstat.client_start_time).count();
if (t > 1e-9) {
rps_values.push_back(cstat.req_success / t);
}
}
// We will get connect event before FFTB.
if (!recorded(cstat.connect_start_time) ||
!recorded(cstat.connect_time)) {
continue;
}
connect_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
stat.connect_times[i] - stat.start_times[i]));
std::chrono::duration_cast<std::chrono::duration<double>>(
cstat.connect_time - cstat.connect_start_time).count());
if (!recorded(cstat.ttfb)) {
continue;
}
ttfb_times.push_back(
std::chrono::duration_cast<std::chrono::microseconds>(
stat.ttfbs[i] - stat.start_times[i]));
std::chrono::duration_cast<std::chrono::duration<double>>(
cstat.ttfb - cstat.connect_start_time).count());
}
}
return {compute_time_stat(request_times), compute_time_stat(connect_times),
compute_time_stat(ttfb_times)};
compute_time_stat(ttfb_times), compute_time_stat(rps_values)};
}
} // namespace
@ -1156,7 +1235,7 @@ std::string get_reqline(const char *uri, const http_parser_url &u) {
}
if (util::has_uri_field(u, UF_QUERY)) {
reqline += "?";
reqline += '?';
reqline += util::get_uri_field(uri, u, UF_QUERY);
}
@ -1469,6 +1548,9 @@ Options:
only and any white spaces are treated as a part of
protocol string.
Default: )" << DEFAULT_NPN_LIST << R"(
--h1 Short hand for --npn-list=http/1.1
--no-tls-proto=http/1.1, which effectively force
http/1.1 for both http and https URI.
-v, --verbose
Output debug information.
--version Display version information and exit.
@ -1516,6 +1598,7 @@ int main(int argc, char **argv) {
{"base-uri", required_argument, nullptr, 'B'},
{"npn-list", required_argument, &flag, 4},
{"rate-period", required_argument, &flag, 5},
{"h1", no_argument, &flag, 6},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
auto c = getopt_long(argc, argv, "hvW:c:d:m:n:p:t:w:H:i:r:T:N:B:",
@ -1682,6 +1765,11 @@ int main(int argc, char **argv) {
exit(EXIT_FAILURE);
}
break;
case 6:
// --h1
config.npn_list = util::parse_config_str_list("http/1.1");
config.no_tls_proto = Config::PROTO_HTTP1_1;
break;
}
break;
default:
@ -1893,8 +1981,8 @@ int main(int argc, char **argv) {
shared_nva.emplace_back("user-agent", user_agent);
// list overridalbe headers
auto override_hdrs =
make_array<std::string>(":authority", ":host", ":method", ":scheme");
auto override_hdrs = make_array<std::string>(":authority", ":host", ":method",
":scheme", "user-agent");
for (auto &kv : config.custom_headers) {
if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
@ -1912,20 +2000,44 @@ int main(int argc, char **argv) {
}
}
auto method_it =
std::find_if(std::begin(shared_nva), std::end(shared_nva),
[](const Header &nv) { return nv.name == ":method"; });
assert(method_it != std::end(shared_nva));
config.h1reqs.reserve(reqlines.size());
config.nva.reserve(reqlines.size());
config.nv.reserve(reqlines.size());
for (auto &req : reqlines) {
// For HTTP/1.1
std::string h1req;
h1req = config.data_fd == -1 ? "GET" : "POST";
h1req += " " + req;
auto h1req = (*method_it).value;
h1req += ' ';
h1req += req;
h1req += " HTTP/1.1\r\n";
h1req += "Host: " + config.host + "\r\n";
h1req += "User-Agent: " + user_agent + "\r\n";
h1req += "Accept: */*\r\n";
for (auto &nv : shared_nva) {
if (nv.name == ":authority") {
h1req += "Host: ";
h1req += nv.value;
h1req += "\r\n";
continue;
}
if (nv.name[0] == ':') {
continue;
}
h1req += nv.name;
h1req += ": ";
h1req += nv.value;
h1req += "\r\n";
}
h1req += "\r\n";
config.h1reqs.push_back(h1req);
config.h1reqs.push_back(std::move(h1req));
// For nghttp2
std::vector<nghttp2_nv> nva;
// 1 for :path
nva.reserve(1 + shared_nva.size());
nva.push_back(http2::make_nv_ls(":path", req));
@ -1937,6 +2049,8 @@ int main(int argc, char **argv) {
// For spdylay
std::vector<const char *> cva;
// 2 for :path and :version, 1 for terminal nullptr
cva.reserve(2 * (2 + shared_nva.size()) + 1);
cva.push_back(":path");
cva.push_back(req.c_str());
@ -2058,7 +2172,7 @@ int main(int argc, char **argv) {
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
Stats stats(0);
Stats stats(0, 0);
for (const auto &w : workers) {
const auto &s = w->stats;
@ -2138,7 +2252,11 @@ time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
<< util::format_duration(ts.ttfb.max) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.mean) << " " << std::setw(10)
<< util::format_duration(ts.ttfb.sd) << std::setw(9)
<< util::dtos(ts.ttfb.within_sd) << "%" << std::endl;
<< util::dtos(ts.ttfb.within_sd) << "%"
<< "\nreq/s (client) : " << std::setw(10) << ts.rps.min << " "
<< std::setw(10) << ts.rps.max << " " << std::setw(10)
<< ts.rps.mean << " " << std::setw(10) << ts.rps.sd << std::setw(9)
<< util::dtos(ts.rps.within_sd) << "%" << std::endl;
SSL_CTX_free(ssl_ctx);

View File

@ -124,26 +124,47 @@ struct RequestStat {
bool completed;
};
template <typename Duration> struct TimeStat {
struct ClientStat {
// time client started (i.e., first connect starts)
std::chrono::steady_clock::time_point client_start_time;
// time client end (i.e., client somehow processed all requests it
// is responsible for, and disconnected)
std::chrono::steady_clock::time_point client_end_time;
// The number of requests completed successfull, but not necessarily
// means successful HTTP status code.
size_t req_success;
// The following 3 numbers are overwritten each time when connection
// is made.
// time connect starts
std::chrono::steady_clock::time_point connect_start_time;
// time to connect
std::chrono::steady_clock::time_point connect_time;
// time to first byte (TTFB)
std::chrono::steady_clock::time_point ttfb;
};
struct SDStat {
// min, max, mean and sd (standard deviation)
Duration min, max, mean, sd;
double min, max, mean, sd;
// percentage of samples inside mean -/+ sd
double within_sd;
};
struct TimeStats {
struct SDStats {
// time for request
TimeStat<std::chrono::microseconds> request;
SDStat request;
// time for connect
TimeStat<std::chrono::microseconds> connect;
SDStat connect;
// time to first byte (TTFB)
TimeStat<std::chrono::microseconds> ttfb;
SDStat ttfb;
// request per second for each client
SDStat rps;
};
enum TimeStatType { STAT_REQUEST, STAT_CONNECT, STAT_FIRST_BYTE };
struct Stats {
Stats(size_t req_todo);
Stats(size_t req_todo, size_t nclients);
// The total number of requests
size_t req_todo;
// The number of requests issued so far
@ -179,12 +200,8 @@ struct Stats {
std::array<size_t, 6> status;
// The statistics per request
std::vector<RequestStat> req_stats;
// time connect starts
std::vector<std::chrono::steady_clock::time_point> start_times;
// time to connect
std::vector<std::chrono::steady_clock::time_point> connect_times;
// time to first byte (TTFB)
std::vector<std::chrono::steady_clock::time_point> ttfbs;
// THe statistics per client
std::vector<ClientStat> client_stats;
};
enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
@ -210,6 +227,8 @@ struct Worker {
size_t nreqs_rem;
size_t rate;
ev_timer timeout_watcher;
// The next client ID this worker assigns
uint32_t next_client_id;
Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
size_t rate, Config *config);
@ -240,13 +259,14 @@ struct Client {
addrinfo *current_addr;
size_t reqidx;
ClientState state;
bool first_byte_received;
// The number of requests this client has to issue.
size_t req_todo;
// The number of requests this client has issued so far.
size_t req_started;
// The number of requests this client has done so far.
size_t req_done;
// The client id per worker
uint32_t id;
int fd;
Buffer<64_k> wb;
ev_timer conn_active_watcher;
@ -256,7 +276,7 @@ struct Client {
enum { ERR_CONNECT_FAIL = -100 };
Client(Worker *worker, size_t req_todo);
Client(uint32_t id, Worker *worker, size_t req_todo);
~Client();
int make_socket(addrinfo *addr);
int connect();
@ -302,9 +322,12 @@ struct Client {
bool final = false);
void record_request_time(RequestStat *req_stat);
void record_start_time(Stats *stat);
void record_connect_time(Stats *stat);
void record_connect_start_time();
void record_connect_time();
void record_ttfb();
void clear_connect_times();
void record_client_start_time();
void record_client_end_time();
void signal_write();
};

View File

@ -51,7 +51,15 @@ Http1Session::~Http1Session() {}
namespace {
// HTTP response message begin
int htp_msg_begincb(http_parser *htp) { return 0; }
int htp_msg_begincb(http_parser *htp) {
auto session = static_cast<Http1Session *>(htp->data);
if (session->stream_resp_counter_ >= session->stream_req_counter_) {
return -1;
}
return 0;
}
} // namespace
namespace {
@ -144,7 +152,7 @@ void Http1Session::on_connect() { client_->signal_write(); }
int Http1Session::submit_request(RequestStat *req_stat) {
auto config = client_->worker->config;
auto req = config->h1reqs[client_->reqidx];
const auto &req = config->h1reqs[client_->reqidx];
client_->reqidx++;
if (client_->reqidx == config->h1reqs.size()) {

View File

@ -454,8 +454,8 @@ std::string rewrite_location_uri(const std::string &uri,
return "";
}
auto field = &u.field_data[UF_HOST];
if (!util::startsWith(std::begin(match_host), std::end(match_host),
&uri[field->off], &uri[field->off] + field->len) ||
if (!util::starts_with(std::begin(match_host), std::end(match_host),
&uri[field->off], &uri[field->off] + field->len) ||
(match_host.size() != field->len && match_host[field->len] != ':')) {
return "";
}
@ -471,12 +471,12 @@ std::string rewrite_location_uri(const std::string &uri,
}
if (u.field_set & (1 << UF_QUERY)) {
field = &u.field_data[UF_QUERY];
res += "?";
res += '?';
res.append(&uri[field->off], field->len);
}
if (u.field_set & (1 << UF_FRAGMENT)) {
field = &u.field_data[UF_FRAGMENT];
res += "#";
res += '#';
res.append(&uri[field->off], field->len);
}
return res;
@ -1185,12 +1185,12 @@ std::string path_join(const char *base_path, size_t base_pathlen,
}
if (rel_querylen == 0) {
if (base_querylen) {
res += "?";
res += '?';
res.append(base_query, base_querylen);
}
return res;
}
res += "?";
res += '?';
res.append(rel_query, rel_querylen);
return res;
}
@ -1242,7 +1242,7 @@ std::string path_join(const char *base_path, size_t base_pathlen,
;
}
if (rel_querylen) {
res += "?";
res += '?';
res.append(rel_query, rel_querylen);
}
return res;
@ -1500,7 +1500,7 @@ int construct_push_component(std::string &scheme, std::string &authority,
if (u.field_set & (1 << UF_HOST)) {
http2::copy_url_component(authority, &u, UF_HOST, uri);
if (u.field_set & (1 << UF_PORT)) {
authority += ":";
authority += ':';
authority += util::utos(u.port);
}
}

View File

@ -340,10 +340,11 @@ std::string normalize_path(InputIt first, InputIt last) {
} else {
for (; first < last - 2;) {
if (*first == '%') {
if (util::isHexDigit(*(first + 1)) && util::isHexDigit(*(first + 2))) {
if (util::is_hex_digit(*(first + 1)) &&
util::is_hex_digit(*(first + 2))) {
auto c = (util::hex_to_uint(*(first + 1)) << 4) +
util::hex_to_uint(*(first + 2));
if (util::inRFC3986UnreservedChars(c)) {
if (util::in_rfc3986_unreserved_chars(c)) {
result += c;
first += 3;
continue;

View File

@ -95,8 +95,7 @@ constexpr auto anchors = std::array<Anchor, 5>{{
Config::Config()
: header_table_size(-1),
min_header_table_size(std::numeric_limits<uint32_t>::max()), padding(0),
max_concurrent_streams(100),
peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
max_concurrent_streams(100), peer_max_concurrent_streams(100),
weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), timeout(0.), window_bits(-1),
connection_window_bits(-1), verbose(0), null_out(false),
remote_name(false), get_assets(false), stat(false), upgrade(false),
@ -168,7 +167,7 @@ std::string Request::make_reqpath() const {
? util::get_uri_field(uri.c_str(), u, UF_PATH)
: "/";
if (util::has_uri_field(u, UF_QUERY)) {
path += "?";
path += '?';
path.append(uri.c_str() + u.field_data[UF_QUERY].off,
u.field_data[UF_QUERY].len);
}
@ -199,7 +198,7 @@ std::string decode_host(std::string host) {
auto zone_id_src = (*(zone_start + 1) == '2' && *(zone_start + 2) == '5')
? zone_start + 3
: zone_start + 1;
auto zone_id = util::percentDecode(zone_id_src, std::end(host));
auto zone_id = util::percent_decode(zone_id_src, std::end(host));
host.erase(zone_start + 1, std::end(host));
host += zone_id;
return host;
@ -828,7 +827,7 @@ int HttpClient::on_upgrade_connect() {
reqvec[0]->method = "GET";
} else {
req = (*meth).value;
req += " ";
req += ' ';
reqvec[0]->method = (*meth).value;
}
req += reqvec[0]->make_reqpath();
@ -2449,7 +2448,7 @@ Options:
-M, --peer-max-concurrent-streams=<N>
Use <N> as SETTINGS_MAX_CONCURRENT_STREAMS value of
remote endpoint as if it is received in SETTINGS frame.
The default is large enough as it is seen as unlimited.
Default: 100
-c, --header-table-size=<SIZE>
Specify decoder header table size. If this option is
used multiple times, and the minimum value among the

View File

@ -325,11 +325,11 @@ void exec_binary(SignalServer *ssv) {
}
for (size_t i = 0; i < envlen; ++i) {
if (util::startsWith(environ[i], ENV_LISTENER4_FD) ||
util::startsWith(environ[i], ENV_LISTENER6_FD) ||
util::startsWith(environ[i], ENV_PORT) ||
util::startsWith(environ[i], ENV_UNIX_FD) ||
util::startsWith(environ[i], ENV_UNIX_PATH)) {
if (util::starts_with(environ[i], ENV_LISTENER4_FD) ||
util::starts_with(environ[i], ENV_LISTENER6_FD) ||
util::starts_with(environ[i], ENV_PORT) ||
util::starts_with(environ[i], ENV_UNIX_FD) ||
util::starts_with(environ[i], ENV_UNIX_PATH)) {
continue;
}
@ -1480,9 +1480,14 @@ HTTP/2 and SPDY:
meant for debugging purpose and not intended to enhance
protocol security.
--no-server-push
Disable HTTP/2 server push. Server push is only
supported by default mode and HTTP/2 frontend. SPDY
frontend does not support server push.
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies --http2-bridge or --client mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
Mode:
(default mode)

View File

@ -859,13 +859,13 @@ ssize_t parse_proxy_line_port(const uint8_t *first, const uint8_t *last) {
}
if (*p == '0') {
if (p + 1 != last && util::isDigit(*(p + 1))) {
if (p + 1 != last && util::is_digit(*(p + 1))) {
return -1;
}
return 1;
}
for (; p != last && util::isDigit(*p); ++p) {
for (; p != last && util::is_digit(*p); ++p) {
port *= 10;
port += *p - '0';

View File

@ -471,7 +471,7 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
namespace {
bool var_token(char c) {
return util::isAlpha(c) || util::isDigit(c) || c == '_';
return util::is_alpha(c) || util::is_digit(c) || c == '_';
}
} // namespace
@ -519,7 +519,7 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
auto type = log_var_lookup_token(var_name, var_namelen);
if (type == SHRPX_LOGF_NONE) {
if (util::istartsWith(var_name, var_namelen, "http_")) {
if (util::istarts_with(var_name, var_namelen, "http_")) {
if (util::streq("host", var_name + str_size("http_"),
var_namelen - str_size("http_"))) {
// Special handling of host header field. We will use
@ -596,7 +596,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
// This effectively makes empty pattern to "/".
pattern.assign(raw_pattern.first, raw_pattern.second);
util::inp_strlower(pattern);
pattern += "/";
pattern += '/';
} else {
pattern.assign(raw_pattern.first, slash);
util::inp_strlower(pattern);
@ -1342,7 +1342,7 @@ int parse_config(const char *opt, const char *optarg,
pat_delim = optarg + optarglen;
}
DownstreamAddr addr;
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = strcopy(path, pat_delim);
addr.host_unix = true;
@ -1368,7 +1368,7 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_FRONTEND: {
if (util::istartsWith(optarg, SHRPX_UNIX_PATH_PREFIX)) {
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
mod_config()->host = strcopy(path);
mod_config()->port = 0;
@ -1664,7 +1664,7 @@ int parse_config(const char *opt, const char *optarg,
// Surprisingly, u.field_set & UF_USERINFO is nonzero even if
// userinfo component is empty string.
if (!val.empty()) {
val = util::percentDecode(val.begin(), val.end());
val = util::percent_decode(std::begin(val), std::end(val));
mod_config()->downstream_http_proxy_userinfo = strcopy(val);
}
}

View File

@ -120,7 +120,8 @@ bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
}
} // namespace
Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream) {
Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
bool next_blocked) {
// Delete downstream when this function returns.
auto delptr = std::unique_ptr<Downstream>(downstream);
@ -144,7 +145,7 @@ Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream) {
return nullptr;
}
if (ent.num_active >= conn_max_per_host_) {
if (!next_blocked || ent.num_active >= conn_max_per_host_) {
return nullptr;
}

View File

@ -79,10 +79,12 @@ public:
// |host|.
bool can_activate(const std::string &host) const;
// Removes and frees |downstream| object. If |downstream| is in
// Downstream::DISPATCH_ACTIVE, this function may return Downstream
// object with the same target host in Downstream::DISPATCH_BLOCKED
// if its connection is now not blocked by conn_max_per_host_ limit.
Downstream *remove_and_get_blocked(Downstream *downstream);
// Downstream::DISPATCH_ACTIVE, and |next_blocked| is true, this
// function may return Downstream object with the same target host
// in Downstream::DISPATCH_BLOCKED if its connection is now not
// blocked by conn_max_per_host_ limit.
Downstream *remove_and_get_blocked(Downstream *downstream,
bool next_blocked = true);
Downstream *get_downstreams() const;
HostEntry &find_host_entry(const std::string &host);
const std::string &make_host_key(const std::string &host) const;

View File

@ -55,7 +55,7 @@ std::string create_via_header_value(int major, int minor) {
std::string hdrs;
hdrs += static_cast<char>(major + '0');
if (major < 2) {
hdrs += ".";
hdrs += '.';
hdrs += static_cast<char>(minor + '0');
}
hdrs += " nghttpx";

View File

@ -67,14 +67,8 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
error_code = NGHTTP2_INTERNAL_ERROR;
}
if (downstream_->get_downstream_stream_id() != -1) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
<< ", stream_id="
<< downstream_->get_downstream_stream_id()
<< ", error_code=" << error_code;
}
if (http2session_->get_state() == Http2Session::CONNECTED &&
downstream_->get_downstream_stream_id() != -1) {
submit_rst_stream(downstream_, error_code);
http2session_->consume(downstream_->get_downstream_stream_id(),
@ -143,7 +137,8 @@ int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream,
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream
<< ", stream_id="
<< downstream->get_downstream_stream_id();
<< downstream->get_downstream_stream_id()
<< ", error_code=" << error_code;
}
rv = http2session_->submit_rst_stream(
downstream->get_downstream_stream_id(), error_code);

View File

@ -520,7 +520,7 @@ int Http2Session::downstream_connect_proxy() {
std::string req = "CONNECT ";
req += downstream_addr.hostport.get();
if (downstream_addr.port == 80 || downstream_addr.port == 443) {
req += ":";
req += ':';
req += util::utos(downstream_addr.port);
}
req += " HTTP/1.1\r\nHost: ";
@ -682,32 +682,43 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
if (dconn) {
auto downstream = dconn->get_downstream();
if (downstream && downstream->get_downstream_stream_id() == stream_id) {
auto upstream = downstream->get_upstream();
if (downstream->get_upgraded() &&
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// For tunneled connection, we have to submit RST_STREAM to
// upstream *after* whole response body is sent. We just set
// MSG_COMPLETE here. Upstream will take care of that.
downstream->get_upstream()->on_downstream_body_complete(downstream);
downstream->set_response_state(Downstream::MSG_COMPLETE);
} else if (error_code == NGHTTP2_NO_ERROR) {
switch (downstream->get_response_state()) {
case Downstream::MSG_COMPLETE:
case Downstream::MSG_BAD_HEADER:
break;
default:
if (downstream->get_downstream_stream_id() % 2 == 0 &&
downstream->get_request_state() == Downstream::INITIAL) {
// Downstream is canceled in backend before it is submitted in
// frontend session.
// This will avoid to send RST_STREAM to backend
downstream->set_response_state(Downstream::MSG_RESET);
upstream->cancel_premature_downstream(downstream);
} else {
if (downstream->get_upgraded() &&
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// For tunneled connection, we have to submit RST_STREAM to
// upstream *after* whole response body is sent. We just set
// MSG_COMPLETE here. Upstream will take care of that.
downstream->get_upstream()->on_downstream_body_complete(downstream);
downstream->set_response_state(Downstream::MSG_COMPLETE);
} else if (error_code == NGHTTP2_NO_ERROR) {
switch (downstream->get_response_state()) {
case Downstream::MSG_COMPLETE:
case Downstream::MSG_BAD_HEADER:
break;
default:
downstream->set_response_state(Downstream::MSG_RESET);
}
} else if (downstream->get_response_state() !=
Downstream::MSG_BAD_HEADER) {
downstream->set_response_state(Downstream::MSG_RESET);
}
} else if (downstream->get_response_state() !=
Downstream::MSG_BAD_HEADER) {
downstream->set_response_state(Downstream::MSG_RESET);
if (downstream->get_response_state() == Downstream::MSG_RESET &&
downstream->get_response_rst_stream_error_code() ==
NGHTTP2_NO_ERROR) {
downstream->set_response_rst_stream_error_code(error_code);
}
call_downstream_readcb(http2session, downstream);
}
if (downstream->get_response_state() == Downstream::MSG_RESET &&
downstream->get_response_rst_stream_error_code() ==
NGHTTP2_NO_ERROR) {
downstream->set_response_rst_stream_error_code(error_code);
}
call_downstream_readcb(http2session, downstream);
// dconn may be deleted
}
}
@ -730,6 +741,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
auto http2session = static_cast<Http2Session *>(user_data);
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
@ -740,44 +752,80 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (frame->hd.type != NGHTTP2_HEADERS) {
return 0;
}
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!downstream->get_expect_final_response();
auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
!downstream->get_expect_final_response();
if (downstream->get_response_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< downstream->get_response_headers_sum() + namelen + valuelen
<< ", num=" << downstream->get_response_headers().size() + 1;
}
if (downstream->get_response_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
downstream->get_response_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< downstream->get_response_headers_sum() + namelen + valuelen
<< ", num=" << downstream->get_response_headers().size() + 1;
if (trailer) {
// we don't care trailer part exceeds header size limit; just
// discard it.
return 0;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (trailer) {
// we don't care trailer part exceeds header size limit; just
// discard it.
// just store header fields for trailer part
downstream->add_response_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
return 0;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(name, namelen);
if (trailer) {
// just store header fields for trailer part
downstream->add_response_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
downstream->add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
case NGHTTP2_PUSH_PROMISE: {
auto promised_stream_id = frame->push_promise.promised_stream_id;
auto promised_sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, promised_stream_id));
if (!promised_sd || !promised_sd->dconn) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
return 0;
}
auto token = http2::lookup_token(name, namelen);
auto promised_downstream = promised_sd->dconn->get_downstream();
assert(promised_downstream);
if (promised_downstream->get_request_headers_sum() + namelen + valuelen >
get_config()->header_field_buffer ||
promised_downstream->get_request_headers().size() >=
get_config()->max_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, promised_downstream)
<< "Too large or many header field size="
<< promised_downstream->get_request_headers_sum() + namelen +
valuelen << ", num="
<< promised_downstream->get_request_headers().size() + 1;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(name, namelen);
promised_downstream->add_request_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX,
token);
return 0;
}
}
downstream->add_response_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
} // namespace
@ -786,24 +834,52 @@ namespace {
int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) {
auto http2session = static_cast<Http2Session *>(user_data);
if (frame->hd.type != NGHTTP2_HEADERS ||
frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE) {
return 0;
}
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
auto downstream = sd->dconn->get_downstream();
if (!downstream ||
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
return 0;
}
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
case NGHTTP2_PUSH_PROMISE: {
auto promised_stream_id = frame->push_promise.promised_stream_id;
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
return 0;
}
auto downstream = sd->dconn->get_downstream();
assert(downstream);
assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
if (http2session->handle_downstream_push_promise(downstream,
promised_stream_id) != 0) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
}
return 0;
}
auto downstream = sd->dconn->get_downstream();
if (!downstream ||
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
http2session->submit_rst_stream(frame->hd.stream_id,
NGHTTP2_INTERNAL_ERROR);
return 0;
}
return 0;
}
} // namespace
@ -976,7 +1052,8 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
rv = on_response_headers(http2session, downstream, session, frame);
if (rv != 0) {
@ -1044,17 +1121,47 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
http2session->connection_alive();
}
return 0;
case NGHTTP2_PUSH_PROMISE:
case NGHTTP2_PUSH_PROMISE: {
auto promised_stream_id = frame->push_promise.promised_stream_id;
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session)
<< "Received downstream PUSH_PROMISE stream_id="
<< frame->hd.stream_id
<< ", promised_stream_id=" << frame->push_promise.promised_stream_id;
<< ", promised_stream_id=" << promised_stream_id;
}
// We just respond with RST_STREAM.
http2session->submit_rst_stream(frame->push_promise.promised_stream_id,
NGHTTP2_REFUSED_STREAM);
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!sd || !sd->dconn) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
return 0;
}
auto downstream = sd->dconn->get_downstream();
assert(downstream);
assert(downstream->get_downstream_stream_id() == frame->hd.stream_id);
auto promised_sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, promised_stream_id));
if (!promised_sd || !promised_sd->dconn) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
return 0;
}
auto promised_downstream = promised_sd->dconn->get_downstream();
assert(promised_downstream);
if (http2session->handle_downstream_push_promise_complete(
downstream, promised_downstream) != 0) {
http2session->submit_rst_stream(promised_stream_id, NGHTTP2_CANCEL);
return 0;
}
return 0;
}
default:
return 0;
}
@ -1279,16 +1386,22 @@ int Http2Session::connection_made() {
flow_control_ = true;
std::array<nghttp2_settings_entry, 3> entry;
entry[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
entry[0].value = 0;
entry[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[1].value = get_config()->http2_max_concurrent_streams;
size_t nentry = 2;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = get_config()->http2_max_concurrent_streams;
entry[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[2].value = (1 << get_config()->http2_downstream_window_bits) - 1;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << get_config()->http2_downstream_window_bits) - 1;
if (get_config()->no_server_push || get_config()->http2_proxy ||
get_config()->client_proxy) {
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
entry[nentry].value = 0;
++nentry;
}
rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
entry.size());
nentry);
if (rv != 0) {
return -1;
}
@ -1771,4 +1884,96 @@ size_t Http2Session::get_group() const { return group_; }
size_t Http2Session::get_index() const { return index_; }
int Http2Session::handle_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) {
auto upstream = downstream->get_upstream();
if (!upstream->push_enabled()) {
return -1;
}
auto promised_downstream =
upstream->on_downstream_push_promise(downstream, promised_stream_id);
if (!promised_downstream) {
return -1;
}
// Now we have Downstream object for pushed stream.
// promised_downstream->get_stream() still returns 0.
auto handler = upstream->get_client_handler();
auto worker = handler->get_worker();
auto promised_dconn =
make_unique<Http2DownstreamConnection>(worker->get_dconn_pool(), this);
promised_dconn->set_client_handler(handler);
auto ptr = promised_dconn.get();
if (promised_downstream->attach_downstream_connection(
std::move(promised_dconn)) != 0) {
return -1;
}
auto promised_sd = make_unique<StreamData>();
nghttp2_session_set_stream_user_data(session_, promised_stream_id,
promised_sd.get());
ptr->attach_stream_data(promised_sd.get());
streams_.append(promised_sd.release());
return 0;
}
int Http2Session::handle_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
auto authority =
promised_downstream->get_request_header(http2::HD__AUTHORITY);
auto path = promised_downstream->get_request_header(http2::HD__PATH);
auto method = promised_downstream->get_request_header(http2::HD__METHOD);
auto scheme = promised_downstream->get_request_header(http2::HD__SCHEME);
if (!authority) {
authority = promised_downstream->get_request_header(http2::HD_HOST);
}
auto method_token = http2::lookup_method_token(method->value);
if (method_token == -1) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Unrecognized method: " << method->value;
}
return -1;
}
// TODO Rewrite authority if we enabled rewrite host. But we
// really don't know how to rewrite host. Should we use the same
// host in associated stream?
promised_downstream->set_request_http2_authority(
http2::value_to_str(authority));
promised_downstream->set_request_method(method_token);
// libnghttp2 ensures that we don't have CONNECT method in
// PUSH_PROMISE, and guarantees that :scheme exists.
promised_downstream->set_request_http2_scheme(http2::value_to_str(scheme));
// For server-wide OPTIONS request, path is empty.
if (method_token != HTTP_OPTIONS || path->value != "*") {
promised_downstream->set_request_path(http2::rewrite_clean_path(
std::begin(path->value), std::end(path->value)));
}
promised_downstream->inspect_http2_request();
auto upstream = promised_downstream->get_upstream();
promised_downstream->set_request_state(Downstream::MSG_COMPLETE);
if (upstream->on_downstream_push_promise_complete(downstream,
promised_downstream) != 0) {
return -1;
}
return 0;
}
} // namespace shrpx

View File

@ -156,6 +156,11 @@ public:
size_t get_index() const;
int handle_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id);
int handle_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream);
enum {
// Disconnected
DISCONNECTED,

View File

@ -543,6 +543,13 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
case NGHTTP2_PUSH_PROMISE: {
auto promised_stream_id = frame->push_promise.promised_stream_id;
if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) {
// In case of push from backend, downstream object was already
// created.
return 0;
}
auto downstream = make_unique<Downstream>(upstream, handler->get_mcpool(),
promised_stream_id, 0);
@ -1712,6 +1719,7 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
}
if (!downstream->request_submission_ready()) {
// pushed stream is handled here
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
downstream->pop_downstream_connection();
continue;
@ -1798,7 +1806,6 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
const std::string &authority,
const std::string &path,
Downstream *downstream) {
int rv;
std::vector<nghttp2_nv> nva;
// 4 for :method, :scheme, :path and :authority
nva.reserve(4 + downstream->get_request_headers().size());
@ -1827,16 +1834,16 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
}
}
rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
downstream->get_stream_id(), nva.data(),
nva.size(), nullptr);
auto promised_stream_id = nghttp2_submit_push_promise(
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
nva.size(), nullptr);
if (rv < 0) {
if (promised_stream_id < 0) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
<< nghttp2_strerror(rv);
<< nghttp2_strerror(promised_stream_id);
}
if (nghttp2_is_fatal(rv)) {
if (nghttp2_is_fatal(promised_stream_id)) {
return -1;
}
return 0;
@ -1847,22 +1854,25 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
for (auto &nv : nva) {
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
}
ULOG(INFO, this) << "HTTP push request headers. promised_stream_id=" << rv
<< "\n" << ss.str();
ULOG(INFO, this) << "HTTP push request headers. promised_stream_id="
<< promised_stream_id << "\n" << ss.str();
}
return 0;
}
bool Http2Upstream::push_enabled() const {
return !(get_config()->no_server_push ||
nghttp2_session_get_remote_settings(
session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
get_config()->http2_proxy || get_config()->client_proxy);
}
int Http2Upstream::initiate_push(Downstream *downstream, const char *uri,
size_t len) {
int rv;
if (len == 0 || get_config()->no_server_push ||
nghttp2_session_get_remote_settings(session_,
NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
get_config()->http2_proxy || get_config()->client_proxy ||
(downstream->get_stream_id() % 2) == 0) {
if (len == 0 || !push_enabled() || (downstream->get_stream_id() % 2)) {
return 0;
}
@ -1917,4 +1927,58 @@ bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; }
Http2Upstream::WriteBuffer *Http2Upstream::get_response_buf() { return &wb_; }
Downstream *
Http2Upstream::on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) {
// promised_stream_id is for backend HTTP/2 session, not for
// frontend.
auto promised_downstream =
make_unique<Downstream>(this, handler_->get_mcpool(), 0, 0);
promised_downstream->set_downstream_stream_id(promised_stream_id);
promised_downstream->disable_upstream_rtimer();
promised_downstream->set_request_major(2);
promised_downstream->set_request_minor(0);
auto ptr = promised_downstream.get();
add_pending_downstream(std::move(promised_downstream));
downstream_queue_.mark_active(ptr);
return ptr;
}
int Http2Upstream::on_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
std::vector<nghttp2_nv> nva;
auto &headers = promised_downstream->get_request_headers();
nva.reserve(headers.size());
for (auto &kv : headers) {
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
}
auto promised_stream_id = nghttp2_submit_push_promise(
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
nva.size(), promised_downstream);
if (promised_stream_id < 0) {
return -1;
}
promised_downstream->set_stream_id(promised_stream_id);
return 0;
}
void Http2Upstream::cancel_premature_downstream(
Downstream *promised_downstream) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Remove premature promised stream "
<< promised_downstream;
}
downstream_queue_.remove_and_get_blocked(promised_downstream, false);
}
} // namespace shrpx

View File

@ -87,6 +87,14 @@ public:
virtual void response_drain(size_t n);
virtual bool response_empty() const;
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id);
virtual int
on_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream);
virtual bool push_enabled() const;
virtual void cancel_premature_downstream(Downstream *promised_downstream);
bool get_flow_control() const;
// Perform HTTP/2 upgrade from |upstream|. On success, this object
// takes ownership of the |upstream|. This function returns 0 if it

View File

@ -1191,4 +1191,20 @@ bool HttpsUpstream::response_empty() const {
return buf->rleft() == 0;
}
Downstream *
HttpsUpstream::on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) {
return nullptr;
}
int HttpsUpstream::on_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
return -1;
}
bool HttpsUpstream::push_enabled() const { return false; }
void HttpsUpstream::cancel_premature_downstream(
Downstream *promised_downstream) {}
} // namespace shrpx

View File

@ -82,6 +82,14 @@ public:
virtual void response_drain(size_t n);
virtual bool response_empty() const;
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id);
virtual int
on_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream);
virtual bool push_enabled() const;
virtual void cancel_premature_downstream(Downstream *promised_downstream);
void reset_current_header_length();
void log_response_headers(DefaultMemchunks *buf) const;

View File

@ -297,7 +297,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
if (frac.size() < 3) {
frac = std::string(3 - frac.size(), '0') + frac;
}
sec += ".";
sec += '.';
sec += frac;
std::tie(p, avail) = copy(sec, avail, p);
@ -415,12 +415,12 @@ void log_chld(pid_t pid, int rstatus, const char *msg) {
auto s = strsignal(sig);
if (s) {
signalstr += s;
signalstr += "(";
signalstr += '(';
} else {
signalstr += "UNKNOWN(";
}
signalstr += util::utos(sig);
signalstr += ")";
signalstr += ')';
}
LOG(NOTICE) << msg << ": [" << pid << "] exited "

View File

@ -1230,4 +1230,20 @@ bool SpdyUpstream::response_empty() const { return wb_.rleft() == 0; }
SpdyUpstream::WriteBuffer *SpdyUpstream::get_response_buf() { return &wb_; }
Downstream *
SpdyUpstream::on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) {
return nullptr;
}
int SpdyUpstream::on_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
return -1;
}
bool SpdyUpstream::push_enabled() const { return false; }
void SpdyUpstream::cancel_premature_downstream(
Downstream *promised_downstream) {}
} // namespace shrpx

View File

@ -82,6 +82,14 @@ public:
virtual void response_drain(size_t n);
virtual bool response_empty() const;
virtual Downstream *on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id);
virtual int
on_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream);
virtual bool push_enabled() const;
virtual void cancel_premature_downstream(Downstream *promised_downstream);
bool get_flow_control() const;
int consume(int32_t stream_id, size_t len);

View File

@ -792,7 +792,7 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
// Don't attempt to match a presented identifier where the wildcard
// character is embedded within an A-label.
if (ptLeftLabelEnd == 0 || strchr(ptLeftLabelEnd + 1, '.') == 0 ||
ptLeftLabelEnd < ptWildcard || util::istartsWith(pattern, "xn--")) {
ptLeftLabelEnd < ptWildcard || util::istarts_with(pattern, "xn--")) {
wildcardEnabled = false;
}
if (!wildcardEnabled) {
@ -807,9 +807,9 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
if (hnLeftLabelEnd - hostname < ptLeftLabelEnd - pattern) {
return false;
}
return util::istartsWith(hostname, hnLeftLabelEnd, pattern, ptWildcard) &&
util::iendsWith(hostname, hnLeftLabelEnd, ptWildcard + 1,
ptLeftLabelEnd);
return util::istarts_with(hostname, hnLeftLabelEnd, pattern, ptWildcard) &&
util::iends_with(hostname, hnLeftLabelEnd, ptWildcard + 1,
ptLeftLabelEnd);
}
} // namespace

View File

@ -76,6 +76,28 @@ public:
virtual int response_riovec(struct iovec *iov, int iovcnt) const = 0;
virtual void response_drain(size_t n) = 0;
virtual bool response_empty() const = 0;
// Called when PUSH_PROMISE was started in downstream. The
// associated downstream is given as |downstream|. The promised
// stream ID is given as |promised_stream_id|. If upstream supports
// server push for the corresponding upstream connection, it should
// return Downstream object for pushed stream. Otherwise, returns
// nullptr.
virtual Downstream *
on_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) = 0;
// Called when PUSH_PROMISE frame was completely received in
// downstream. The associated downstream is given as |downstream|.
// This function returns 0 if it succeeds, or -1.
virtual int
on_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream) = 0;
// Returns true if server push is enabled in upstream connection.
virtual bool push_enabled() const = 0;
// Cancels promised downstream. This function is called when
// PUSH_PROMISE for |promised_downstream| is not submitted to
// upstream session.
virtual void cancel_premature_downstream(Downstream *promised_downstream) = 0;
};
} // namespace shrpx

View File

@ -63,32 +63,31 @@ namespace nghttp2 {
namespace util {
const char DEFAULT_STRIP_CHARSET[] = "\r\n\t ";
const char UPPER_XDIGITS[] = "0123456789ABCDEF";
bool inRFC3986UnreservedChars(const char c) {
static const char unreserved[] = {'-', '.', '_', '~'};
return isAlpha(c) || isDigit(c) ||
std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4];
bool in_rfc3986_unreserved_chars(const char c) {
static constexpr const char unreserved[] = {'-', '.', '_', '~'};
return is_alpha(c) || is_digit(c) ||
std::find(std::begin(unreserved), std::end(unreserved), c) !=
std::end(unreserved);
}
bool in_rfc3986_sub_delims(const char c) {
static const char sub_delims[] = {'!', '$', '&', '\'', '(', ')',
'*', '+', ',', ';', '='};
static constexpr const char sub_delims[] = {'!', '$', '&', '\'', '(', ')',
'*', '+', ',', ';', '='};
return std::find(std::begin(sub_delims), std::end(sub_delims), c) !=
std::end(sub_delims);
}
std::string percentEncode(const unsigned char *target, size_t len) {
std::string percent_encode(const unsigned char *target, size_t len) {
std::string dest;
for (size_t i = 0; i < len; ++i) {
unsigned char c = target[i];
if (inRFC3986UnreservedChars(c)) {
if (in_rfc3986_unreserved_chars(c)) {
dest += c;
} else {
dest += "%";
dest += '%';
dest += UPPER_XDIGITS[c >> 4];
dest += UPPER_XDIGITS[(c & 0x0f)];
}
@ -96,20 +95,21 @@ std::string percentEncode(const unsigned char *target, size_t len) {
return dest;
}
std::string percentEncode(const std::string &target) {
return percentEncode(reinterpret_cast<const unsigned char *>(target.c_str()),
target.size());
std::string percent_encode(const std::string &target) {
return percent_encode(reinterpret_cast<const unsigned char *>(target.c_str()),
target.size());
}
std::string percent_encode_path(const std::string &s) {
std::string dest;
for (auto c : s) {
if (inRFC3986UnreservedChars(c) || in_rfc3986_sub_delims(c) || c == '/') {
if (in_rfc3986_unreserved_chars(c) || in_rfc3986_sub_delims(c) ||
c == '/') {
dest += c;
continue;
}
dest += "%";
dest += '%';
dest += UPPER_XDIGITS[(c >> 4) & 0x0f];
dest += UPPER_XDIGITS[(c & 0x0f)];
}
@ -117,18 +117,17 @@ std::string percent_encode_path(const std::string &s) {
}
bool in_token(char c) {
static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+',
'-', '.', '^', '_', '`', '|', '~'};
return isAlpha(c) || isDigit(c) ||
std::find(&extra[0], &extra[sizeof(extra)], c) !=
&extra[sizeof(extra)];
static constexpr const char extra[] = {'!', '#', '$', '%', '&',
'\'', '*', '+', '-', '.',
'^', '_', '`', '|', '~'};
return is_alpha(c) || is_digit(c) ||
std::find(std::begin(extra), std::end(extra), c) != std::end(extra);
}
bool in_attr_char(char c) {
static const char bad[] = {'*', '\'', '%'};
static constexpr const char bad[] = {'*', '\'', '%'};
return util::in_token(c) &&
std::find(std::begin(bad), std::end(bad) - 1, c) == std::end(bad) - 1;
std::find(std::begin(bad), std::end(bad), c) == std::end(bad);
}
std::string percent_encode_token(const std::string &target) {
@ -141,7 +140,7 @@ std::string percent_encode_token(const std::string &target) {
if (c != '%' && in_token(c)) {
dest += c;
} else {
dest += "%";
dest += '%';
dest += UPPER_XDIGITS[c >> 4];
dest += UPPER_XDIGITS[(c & 0x0f)];
}
@ -354,7 +353,7 @@ void streq_advance(const char **ap, const char **bp) {
}
} // namespace
bool istartsWith(const char *a, const char *b) {
bool istarts_with(const char *a, const char *b) {
if (!a || !b) {
return false;
}
@ -510,8 +509,8 @@ void show_candidates(const char *unkopt, option *options) {
for (size_t i = 0; options[i].name != nullptr; ++i) {
auto optnamelen = strlen(options[i].name);
// Use cost 0 for prefix match
if (istartsWith(options[i].name, options[i].name + optnamelen, unkopt,
unkopt + unkoptlen)) {
if (istarts_with(options[i].name, options[i].name + optnamelen, unkopt,
unkopt + unkoptlen)) {
if (optnamelen == static_cast<size_t>(unkoptlen)) {
// Exact match, then we don't show any condidates.
return;
@ -522,8 +521,8 @@ void show_candidates(const char *unkopt, option *options) {
}
// Use cost 0 for suffix match, but match at least 3 characters
if (unkoptlen >= 3 &&
iendsWith(options[i].name, options[i].name + optnamelen, unkopt,
unkopt + unkoptlen)) {
iends_with(options[i].name, options[i].name + optnamelen, unkopt,
unkopt + unkoptlen)) {
cands.emplace_back(0, options[i].name);
continue;
}
@ -712,7 +711,7 @@ std::string ascii_dump(const uint8_t *data, size_t len) {
if (c >= 0x20 && c < 0x7f) {
res += c;
} else {
res += ".";
res += '.';
}
}
@ -755,7 +754,7 @@ bool check_path(const std::string &path) {
path.find('\\') == std::string::npos &&
path.find("/../") == std::string::npos &&
path.find("/./") == std::string::npos &&
!util::endsWith(path, "/..") && !util::endsWith(path, "/.");
!util::ends_with(path, "/..") && !util::ends_with(path, "/.");
}
int64_t to_time64(const timeval &tv) {
@ -1093,6 +1092,20 @@ std::string format_duration(const std::chrono::microseconds &u) {
return dtos(static_cast<double>(t) / d) + unit;
}
std::string format_duration(double t) {
const char *unit = "us";
if (t >= 1.) {
unit = "s";
} else if (t >= 0.001) {
t *= 1000.;
unit = "ms";
} else {
t *= 1000000.;
return utos(static_cast<int64_t>(t)) + unit;
}
return dtos(t) + unit;
}
std::string dtos(double n) {
auto f = utos(static_cast<int64_t>(round(100. * n)) % 100);
return utos(static_cast<int64_t>(n)) + "." + (f.size() == 1 ? "0" : "") + f;
@ -1103,17 +1116,17 @@ std::string make_hostport(const char *host, uint16_t port) {
std::string hostport;
if (ipv6) {
hostport += "[";
hostport += '[';
}
hostport += host;
if (ipv6) {
hostport += "]";
hostport += ']';
}
if (port != 80 && port != 443) {
hostport += ":";
hostport += ':';
hostport += utos(port);
}
@ -1245,8 +1258,13 @@ int read_mime_types(std::map<std::string, std::string> &res,
break;
}
ext_end = std::find_if(ext_start, std::end(line), delim_pred);
#ifdef HAVE_STD_MAP_EMPLACE
res.emplace(std::string(ext_start, ext_end),
std::string(std::begin(line), type_end));
#else // !HAVE_STD_MAP_EMPLACE
res.insert(std::make_pair(std::string(ext_start, ext_end),
std::string(std::begin(line), type_end)));
#endif // !HAVE_STD_MAP_EMPLACE
}
}

View File

@ -64,115 +64,17 @@ constexpr const char NGHTTP2_H1_1[] = "http/1.1";
namespace util {
extern const char DEFAULT_STRIP_CHARSET[];
template <typename InputIterator>
std::pair<InputIterator, InputIterator>
stripIter(InputIterator first, InputIterator last,
const char *chars = DEFAULT_STRIP_CHARSET) {
for (; first != last && strchr(chars, *first) != 0; ++first)
;
if (first == last) {
return std::make_pair(first, last);
}
InputIterator left = last - 1;
for (; left != first && strchr(chars, *left) != 0; --left)
;
return std::make_pair(first, left + 1);
}
template <typename InputIterator, typename OutputIterator>
OutputIterator splitIter(InputIterator first, InputIterator last,
OutputIterator out, char delim, bool doStrip = false,
bool allowEmpty = false) {
for (InputIterator i = first; i != last;) {
InputIterator j = std::find(i, last, delim);
std::pair<InputIterator, InputIterator> p(i, j);
if (doStrip) {
p = stripIter(i, j);
}
if (allowEmpty || p.first != p.second) {
*out++ = p;
}
i = j;
if (j != last) {
++i;
}
}
if (allowEmpty && (first == last || *(last - 1) == delim)) {
*out++ = std::make_pair(last, last);
}
return out;
}
template <typename InputIterator, typename OutputIterator>
OutputIterator split(InputIterator first, InputIterator last,
OutputIterator out, char delim, bool doStrip = false,
bool allowEmpty = false) {
for (InputIterator i = first; i != last;) {
InputIterator j = std::find(i, last, delim);
std::pair<InputIterator, InputIterator> p(i, j);
if (doStrip) {
p = stripIter(i, j);
}
if (allowEmpty || p.first != p.second) {
*out++ = std::string(p.first, p.second);
}
i = j;
if (j != last) {
++i;
}
}
if (allowEmpty && (first == last || *(last - 1) == delim)) {
*out++ = std::string(last, last);
}
return out;
}
template <typename InputIterator, typename DelimiterType>
std::string strjoin(InputIterator first, InputIterator last,
const DelimiterType &delim) {
std::string result;
if (first == last) {
return result;
}
InputIterator beforeLast = last - 1;
for (; first != beforeLast; ++first) {
result += *first;
result += delim;
}
result += *beforeLast;
return result;
}
template <typename InputIterator>
std::string joinPath(InputIterator first, InputIterator last) {
std::vector<std::string> elements;
for (; first != last; ++first) {
if (*first == "..") {
if (!elements.empty()) {
elements.pop_back();
}
} else if (*first == ".") {
// do nothing
} else {
elements.push_back(*first);
}
}
return strjoin(elements.begin(), elements.end(), "/");
}
inline bool isAlpha(const char c) {
inline bool is_alpha(const char c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
inline bool isDigit(const char c) { return '0' <= c && c <= '9'; }
inline bool is_digit(const char c) { return '0' <= c && c <= '9'; }
inline bool isHexDigit(const char c) {
return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
inline bool is_hex_digit(const char c) {
return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
}
bool inRFC3986UnreservedChars(const char c);
bool in_rfc3986_unreserved_chars(const char c);
bool in_rfc3986_sub_delims(const char c);
@ -182,23 +84,23 @@ bool in_token(char c);
bool in_attr_char(char c);
// Returns integer corresponding to hex notation |c|. It is undefined
// if isHexDigit(c) is false.
// if is_hex_digit(c) is false.
uint32_t hex_to_uint(char c);
std::string percentEncode(const unsigned char *target, size_t len);
std::string percent_encode(const unsigned char *target, size_t len);
std::string percentEncode(const std::string &target);
std::string percent_encode(const std::string &target);
// percent-encode path component of URI |s|.
std::string percent_encode_path(const std::string &s);
template <typename InputIt>
std::string percentDecode(InputIt first, InputIt last) {
std::string percent_decode(InputIt first, InputIt last) {
std::string result;
for (; first != last; ++first) {
if (*first == '%') {
if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
isHexDigit(*(first + 2))) {
if (first + 1 != last && first + 2 != last &&
is_hex_digit(*(first + 1)) && is_hex_digit(*(first + 2))) {
result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2));
first += 2;
continue;
@ -267,20 +169,20 @@ inline char lowcase(char c) {
}
template <typename InputIterator1, typename InputIterator2>
bool startsWith(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
bool starts_with(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
if (last1 - first1 < last2 - first2) {
return false;
}
return std::equal(first2, last2, first1);
}
inline bool startsWith(const std::string &a, const std::string &b) {
return startsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
inline bool starts_with(const std::string &a, const std::string &b) {
return starts_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
inline bool startsWith(const char *a, const char *b) {
return startsWith(a, a + strlen(a), b, b + strlen(b));
inline bool starts_with(const char *a, const char *b) {
return starts_with(a, a + strlen(a), b, b + strlen(b));
}
struct CaseCmp {
@ -290,49 +192,49 @@ struct CaseCmp {
};
template <typename InputIterator1, typename InputIterator2>
bool istartsWith(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
bool istarts_with(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
if (last1 - first1 < last2 - first2) {
return false;
}
return std::equal(first2, last2, first1, CaseCmp());
}
inline bool istartsWith(const std::string &a, const std::string &b) {
return istartsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
inline bool istarts_with(const std::string &a, const std::string &b) {
return istarts_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename InputIt>
bool istartsWith(InputIt a, size_t an, const char *b) {
return istartsWith(a, a + an, b, b + strlen(b));
bool istarts_with(InputIt a, size_t an, const char *b) {
return istarts_with(a, a + an, b, b + strlen(b));
}
bool istartsWith(const char *a, const char *b);
bool istarts_with(const char *a, const char *b);
template <typename InputIterator1, typename InputIterator2>
bool endsWith(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
bool ends_with(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
if (last1 - first1 < last2 - first2) {
return false;
}
return std::equal(first2, last2, last1 - (last2 - first2));
}
inline bool endsWith(const std::string &a, const std::string &b) {
return endsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
inline bool ends_with(const std::string &a, const std::string &b) {
return ends_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
template <typename InputIterator1, typename InputIterator2>
bool iendsWith(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
bool iends_with(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) {
if (last1 - first1 < last2 - first2) {
return false;
}
return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp());
}
inline bool iendsWith(const std::string &a, const std::string &b) {
return iendsWith(std::begin(a), std::end(a), std::begin(b), std::end(b));
inline bool iends_with(const std::string &a, const std::string &b) {
return iends_with(std::begin(a), std::end(a), std::begin(b), std::end(b));
}
int strcompare(const char *a, const uint8_t *b, size_t n);
@ -683,6 +585,9 @@ std::string duration_str(double t);
// fractional digits follow.
std::string format_duration(const std::chrono::microseconds &u);
// Just like above, but this takes |t| as seconds.
std::string format_duration(double t);
// Creates "host:port" string using given |host| and |port|. If
// |host| is numeric IPv6 address (e.g., ::1), it is enclosed by "["
// and "]". If |port| is 80 or 443, port part is omitted.

View File

@ -147,15 +147,15 @@ void test_util_percent_encode_path(void) {
void test_util_percent_decode(void) {
{
std::string s = "%66%6F%6f%62%61%72";
CU_ASSERT("foobar" == util::percentDecode(std::begin(s), std::end(s)));
CU_ASSERT("foobar" == util::percent_decode(std::begin(s), std::end(s)));
}
{
std::string s = "%66%6";
CU_ASSERT("f%6" == util::percentDecode(std::begin(s), std::end(s)));
CU_ASSERT("f%6" == util::percent_decode(std::begin(s), std::end(s)));
}
{
std::string s = "%66%";
CU_ASSERT("f%" == util::percentDecode(std::begin(s), std::end(s)));
CU_ASSERT("f%" == util::percent_decode(std::begin(s), std::end(s)));
}
}
@ -341,30 +341,39 @@ void test_util_format_duration(void) {
util::format_duration(std::chrono::microseconds(1000000)));
CU_ASSERT("1.05s" ==
util::format_duration(std::chrono::microseconds(1050000)));
CU_ASSERT("0us" == util::format_duration(0.));
CU_ASSERT("999us" == util::format_duration(0.000999));
CU_ASSERT("1.00ms" == util::format_duration(0.001));
CU_ASSERT("1.09ms" == util::format_duration(0.00109));
CU_ASSERT("1.01ms" == util::format_duration(0.001009));
CU_ASSERT("999.99ms" == util::format_duration(0.99999));
CU_ASSERT("1.00s" == util::format_duration(1.));
CU_ASSERT("1.05s" == util::format_duration(1.05));
}
void test_util_starts_with(void) {
CU_ASSERT(util::startsWith("foo", "foo"));
CU_ASSERT(util::startsWith("fooo", "foo"));
CU_ASSERT(util::startsWith("ofoo", ""));
CU_ASSERT(!util::startsWith("ofoo", "foo"));
CU_ASSERT(util::starts_with("foo", "foo"));
CU_ASSERT(util::starts_with("fooo", "foo"));
CU_ASSERT(util::starts_with("ofoo", ""));
CU_ASSERT(!util::starts_with("ofoo", "foo"));
CU_ASSERT(util::istartsWith("FOO", "fOO"));
CU_ASSERT(util::startsWith("ofoo", ""));
CU_ASSERT(util::istartsWith("fOOo", "Foo"));
CU_ASSERT(!util::istartsWith("ofoo", "foo"));
CU_ASSERT(util::istarts_with("FOO", "fOO"));
CU_ASSERT(util::starts_with("ofoo", ""));
CU_ASSERT(util::istarts_with("fOOo", "Foo"));
CU_ASSERT(!util::istarts_with("ofoo", "foo"));
}
void test_util_ends_with(void) {
CU_ASSERT(util::endsWith("foo", "foo"));
CU_ASSERT(util::endsWith("foo", ""));
CU_ASSERT(util::endsWith("ofoo", "foo"));
CU_ASSERT(!util::endsWith("ofoo", "fo"));
CU_ASSERT(util::ends_with("foo", "foo"));
CU_ASSERT(util::ends_with("foo", ""));
CU_ASSERT(util::ends_with("ofoo", "foo"));
CU_ASSERT(!util::ends_with("ofoo", "fo"));
CU_ASSERT(util::iendsWith("fOo", "Foo"));
CU_ASSERT(util::iendsWith("foo", ""));
CU_ASSERT(util::iendsWith("oFoo", "fOO"));
CU_ASSERT(!util::iendsWith("ofoo", "fo"));
CU_ASSERT(util::iends_with("fOo", "Foo"));
CU_ASSERT(util::iends_with("foo", ""));
CU_ASSERT(util::iends_with("oFoo", "fOO"));
CU_ASSERT(!util::iends_with("ofoo", "fo"));
}
void test_util_parse_http_date(void) {

View File

@ -87,6 +87,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_recv_continuation) ||
!CU_add_test(pSuite, "session_recv_headers_with_priority",
test_nghttp2_session_recv_headers_with_priority) ||
!CU_add_test(pSuite, "session_recv_headers_early_response",
test_nghttp2_session_recv_headers_early_response) ||
!CU_add_test(pSuite, "session_recv_premature_headers",
test_nghttp2_session_recv_premature_headers) ||
!CU_add_test(pSuite, "session_recv_unknown_frame",
@ -161,6 +163,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_submit_response_with_data) ||
!CU_add_test(pSuite, "submit_response_without_data",
test_nghttp2_submit_response_without_data) ||
!CU_add_test(pSuite, "Submit_response_push_response",
test_nghttp2_submit_response_push_response) ||
!CU_add_test(pSuite, "submit_trailer", test_nghttp2_submit_trailer) ||
!CU_add_test(pSuite, "submit_headers_start_stream",
test_nghttp2_submit_headers_start_stream) ||
@ -252,6 +256,8 @@ int main(int argc _U_, char *argv[] _U_) {
test_nghttp2_session_stream_get_state) ||
!CU_add_test(pSuite, "session_stream_get_something",
test_nghttp2_session_stream_get_something) ||
!CU_add_test(pSuite, "session_find_stream",
test_nghttp2_session_find_stream) ||
!CU_add_test(pSuite, "session_keep_closed_stream",
test_nghttp2_session_keep_closed_stream) ||
!CU_add_test(pSuite, "session_keep_idle_stream",
@ -283,6 +289,8 @@ int main(int argc _U_, char *argv[] _U_) {
!CU_add_test(pSuite, "session_detach_item_from_closed_stream",
test_nghttp2_session_detach_item_from_closed_stream) ||
!CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) ||
!CU_add_test(pSuite, "session_change_stream_priority",
test_nghttp2_session_change_stream_priority) ||
!CU_add_test(pSuite, "http_mandatory_headers",
test_nghttp2_http_mandatory_headers) ||
!CU_add_test(pSuite, "http_content_length",

View File

@ -1408,6 +1408,85 @@ void test_nghttp2_session_recv_headers_with_priority(void) {
nghttp2_session_del(session);
}
static int response_on_begin_frame_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data _U_) {
int rv;
if (hd->type != NGHTTP2_HEADERS) {
return 0;
}
rv = nghttp2_submit_response(session, hd->stream_id, resnv, ARRLEN(resnv),
NULL);
CU_ASSERT(0 == rv);
return 0;
}
void test_nghttp2_session_recv_headers_early_response(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_bufs bufs;
nghttp2_buf *buf;
nghttp2_hd_deflater deflater;
nghttp2_mem *mem;
nghttp2_nv *nva;
size_t nvlen;
nghttp2_frame frame;
ssize_t rv;
nghttp2_stream *stream;
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_begin_frame_callback = response_on_begin_frame_callback;
nghttp2_session_server_new(&session, &callbacks, NULL);
nghttp2_hd_deflate_init(&deflater, mem);
nvlen = ARRLEN(reqnv);
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
nghttp2_frame_headers_init(&frame.headers,
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM,
1, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv);
nghttp2_frame_headers_free(&frame.headers, mem);
buf = &bufs.head->buf;
/* Only receive 9 bytes headers, and invoke
on_begin_frame_callback */
rv = nghttp2_session_mem_recv(session, buf->pos, 9);
CU_ASSERT(9 == rv);
rv = nghttp2_session_send(session);
CU_ASSERT(0 == rv);
rv =
nghttp2_session_mem_recv(session, buf->pos + 9, nghttp2_buf_len(buf) - 9);
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) - 9 == rv);
stream = nghttp2_session_get_stream_raw(session, 1);
CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_CLOSED);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
nghttp2_bufs_free(&bufs);
}
void test_nghttp2_session_recv_premature_headers(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
@ -2765,7 +2844,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
CU_ASSERT(6 == item->frame.hd.stream_id);
CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
CU_ASSERT(0 == nghttp2_session_send(session));
/* Attempt to PUSH_PROMISE against non-existent stream */
@ -2939,7 +3018,7 @@ void test_nghttp2_session_on_push_promise_received(void) {
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
CU_ASSERT(NGHTTP2_CANCEL == item->frame.rst_stream.error_code);
nghttp2_frame_push_promise_free(&frame.push_promise, mem);
nghttp2_session_del(session);
@ -3580,6 +3659,15 @@ void test_nghttp2_session_reprioritize_stream(void) {
CU_ASSERT(128 == stream->weight);
CU_ASSERT(dep_stream == stream->dep_prev);
/* Change weight again to test short-path case */
pri_spec.weight = 100;
nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
CU_ASSERT(100 == stream->weight);
CU_ASSERT(dep_stream == stream->dep_prev);
CU_ASSERT(100 == dep_stream->sum_dep_weight);
/* Test circular dependency; stream 1 is first removed and becomes
root. Then stream 3 depends on it. */
nghttp2_priority_spec_init(&pri_spec, 1, 1, 0);
@ -4012,6 +4100,33 @@ void test_nghttp2_submit_response_without_data(void) {
nghttp2_session_del(session);
}
void test_nghttp2_submit_response_push_response(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
my_user_data ud;
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
nghttp2_session_server_new(&session, &callbacks, &ud);
nghttp2_session_open_stream(session, 2, NGHTTP2_FLAG_NONE, &pri_spec_default,
NGHTTP2_STREAM_RESERVED, NULL);
session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
CU_ASSERT(0 ==
nghttp2_submit_response(session, 2, resnv, ARRLEN(resnv), NULL));
ud.frame_not_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_not_send_cb_called);
nghttp2_session_del(session);
}
void test_nghttp2_submit_trailer(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
@ -4572,28 +4687,28 @@ void test_nghttp2_submit_push_promise(void) {
CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
reqnv, ARRLEN(reqnv), &ud));
stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NULL != stream);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
ud.frame_send_cb_called = 0;
ud.sent_frame_type = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_send_cb_called);
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type);
stream = nghttp2_session_get_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
/* submit PUSH_PROMISE while associated stream is not opened */
CU_ASSERT(4 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3,
reqnv, ARRLEN(reqnv), &ud));
ud.frame_not_send_cb_called = 0;
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == ud.frame_not_send_cb_called);
CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type);
stream = nghttp2_session_get_stream(session, 4);
CU_ASSERT(NULL == stream);
CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED ==
nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv,
ARRLEN(reqnv), &ud));
nghttp2_session_del(session);
}
@ -4965,7 +5080,7 @@ void test_nghttp2_session_open_stream(void) {
NGHTTP2_STREAM_RESERVED, NULL);
CU_ASSERT(0 == session->num_incoming_streams);
CU_ASSERT(0 == session->num_outgoing_streams);
CU_ASSERT(NULL == stream->dep_prev);
CU_ASSERT(&session->root == stream->dep_prev);
CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);
@ -7457,7 +7572,8 @@ void test_nghttp2_session_stream_get_state(void) {
stream = nghttp2_session_find_stream(session, 2);
CU_ASSERT(NGHTTP2_STREAM_STATE_CLOSED == nghttp2_stream_get_state(stream));
/* At server, pushed stream object is not retained after closed */
CU_ASSERT(NULL == stream);
/* Push stream 4 associated to stream 5 */
rv = nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 5, reqnv,
@ -7597,6 +7713,34 @@ void test_nghttp2_session_stream_get_something(void) {
nghttp2_session_del(session);
}
void test_nghttp2_session_find_stream(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_stream *stream;
memset(&callbacks, 0, sizeof(callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
open_stream(session, 1);
stream = nghttp2_session_find_stream(session, 1);
CU_ASSERT(NULL != stream);
CU_ASSERT(1 == stream->stream_id);
stream = nghttp2_session_find_stream(session, 0);
CU_ASSERT(&session->root == stream);
CU_ASSERT(0 == stream->stream_id);
stream = nghttp2_session_find_stream(session, 2);
CU_ASSERT(NULL == stream);
nghttp2_session_del(session);
}
void test_nghttp2_session_keep_closed_stream(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
@ -7650,6 +7794,22 @@ void test_nghttp2_session_keep_closed_stream(void) {
CU_ASSERT(NULL == session->closed_stream_tail);
CU_ASSERT(NULL == session->closed_stream_head);
nghttp2_session_close_stream(session, 3, NGHTTP2_NO_ERROR);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(3 == session->closed_stream_head->stream_id);
/* server initiated stream is not counted to max concurrent limit */
open_stream(session, 2);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(3 == session->closed_stream_head->stream_id);
nghttp2_session_close_stream(session, 2, NGHTTP2_NO_ERROR);
CU_ASSERT(1 == session->num_closed_streams);
CU_ASSERT(3 == session->closed_stream_head->stream_id);
nghttp2_session_del(session);
}
@ -7787,13 +7947,12 @@ void test_nghttp2_session_large_dep_tree(void) {
nghttp2_session_server_new(&session, &callbacks, NULL);
stream_id = 1;
for (i = 0; i < 250; ++i) {
for (i = 0; i < 250; ++i, stream_id += 2) {
dep_stream = open_stream_with_dep(session, stream_id, dep_stream);
stream_id += 2;
}
stream_id = 1;
for (i = 0; i < 250; ++i) {
for (i = 0; i < 250; ++i, stream_id += 2) {
stream = nghttp2_session_get_stream(session, stream_id);
CU_ASSERT(nghttp2_stream_dep_find_ancestor(stream, &session->root));
CU_ASSERT(nghttp2_stream_in_dep_tree(stream));
@ -8477,6 +8636,116 @@ void test_nghttp2_session_flooding(void) {
nghttp2_bufs_free(&bufs);
}
void test_nghttp2_session_change_stream_priority(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_stream *stream1, *stream2, *stream3;
nghttp2_priority_spec pri_spec;
int rv;
memset(&callbacks, 0, sizeof(callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
stream1 = open_stream(session, 1);
stream3 = open_stream_with_dep_weight(session, 3, 199, stream1);
stream2 = open_stream_with_dep_weight(session, 2, 101, stream3);
nghttp2_priority_spec_init(&pri_spec, 1, 256, 0);
rv = nghttp2_session_change_stream_priority(session, 2, &pri_spec);
CU_ASSERT(0 == rv);
CU_ASSERT(stream1 == stream2->dep_prev);
CU_ASSERT(256 == stream2->weight);
/* Cannot change stream which does not exist */
rv = nghttp2_session_change_stream_priority(session, 5, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* It is an error to depend on itself */
rv = nghttp2_session_change_stream_priority(session, 1, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* It is an error to change priority of root stream (0) */
rv = nghttp2_session_change_stream_priority(session, 0, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
nghttp2_session_del(session);
}
void test_nghttp2_session_create_idle_stream(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_stream *stream2, *stream4, *stream8, *stream10;
nghttp2_priority_spec pri_spec;
int rv;
memset(&callbacks, 0, sizeof(callbacks));
nghttp2_session_server_new(&session, &callbacks, NULL);
stream2 = open_stream(session, 2);
nghttp2_priority_spec_init(&pri_spec, 2, 111, 1);
rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
CU_ASSERT(0 == rv);
stream4 = nghttp2_session_get_stream_raw(session, 4);
CU_ASSERT(4 == stream4->stream_id);
CU_ASSERT(111 == stream4->weight);
CU_ASSERT(stream2 == stream4->dep_prev);
CU_ASSERT(stream4 == stream2->dep_next);
/* If pri_spec->stream_id does not exist, and it is idle stream, it
is created too */
nghttp2_priority_spec_init(&pri_spec, 8, 109, 0);
rv = nghttp2_session_create_idle_stream(session, 8, &pri_spec);
CU_ASSERT(0 == rv);
stream8 = nghttp2_session_get_stream_raw(session, 8);
stream10 = nghttp2_session_get_stream_raw(session, 10);
CU_ASSERT(8 == stream8->stream_id);
CU_ASSERT(109 == stream8->weight);
CU_ASSERT(10 == stream10->stream_id);
CU_ASSERT(16 == stream10->weight);
CU_ASSERT(stream10 == stream8->dep_prev);
CU_ASSERT(&session->root == stream10->dep_prev);
/* It is an error to attempt to create already existing idle
stream */
rv = nghttp2_session_create_idle_stream(session, 4, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* It is an error to depend on itself */
pri_spec.stream_id = 6;
rv = nghttp2_session_create_idle_stream(session, 6, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* It is an error to create root stream (0) as idle stream */
rv = nghttp2_session_create_idle_stream(session, 0, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
/* It is an error to create non-idle stream */
session->next_stream_id = 20;
pri_spec.stream_id = 2;
rv = nghttp2_session_create_idle_stream(session, 18, &pri_spec);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
nghttp2_session_del(session);
}
static void check_nghttp2_http_recv_headers_fail(
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
int stream_state, const nghttp2_nv *nva, size_t nvlen) {

View File

@ -33,6 +33,7 @@ void test_nghttp2_session_recv_data(void);
void test_nghttp2_session_recv_data_no_auto_flow_control(void);
void test_nghttp2_session_recv_continuation(void);
void test_nghttp2_session_recv_headers_with_priority(void);
void test_nghttp2_session_recv_headers_early_response(void);
void test_nghttp2_session_recv_premature_headers(void);
void test_nghttp2_session_recv_unknown_frame(void);
void test_nghttp2_session_recv_unexpected_continuation(void);
@ -71,6 +72,7 @@ void test_nghttp2_submit_request_with_data(void);
void test_nghttp2_submit_request_without_data(void);
void test_nghttp2_submit_response_with_data(void);
void test_nghttp2_submit_response_without_data(void);
void test_nghttp2_submit_response_push_response(void);
void test_nghttp2_submit_trailer(void);
void test_nghttp2_submit_headers_start_stream(void);
void test_nghttp2_submit_headers_reply(void);
@ -118,6 +120,7 @@ void test_nghttp2_session_stream_attach_item(void);
void test_nghttp2_session_stream_attach_item_subtree(void);
void test_nghttp2_session_stream_get_state(void);
void test_nghttp2_session_stream_get_something(void);
void test_nghttp2_session_find_stream(void);
void test_nghttp2_session_keep_closed_stream(void);
void test_nghttp2_session_keep_idle_stream(void);
void test_nghttp2_session_detach_idle_stream(void);
@ -134,6 +137,8 @@ void test_nghttp2_session_on_begin_headers_temporal_failure(void);
void test_nghttp2_session_defer_then_close(void);
void test_nghttp2_session_detach_item_from_closed_stream(void);
void test_nghttp2_session_flooding(void);
void test_nghttp2_session_change_stream_priority(void);
void test_nghttp2_session_create_idle_stream(void);
void test_nghttp2_http_mandatory_headers(void);
void test_nghttp2_http_content_length(void);
void test_nghttp2_http_content_length_mismatch(void);

2
third-party/mruby vendored

@ -1 +1 @@
Subproject commit 83922d0bbb7de894380fa6b33e7d75061114aa59
Subproject commit 22464fe5a0a10f2b077eaba109ce1e912e4a77de