Compare commits

...

33 Commits

Author SHA1 Message Date
Tatsuhiro Tsujikawa
7146954de2 Update man pages 2014-06-02 22:36:22 +09:00
Tatsuhiro Tsujikawa
75b9be2d5a Bump up version number to 0.4.1, LT revision to 3:1:0 2014-06-02 22:34:18 +09:00
Tatsuhiro Tsujikawa
99aaaccf03 Update doc 2014-06-02 22:33:23 +09:00
Tatsuhiro Tsujikawa
7e217511bf nghttpx: Code cleanup
Mainly make nested code block to rather flat style.
2014-06-01 23:44:32 +09:00
Tatsuhiro Tsujikawa
8c67bbe3a8 src: Avoid copy in evbuffer_pullup()
Previously, we use evbuffer_pullup(buf, -1) to linearize the memory
region and it may cause buffer copy.  To avoid this, we use the return
value of evbuffer_get_contiguous_space() as 2nd parameter.  According
to the libevent manual, by doing so evbuffer_pullup() will not copy or
modify any data in evbuffer.
2014-06-01 21:01:01 +09:00
Tatsuhiro Tsujikawa
cc250386df nghttpx: Lower read watermark 2014-06-01 02:32:26 +09:00
Tatsuhiro Tsujikawa
3b7c733246 nghttpx: Fix typo 2014-06-01 02:32:15 +09:00
Tatsuhiro Tsujikawa
7e5567341f nghttpx: Set timeout to underlying bufferevent
Setting write timeout to filter bufferevent does not work as intended.
It timeouts even when there is no data to write.
2014-06-01 02:25:23 +09:00
Tatsuhiro Tsujikawa
4f7223e89f Add note to manual how to submit frames to new stream ID 2014-05-31 22:01:18 +09:00
Tatsuhiro Tsujikawa
88b69bb669 Reduce huffman decoding table
Previously we have uint16_t as state member variable in
nghttp2_huff_decode structure to express -1 as failure.  This is
because we have 256 valid states.  However, we can express failed
state using flags member variable and make state uint8_t.  This commit
does this and as a result the size of decoding table is reduced.
2014-05-31 00:19:30 +09:00
Tatsuhiro Tsujikawa
7a797b2c11 nghttpx: Reduce socket I/O buffer size 2014-05-29 22:24:15 +09:00
Tatsuhiro Tsujikawa
832f2fc00f Call on_data_chunk_recv_callback only when stream is active 2014-05-29 22:18:52 +09:00
Tatsuhiro Tsujikawa
d113055899 nghttp2_hd: Use single buffer for an name/value pair
Previously we use 2 separate buffer for each name and value.  The
problem is we would waste buffer space for name because it is usually
small.  Also tuning buffer size for each buffer separately is not
elegant and current HTTP server practice is that one buffer for 1
name/value pair.  This commit unifies 2 buffers into 1.
2014-05-28 23:33:37 +09:00
Tatsuhiro Tsujikawa
53e75ff0d0 Merge branch 'travis' of https://github.com/alagoutte/nghttp2 into alagoutte-travis 2014-05-28 23:26:02 +09:00
Alexis La Goutte
3921af434a Add .travis.yml to get build by Travis-ci (Github)
Use gcc-4.8 (don't build with gcc-4.6)
Update libstdc++-4.8 to fix Clang build

Don't install cython (Never build...)
2014-05-28 15:52:16 +02:00
Tatsuhiro Tsujikawa
5e6a2fa256 Assert ctx->state >= 0 in huffman decoding 2014-05-28 00:30:11 +09:00
Tatsuhiro Tsujikawa
86ab9f33de nghttpx: Fix regression bug that frame with stream_id = 0 not handled 2014-05-28 00:26:27 +09:00
Tatsuhiro Tsujikawa
d844b0acd0 h2load: Use std::async to dispatch parallel jobs 2014-05-26 23:29:28 +09:00
Tatsuhiro Tsujikawa
d733c87567 Make state in nghttp2_hd_huff_decode_context int16_t to make compiler happy 2014-05-26 21:50:54 +09:00
Tatsuhiro Tsujikawa
589d3e71a3 Merge branch 'misc' of https://github.com/alagoutte/nghttp2 into alagoutte-misc 2014-05-26 21:48:30 +09:00
Alexis La Goutte
db354b228a Remove unused include (stdint.h) 2014-05-26 08:58:15 +02:00
Tatsuhiro Tsujikawa
1fa5852f8f nghttpx: Treat '*' in <HOST> parameter of --frontend as wildcard explicitly
It seems that specifyig '*' to node parameter in getaddrinfo() is
treated as specifying NULL, but it is not documented.  So rather than
relying on this feature, we explicitly treat '*' as "wildcard" address
and specify NULL to node parameter in getaddrinfo().

Now '*,3000' is a default value of --frontend option.  Specyfing '*'
binds all addresses including both IPv4 and IPv6.
2014-05-25 16:15:48 +09:00
Tatsuhiro Tsujikawa
ebf0e4d787 nghttpd, nghttpx: Check END_STREAM flag in HEADERS other than request 2014-05-24 15:02:46 +09:00
Tatsuhiro Tsujikawa
9677788317 Don't count closed streams in nghttp2_session_want_{read,write} 2014-05-23 22:23:38 +09:00
Tatsuhiro Tsujikawa
78a55935ac Define constant for the length of priority related fields 2014-05-22 21:41:43 +09:00
Tatsuhiro Tsujikawa
2aa84019c7 Define constants for ALTSVC frame parsing 2014-05-22 21:36:12 +09:00
Tatsuhiro Tsujikawa
6d942dc0a6 Update doc 2014-05-21 23:01:21 +09:00
Tatsuhiro Tsujikawa
d408ee1783 Update doc
We are currently disabled ALPN in nghttp2.org
2014-05-21 23:00:01 +09:00
Tatsuhiro Tsujikawa
672ad82849 nghttpx: Clarify that --npn-list is used in both ALPN and NPN 2014-05-21 21:28:58 +09:00
Tatsuhiro Tsujikawa
896717f5d4 nghttpx: Make --npn-list option work in ALPN
Previously --npn-list option is ignored in ALPN protocol selection
callback.  This change fixes this issue.
2014-05-21 21:16:44 +09:00
Tatsuhiro Tsujikawa
eba2825286 Handle the case where jemalloc is available without linking extra library 2014-05-18 19:38:33 +09:00
Tatsuhiro Tsujikawa
a4a06947a5 Update doc 2014-05-17 22:20:48 +09:00
Tatsuhiro Tsujikawa
e54e86375b Bump up version number to 0.5.0-DEV 2014-05-17 00:14:07 +09:00
34 changed files with 1147 additions and 735 deletions

36
.travis.yml Normal file
View File

@@ -0,0 +1,36 @@
language: cpp
compiler:
- clang
- gcc
python:
- "3.4"
before_install:
- $CC --version
- sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
#Install and use gcc-4.8 (don't build with gcc-4.6)
#libstdc++-4.8 is needed by Clang to build too
- sudo apt-get -qq install g++-4.8 libstdc++-4.8-dev
- >
sudo apt-get install --no-install-recommends -qq
autoconf
automake
autotools-dev
libtool
pkg-config
zlib1g-dev
libcunit1-dev
libssl-dev
libxml2-dev
libevent-dev
libjansson-dev
libjemalloc-dev
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi
- $CC --version
before_script:
- autoreconf -i
- automake
- autoconf
- ./configure --enable-werror
script:
- make

View File

@@ -28,9 +28,10 @@ Public Test Server
The following endpoints are available to try out nghttp2 The following endpoints are available to try out nghttp2
implementation. implementation.
* https://nghttp2.org/ (TLS + NPN / ALPN) * https://nghttp2.org/ (TLS + NPN)
ALPN and NPN offer ``h2-12``, ``spdy/3.1`` and ``http/1.1``. NPN offer ``h2-12``, ``spdy/3.1`` and ``http/1.1``.
ALPN is currently disabled.
* http://nghttp2.org/ (Upgrade / Direct) * http://nghttp2.org/ (Upgrade / Direct)
@@ -122,6 +123,13 @@ used::
$ ./configure $ ./configure
$ make $ make
.. note::
Mac OS X users may need ``--disable-threads`` configure option to
disable multi threading in nghttpd, nghttpx and h2load to prevent
them from crashing. Patch is welcome to make multi threading work
on Mac OS X platform.
Building documentation Building documentation
---------------------- ----------------------

View File

@@ -21,13 +21,13 @@ dnl LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([nghttp2], [0.4.0], [t-tujikawa@users.sourceforge.net]) AC_INIT([nghttp2], [0.4.1], [t-tujikawa@users.sourceforge.net])
LT_PREREQ([2.2.6]) LT_PREREQ([2.2.6])
LT_INIT() LT_INIT()
dnl See versioning rule: dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 3) AC_SUBST(LT_CURRENT, 3)
AC_SUBST(LT_REVISION, 0) AC_SUBST(LT_REVISION, 1)
AC_SUBST(LT_AGE, 0) AC_SUBST(LT_AGE, 0)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
@@ -275,8 +275,9 @@ if test "x${request_jemalloc}" != "xno"; then
LIBS_OLD=$LIBS LIBS_OLD=$LIBS
AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes]) AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes])
LIBS=$LIBS_OLD LIBS=$LIBS_OLD
if test "x${have_jemalloc}" = "xyes"; then if test "x${have_jemalloc}" = "xyes" &&
JEMALLOC_LIBS="-ljemalloc" test "x${ac_cv_search_malloc_stats_print}" != "xnone required"; then
JEMALLOC_LIBS=${ac_cv_search_malloc_stats_print}
AC_SUBST([JEMALLOC_LIBS]) AC_SUBST([JEMALLOC_LIBS])
fi fi
fi fi

View File

@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1.
.TH H2LOAD "1" "May 2014" "h2load nghttp2/0.4.0" "User Commands" .TH H2LOAD "1" "June 2014" "h2load nghttp2/0.4.1" "User Commands"
.SH NAME .SH NAME
h2load \- HTTP/2 benchmarking tool h2load \- HTTP/2 benchmarking tool
.SH SYNOPSIS .SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1.
.TH NGHTTP "1" "May 2014" "nghttp nghttp2/0.4.0" "User Commands" .TH NGHTTP "1" "June 2014" "nghttp nghttp2/0.4.1" "User Commands"
.SH NAME .SH NAME
nghttp \- HTTP/2 experimental client nghttp \- HTTP/2 experimental client
.SH SYNOPSIS .SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1.
.TH NGHTTPD "1" "May 2014" "nghttpd nghttp2/0.4.0" "User Commands" .TH NGHTTPD "1" "June 2014" "nghttpd nghttp2/0.4.1" "User Commands"
.SH NAME .SH NAME
nghttpd \- HTTP/2 experimental server nghttpd \- HTTP/2 experimental server
.SH SYNOPSIS .SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.45.1.
.TH NGHTTPX "1" "May 2014" "nghttpx nghttp2/0.4.0" "User Commands" .TH NGHTTPX "1" "June 2014" "nghttpx nghttp2/0.4.1" "User Commands"
.SH NAME .SH NAME
nghttpx \- HTTP/2 experimental proxy nghttpx \- HTTP/2 experimental proxy
.SH SYNOPSIS .SH SYNOPSIS
@@ -27,8 +27,10 @@ Set backend host and port.
Default: '127.0.0.1,80' Default: '127.0.0.1,80'
.TP .TP
\fB\-f\fR, \fB\-\-frontend=\fR<HOST,PORT> \fB\-f\fR, \fB\-\-frontend=\fR<HOST,PORT>
Set frontend host and port. Set frontend host and port. If <HOST> is '*', it
Default: '0.0.0.0,3000' assumes all addresses including both IPv4 and
IPv6.
Default: '*,3000'
.TP .TP
\fB\-\-backlog=\fR<NUM> \fB\-\-backlog=\fR<NUM>
Set listen backlog size. If \fB\-1\fR is given, Set listen backlog size. If \fB\-1\fR is given,
@@ -188,12 +190,12 @@ format. Without this option, DHE cipher suites
are not available. are not available.
.TP .TP
\fB\-\-npn\-list=\fR<LIST> \fB\-\-npn\-list=\fR<LIST>
Comma delimited list of NPN/ALPN protocol sorted Comma delimited list of ALPN protocol identifier
in the order of preference. That means most sorted in the order of preference. That means
desirable protocol comes first. The parameter most desirable protocol comes first. This is
must be delimited by a single comma only and any used in both ALPN and NPN. The parameter must be
white spaces are treated as a part of protocol delimited by a single comma only and any white
string. spaces are treated as a part of protocol string.
Default: h2\-12,spdy/3.1,spdy/3,spdy/2,http/1.1 Default: h2\-12,spdy/3.1,spdy/3,spdy/2,http/1.1
.TP .TP
\fB\-\-verify\-client\fR \fB\-\-verify\-client\fR

View File

@@ -2200,6 +2200,15 @@ int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was * No stream ID is available because maximum stream ID was
* reached. * reached.
*
* .. warning::
*
* This function returns assigned stream ID if it succeeds. But
* that stream is not opened yet. The application must not submit
* frame to that stream ID before
* :member:`nghttp2_session_callbacks.before_frame_send_callback` is
* called for this frame.
*
*/ */
int32_t nghttp2_submit_request(nghttp2_session *session, int32_t nghttp2_submit_request(nghttp2_session *session,
const nghttp2_priority_spec *pri_spec, const nghttp2_priority_spec *pri_spec,
@@ -2309,6 +2318,15 @@ int nghttp2_submit_response(nghttp2_session *session,
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was * No stream ID is available because maximum stream ID was
* reached. * reached.
*
* .. warning::
*
* This function returns assigned stream ID if it succeeds and
* |stream_id| is -1. But that stream is not opened yet. The
* application must not submit frame to that stream ID before
* :member:`nghttp2_session_callbacks.before_frame_send_callback` is
* called for this frame.
*
*/ */
int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
int32_t stream_id, int32_t stream_id,
@@ -2469,6 +2487,15 @@ int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was * No stream ID is available because maximum stream ID was
* reached. * reached.
*
* .. 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
* :member:`nghttp2_session_callbacks.before_frame_send_callback` is
* called for this frame.
*
*/ */
int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags, int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
int32_t stream_id, int32_t stream_id,

View File

@@ -91,8 +91,8 @@ void nghttp2_frame_headers_free(nghttp2_headers *frame)
void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id, void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id,
const nghttp2_priority_spec *pri_spec) const nghttp2_priority_spec *pri_spec)
{ {
frame_set_hd(&frame->hd, 5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, frame_set_hd(&frame->hd, NGHTTP2_PRIORITY_SPECLEN, NGHTTP2_PRIORITY,
stream_id); NGHTTP2_FLAG_NONE, stream_id);
frame->pri_spec = *pri_spec; frame->pri_spec = *pri_spec;
} }
@@ -196,8 +196,7 @@ void nghttp2_frame_altsvc_init(nghttp2_altsvc *frame, int32_t stream_id,
{ {
size_t payloadlen; size_t payloadlen;
/* 8 for Max-Age, Port, Reserved and PID_LEN. 1 for HOST_LEN. */ payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len + origin_len;
payloadlen = 8 + protocol_id_len + 1 + host_len + origin_len;
frame_set_hd(&frame->hd, payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE, frame_set_hd(&frame->hd, payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE,
stream_id); stream_id);
@@ -263,7 +262,7 @@ void nghttp2_frame_private_data_free(nghttp2_private_data *frame)
size_t nghttp2_frame_priority_len(uint8_t flags) size_t nghttp2_frame_priority_len(uint8_t flags)
{ {
if(flags & NGHTTP2_FLAG_PRIORITY) { if(flags & NGHTTP2_FLAG_PRIORITY) {
return 5; return NGHTTP2_PRIORITY_SPECLEN;
} }
return 0; return 0;
@@ -437,7 +436,7 @@ int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame)
buf = &bufs->head->buf; buf = &bufs->head->buf;
assert(nghttp2_buf_avail(buf) >= 5); assert(nghttp2_buf_avail(buf) >= NGHTTP2_PRIORITY_SPECLEN);
buf->pos -= NGHTTP2_FRAME_HDLEN; buf->pos -= NGHTTP2_FRAME_HDLEN;
@@ -445,7 +444,7 @@ int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame)
nghttp2_frame_pack_priority_spec(buf->last, &frame->pri_spec); nghttp2_frame_pack_priority_spec(buf->last, &frame->pri_spec);
buf->last += 5; buf->last += NGHTTP2_PRIORITY_SPECLEN;
return 0; return 0;
} }

View File

@@ -60,6 +60,18 @@
/* The maximum header table size in SETTINGS_HEADER_TABLE_SIZE */ /* The maximum header table size in SETTINGS_HEADER_TABLE_SIZE */
#define NGHTTP2_MAX_HEADER_TABLE_SIZE ((1u << 31) - 1) #define NGHTTP2_MAX_HEADER_TABLE_SIZE ((1u << 31) - 1)
/* Length of priority related fields in HEADERS/PRIORITY frames */
#define NGHTTP2_PRIORITY_SPECLEN 5
/* Length of fixed part in ALTSVC frame, that is the sum of fields of
Max-Age, Port, Reserved and PID_LEN. */
#define NGHTTP2_ALTSVC_FIXED_PARTLEN 8
/* Minimum length of ALTSVC frame. NGHTTP2_ALTSVC_FIXED_PARTLEN +
HOST_LEN. */
#define NGHTTP2_ALTSVC_MINLEN 9
/* Category of frames. */ /* Category of frames. */
typedef enum { typedef enum {
/* non-DATA frame */ /* non-DATA frame */

View File

@@ -352,37 +352,29 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
inflater->ent_keep = NULL; inflater->ent_keep = NULL;
inflater->name_keep = NULL; inflater->nv_keep = NULL;
inflater->value_keep = NULL;
inflater->end_headers_index = 0; inflater->end_headers_index = 0;
inflater->opcode = NGHTTP2_HD_OPCODE_NONE; inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
inflater->state = NGHTTP2_HD_STATE_OPCODE; inflater->state = NGHTTP2_HD_STATE_OPCODE;
rv = nghttp2_bufs_init(&inflater->namebufs, NGHTTP2_HD_MAX_NAME, 1); rv = nghttp2_bufs_init(&inflater->nvbufs, NGHTTP2_HD_MAX_NV / 2, 2);
if(rv != 0) { if(rv != 0) {
goto namebuf_fail; goto nvbufs_fail;
}
rv = nghttp2_bufs_init(&inflater->valuebufs, NGHTTP2_HD_MAX_VALUE / 2, 2);
if(rv != 0) {
goto valuebuf_fail;
} }
inflater->huffman_encoded = 0; inflater->huffman_encoded = 0;
inflater->index = 0; inflater->index = 0;
inflater->left = 0; inflater->left = 0;
inflater->newnamelen = 0;
inflater->index_required = 0; inflater->index_required = 0;
inflater->no_index = 0; inflater->no_index = 0;
inflater->ent_name = NULL; inflater->ent_name = NULL;
return 0; return 0;
valuebuf_fail: nvbufs_fail:
nghttp2_bufs_free(&inflater->namebufs);
namebuf_fail:
hd_context_free(&inflater->ctx); hd_context_free(&inflater->ctx);
fail: fail:
return rv; return rv;
@@ -397,10 +389,9 @@ static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater)
} }
inflater->ent_keep = NULL; inflater->ent_keep = NULL;
} }
free(inflater->name_keep);
free(inflater->value_keep); free(inflater->nv_keep);
inflater->name_keep = NULL; inflater->nv_keep = NULL;
inflater->value_keep = NULL;
} }
void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater)
@@ -411,8 +402,7 @@ void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater)
void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater)
{ {
hd_inflate_keep_free(inflater); hd_inflate_keep_free(inflater);
nghttp2_bufs_free(&inflater->namebufs); nghttp2_bufs_free(&inflater->nvbufs);
nghttp2_bufs_free(&inflater->valuebufs);
hd_context_free(&inflater->ctx); hd_context_free(&inflater->ctx);
} }
@@ -1488,27 +1478,27 @@ static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv, int value_only) nghttp2_nv *nv, int value_only)
{ {
ssize_t rv; ssize_t rv;
size_t buflen;
uint8_t *buf;
rv = nghttp2_bufs_remove(&inflater->nvbufs, &buf);
if(rv < 0) {
return NGHTTP2_ERR_NOMEM;
}
buflen = rv;
if(value_only) { if(value_only) {
nv->name = NULL; nv->name = NULL;
nv->namelen = 0;
} else { } else {
rv = nghttp2_bufs_remove(&inflater->namebufs, &nv->name); nv->name = buf;
nv->namelen = inflater->newnamelen;
if(rv < 0) {
return NGHTTP2_ERR_NOMEM;
} }
nv->namelen = rv; nv->value = buf + nv->namelen;
} nv->valuelen = buflen - nv->namelen;
rv = nghttp2_bufs_remove(&inflater->valuebufs, &nv->value);
if(rv < 0) {
free(nv->name);
return NGHTTP2_ERR_NOMEM;
}
nv->valuelen = rv;
return 0; return 0;
} }
@@ -1545,9 +1535,10 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
uint8_t ent_flags; uint8_t ent_flags;
ent_flags = /* nv->value points to the middle of the buffer pointed by
NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC | nv->name. So we just need to keep track of nv->name for memory
NGHTTP2_HD_FLAG_NAME_GIFT | NGHTTP2_HD_FLAG_VALUE_GIFT; management. */
ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_NAME_GIFT;
new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags); new_ent = add_hd_table_incremental(&inflater->ctx, NULL, &nv, ent_flags);
@@ -1559,15 +1550,13 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
} }
free(nv.name); free(nv.name);
free(nv.value);
return NGHTTP2_ERR_NOMEM; return NGHTTP2_ERR_NOMEM;
} }
emit_literal_header(nv_out, &nv); emit_literal_header(nv_out, &nv);
inflater->name_keep = nv.name; inflater->nv_keep = nv.name;
inflater->value_keep = nv.value;
return 0; return 0;
} }
@@ -1642,7 +1631,7 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
emit_literal_header(nv_out, &nv); emit_literal_header(nv_out, &nv);
inflater->value_keep = nv.value; inflater->nv_keep = nv.value;
return 0; return 0;
} }
@@ -1793,7 +1782,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN: case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN:
rfin = 0; rfin = 0;
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
NGHTTP2_HD_MAX_NAME); NGHTTP2_HD_MAX_NV);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
} }
@@ -1815,7 +1804,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
} }
break; break;
case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF: case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF:
rv = hd_inflate_read_huff(inflater, &inflater->namebufs, in, last); rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
} }
@@ -1831,11 +1820,13 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto almost_ok; goto almost_ok;
} }
inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
break; break;
case NGHTTP2_HD_STATE_NEWNAME_READ_NAME: case NGHTTP2_HD_STATE_NEWNAME_READ_NAME:
rv = hd_inflate_read(inflater, &inflater->namebufs, in, last); rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
} }
@@ -1850,6 +1841,8 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto almost_ok; goto almost_ok;
} }
inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN; inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
break; break;
@@ -1863,7 +1856,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
case NGHTTP2_HD_STATE_READ_VALUELEN: case NGHTTP2_HD_STATE_READ_VALUELEN:
rfin = 0; rfin = 0;
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
NGHTTP2_HD_MAX_VALUE); NGHTTP2_HD_MAX_NV);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
} }
@@ -1898,7 +1891,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
} }
break; break;
case NGHTTP2_HD_STATE_READ_VALUEHUFF: case NGHTTP2_HD_STATE_READ_VALUEHUFF:
rv = hd_inflate_read_huff(inflater, &inflater->valuebufs, in, last); rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
if(rv < 0) { if(rv < 0) {
goto fail; goto fail;
} }
@@ -1929,7 +1922,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
return in - first; return in - first;
case NGHTTP2_HD_STATE_READ_VALUE: case NGHTTP2_HD_STATE_READ_VALUE:
rv = hd_inflate_read(inflater, &inflater->valuebufs, in, last); rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
if(rv < 0) { if(rv < 0) {
DEBUGF(fprintf(stderr, "inflatehd: value read failure %zd: %s\n", DEBUGF(fprintf(stderr, "inflatehd: value read failure %zd: %s\n",
rv, nghttp2_strerror(rv))); rv, nghttp2_strerror(rv)));

View File

@@ -37,10 +37,10 @@
#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE #define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
#define NGHTTP2_HD_ENTRY_OVERHEAD 32 #define NGHTTP2_HD_ENTRY_OVERHEAD 32
/* The maximum value length of name/value pair. This is not specified /* The maximum length of one name/value pair. This is the sum of the
by the spec. We just chose the arbitrary size */ length of name and value. This is not specified by the spec. We
#define NGHTTP2_HD_MAX_NAME 256 just chose the arbitrary size */
#define NGHTTP2_HD_MAX_VALUE 8192 #define NGHTTP2_HD_MAX_NV 8192
/* Default size of maximum table buffer size for encoder. Even if /* Default size of maximum table buffer size for encoder. Even if
remote decoder notifies larger buffer size for its decoding, remote decoder notifies larger buffer size for its decoding,
@@ -146,20 +146,17 @@ struct nghttp2_hd_deflater {
struct nghttp2_hd_inflater { struct nghttp2_hd_inflater {
nghttp2_hd_context ctx; nghttp2_hd_context ctx;
/* header name buffer */ /* header buffer */
nghttp2_bufs namebufs; nghttp2_bufs nvbufs;
/* header value buffer */
nghttp2_bufs valuebufs;
/* Stores current state of huffman decoding */ /* Stores current state of huffman decoding */
nghttp2_hd_huff_decode_context huff_decode_ctx; nghttp2_hd_huff_decode_context huff_decode_ctx;
/* Pointer to the nghttp2_hd_entry which is used current header /* Pointer to the nghttp2_hd_entry which is used current header
emission. This is required because in some cases the emission. This is required because in some cases the
ent_keep->ref == 0 and we have to keep track of it. */ ent_keep->ref == 0 and we have to keep track of it. */
nghttp2_hd_entry *ent_keep; nghttp2_hd_entry *ent_keep;
/* Pointers to the name/value pair which are used current header /* Pointer to the name/value pair buffer which is used in the
emission. They are usually used to keep track of malloc'ed memory current header emission. */
for huffman decoding. */ uint8_t *nv_keep;
uint8_t *name_keep, *value_keep;
/* Pointers to the name/value pair which is referred as indexed /* Pointers to the name/value pair which is referred as indexed
name. This entry must be in header table. */ name. This entry must be in header table. */
nghttp2_hd_entry *ent_name; nghttp2_hd_entry *ent_name;
@@ -170,6 +167,9 @@ struct nghttp2_hd_inflater {
/* The index of header table to toggle off the entry from reference /* The index of header table to toggle off the entry from reference
set at the end of decompression. */ set at the end of decompression. */
size_t end_headers_index; size_t end_headers_index;
/* The length of new name encoded in literal. For huffman encoded
string, this is the length after it is decoded. */
size_t newnamelen;
/* The maximum header table size the inflater supports. This is the /* The maximum header table size the inflater supports. This is the
same value transmitted in SETTINGS_HEADER_TABLE_SIZE */ same value transmitted in SETTINGS_HEADER_TABLE_SIZE */
size_t settings_hd_table_bufsize_max; size_t settings_hd_table_bufsize_max;

View File

@@ -177,8 +177,10 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
for(i = 0; i < srclen; ++i) { for(i = 0; i < srclen; ++i) {
uint8_t in = src[i] >> 4; uint8_t in = src[i] >> 4;
for(j = 0; j < 2; ++j) { for(j = 0; j < 2; ++j) {
const nghttp2_huff_decode *t = &huff_decode_table[ctx->state][in]; const nghttp2_huff_decode *t;
if(t->state == -1) {
t = &huff_decode_table[ctx->state][in];
if(t->flags & NGHTTP2_HUFF_FAIL) {
return NGHTTP2_ERR_HEADER_COMP; return NGHTTP2_ERR_HEADER_COMP;
} }
if(t->flags & NGHTTP2_HUFF_SYM) { if(t->flags & NGHTTP2_HUFF_SYM) {

View File

@@ -36,13 +36,17 @@ typedef enum {
sequence. */ sequence. */
NGHTTP2_HUFF_ACCEPTED = 1, NGHTTP2_HUFF_ACCEPTED = 1,
/* This state emits symbol */ /* This state emits symbol */
NGHTTP2_HUFF_SYM = (1 << 1) NGHTTP2_HUFF_SYM = (1 << 1),
/* If state machine reaches this state, decoding fails. */
NGHTTP2_HUFF_FAIL = (1 << 2),
} nghttp2_huff_decode_flag; } nghttp2_huff_decode_flag;
typedef struct { typedef struct {
/* huffman decoding state, which is actually the node ID of internal /* huffman decoding state, which is actually the node ID of internal
huffman tree */ huffman tree. We have 257 leaf nodes, but they are identical to
int16_t state; root node other than emitting a symbol, so we have 256 internal
nodes [1..255], inclusive. */
uint8_t state;
/* bitwise OR of zero or more of the nghttp2_huff_decode_flag */ /* bitwise OR of zero or more of the nghttp2_huff_decode_flag */
uint8_t flags; uint8_t flags;
/* symbol if NGHTTP2_HUFF_SYM flag set */ /* symbol if NGHTTP2_HUFF_SYM flag set */

View File

@@ -3624,7 +3624,7 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
{0, 0x03, 253}, {0, 0x03, 253},
{0, 0x03, 254}, {0, 0x03, 254},
{0, 0x03, 255}, {0, 0x03, 255},
{-1, 0x00, 0}, {0, 0x04, 0},
{189, 0x00, 0}, {189, 0x00, 0},
{191, 0x00, 0}, {191, 0x00, 0},
{192, 0x00, 0}, {192, 0x00, 0},
@@ -3772,8 +3772,8 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
{14, 0x03, 254}, {14, 0x03, 254},
{1, 0x02, 255}, {1, 0x02, 255},
{14, 0x03, 255}, {14, 0x03, 255},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{0, 0x03, 0}, {0, 0x03, 0},
{0, 0x03, 1}, {0, 0x03, 1},
{0, 0x03, 2}, {0, 0x03, 2},
@@ -3840,10 +3840,10 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
}, },
/* 187 */ /* 187 */
{ {
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{1, 0x02, 0}, {1, 0x02, 0},
{14, 0x03, 0}, {14, 0x03, 0},
{1, 0x02, 1}, {1, 0x02, 1},
@@ -3859,14 +3859,14 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
}, },
/* 188 */ /* 188 */
{ {
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{-1, 0x00, 0}, {0, 0x04, 0},
{2, 0x02, 0}, {2, 0x02, 0},
{6, 0x02, 0}, {6, 0x02, 0},
{15, 0x02, 0}, {15, 0x02, 0},

View File

@@ -29,8 +29,6 @@
# include <config.h> # include <config.h>
#endif /* HAVE_CONFIG_H */ #endif /* HAVE_CONFIG_H */
#include <stdint.h>
/* Macros, types and constants for internal use */ /* Macros, types and constants for internal use */
#ifdef DEBUGBUILD #ifdef DEBUGBUILD

View File

@@ -4342,7 +4342,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
if(iframe->payloadleft != 5) { if(iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) {
busy = 1; busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
@@ -4352,7 +4352,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->state = NGHTTP2_IB_READ_NBYTE; iframe->state = NGHTTP2_IB_READ_NBYTE;
inbound_frame_set_mark(iframe, 5); inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN);
break; break;
case NGHTTP2_RST_STREAM: case NGHTTP2_RST_STREAM:
@@ -4490,7 +4490,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break; break;
} }
if(iframe->payloadleft < 9) { if(iframe->payloadleft < NGHTTP2_ALTSVC_MINLEN) {
busy = 1; busy = 1;
iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
@@ -4499,7 +4499,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
} }
iframe->state = NGHTTP2_IB_READ_NBYTE; iframe->state = NGHTTP2_IB_READ_NBYTE;
inbound_frame_set_mark(iframe, 8); inbound_frame_set_mark(iframe, NGHTTP2_ALTSVC_FIXED_PARTLEN);
break; break;
case NGHTTP2_BLOCKED: case NGHTTP2_BLOCKED:
@@ -4711,7 +4711,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
case NGHTTP2_ALTSVC: { case NGHTTP2_ALTSVC: {
size_t varlen; size_t varlen;
varlen = iframe->frame.hd.length - 8; varlen = iframe->frame.hd.length - NGHTTP2_ALTSVC_FIXED_PARTLEN;
if(varlen > 0) { if(varlen > 0) {
iframe->raw_lbuf = malloc(varlen); iframe->raw_lbuf = malloc(varlen);
@@ -5195,7 +5195,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen)); DEBUGF(fprintf(stderr, "recv: data_readlen=%zu\n", data_readlen));
if(data_readlen > 0 && if(stream && data_readlen > 0 &&
session->callbacks.on_data_chunk_recv_callback) { session->callbacks.on_data_chunk_recv_callback) {
rv = session->callbacks.on_data_chunk_recv_callback rv = session->callbacks.on_data_chunk_recv_callback
(session, (session,
@@ -5293,28 +5293,47 @@ int nghttp2_session_recv(nghttp2_session *session)
} }
} }
/*
* Returns the number of active streams, which includes streams in
* reserved state.
*/
static size_t session_get_num_active_streams(nghttp2_session *session)
{
return nghttp2_map_size(&session->streams) - session->num_closed_streams;
}
int nghttp2_session_want_read(nghttp2_session *session) int nghttp2_session_want_read(nghttp2_session *session)
{ {
size_t num_active_streams;
/* If these flags are set, we don't want to read. The application /* If these flags are set, we don't want to read. The application
should drop the connection. */ should drop the connection. */
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) && if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) { (session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0; return 0;
} }
num_active_streams = session_get_num_active_streams(session);
/* Unless GOAWAY is sent or received, we always want to read /* Unless GOAWAY is sent or received, we always want to read
incoming frames. After GOAWAY is sent or received, we are only incoming frames. After GOAWAY is sent or received, we are only
interested in active streams. */ interested in active streams. */
return !session->goaway_flags || nghttp2_map_size(&session->streams) > 0; return !session->goaway_flags || num_active_streams > 0;
} }
int nghttp2_session_want_write(nghttp2_session *session) int nghttp2_session_want_write(nghttp2_session *session)
{ {
size_t num_active_streams;
/* If these flags are set, we don't want to write any data. The /* If these flags are set, we don't want to write any data. The
application should drop the connection. */ application should drop the connection. */
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) && if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) { (session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0; return 0;
} }
num_active_streams = session_get_num_active_streams(session);
/* /*
* Unless GOAWAY is sent or received, we want to write frames if * Unless GOAWAY is sent or received, we want to write frames if
* there is pending ones. If pending frame is request/push response * there is pending ones. If pending frame is request/push response
@@ -5325,7 +5344,7 @@ int nghttp2_session_want_write(nghttp2_session *session)
return (session->aob.item != NULL || !nghttp2_pq_empty(&session->ob_pq) || return (session->aob.item != NULL || !nghttp2_pq_empty(&session->ob_pq) ||
(!nghttp2_pq_empty(&session->ob_ss_pq) && (!nghttp2_pq_empty(&session->ob_ss_pq) &&
!session_is_outgoing_concurrent_streams_max(session))) && !session_is_outgoing_concurrent_streams_max(session))) &&
(!session->goaway_flags || nghttp2_map_size(&session->streams) > 0); (!session->goaway_flags || num_active_streams > 0);
} }
int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,

View File

@@ -84,6 +84,7 @@ def dfs(node, root):
NGHTTP2_HUFF_ACCEPTED = 1 NGHTTP2_HUFF_ACCEPTED = 1
NGHTTP2_HUFF_SYM = 1 << 1 NGHTTP2_HUFF_SYM = 1 << 1
NGHTTP2_HUFF_FAIL = 1 << 2
def dfs_print(node): def dfs_print(node):
if node.term is not None: if node.term is not None:
@@ -100,7 +101,8 @@ def dfs_print(node):
out = syms[0] out = syms[0]
flags |= NGHTTP2_HUFF_SYM flags |= NGHTTP2_HUFF_SYM
if nd is None: if nd is None:
id = -1 id = 0
flags |= NGHTTP2_HUFF_FAIL
else: else:
id = nd.id id = nd.id
if id is None: if id is None:
@@ -159,13 +161,14 @@ print ''
print '''\ print '''\
enum {{ enum {{
NGHTTP2_HUFF_ACCEPTED = {}, NGHTTP2_HUFF_ACCEPTED = {},
NGHTTP2_HUFF_SYM = {} NGHTTP2_HUFF_SYM = {},
NGHTTP2_HUFF_FAIL = {},
}} nghttp2_huff_decode_flag; }} nghttp2_huff_decode_flag;
'''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM) '''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM, NGHTTP2_HUFF_FAIL)
print '''\ print '''\
typedef struct { typedef struct {
int16_t state; uint8_t state;
uint8_t flags; uint8_t flags;
uint8_t sym; uint8_t sym;
} nghttp2_huff_decode; } nghttp2_huff_decode;

View File

@@ -1258,14 +1258,14 @@ int hd_on_frame_recv_callback
break; break;
} }
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS: {
switch(frame->headers.cat) {
case NGHTTP2_HCAT_REQUEST: {
auto stream = hd->get_stream(frame->hd.stream_id); auto stream = hd->get_stream(frame->hd.stream_id);
if(!stream) { if(!stream) {
return 0; return 0;
} }
if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
http2::normalize_headers(stream->headers); http2::normalize_headers(stream->headers);
if(!http2::check_http2_headers(stream->headers)) { if(!http2::check_http2_headers(stream->headers)) {
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
@@ -1285,6 +1285,8 @@ int hd_on_frame_recv_callback
hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
return 0; return 0;
} }
}
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
remove_stream_read_timeout(stream); remove_stream_read_timeout(stream);
@@ -1292,12 +1294,9 @@ int hd_on_frame_recv_callback
} else { } else {
add_stream_read_timeout(stream); add_stream_read_timeout(stream);
} }
break; break;
} }
default:
break;
}
break;
case NGHTTP2_SETTINGS: case NGHTTP2_SETTINGS:
if(frame->hd.flags & NGHTTP2_FLAG_ACK) { if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
hd->remove_settings_timer(); hd->remove_settings_timer();

View File

@@ -35,6 +35,7 @@
#include <iostream> #include <iostream>
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <future>
#ifdef HAVE_SPDYLAY #ifdef HAVE_SPDYLAY
#include <spdylay/spdylay.h> #include <spdylay/spdylay.h>
@@ -549,6 +550,15 @@ std::string get_reqline(const char *uri, const http_parser_url& u)
} }
} // namespace } // namespace
namespace {
std::unique_ptr<Worker> run(std::unique_ptr<Worker> worker)
{
worker->run();
return worker;
}
} // namespace
namespace { namespace {
int client_select_next_proto_cb(SSL* ssl, int client_select_next_proto_cb(SSL* ssl,
unsigned char **out, unsigned char *outlen, unsigned char **out, unsigned char *outlen,
@@ -888,7 +898,7 @@ int main(int argc, char **argv)
std::cout << "starting benchmark..." << std::endl; std::cout << "starting benchmark..." << std::endl;
std::vector<std::thread> threads; std::vector<std::future<std::unique_ptr<Worker>>> futures;
auto start = std::chrono::steady_clock::now(); auto start = std::chrono::steady_clock::now();
std::vector<std::unique_ptr<Worker>> workers; std::vector<std::unique_ptr<Worker>> workers;
@@ -899,9 +909,9 @@ int main(int argc, char **argv)
<< nclients << " concurrent clients, " << nclients << " concurrent clients, "
<< nreqs << " total requests" << nreqs << " total requests"
<< std::endl; << std::endl;
workers.push_back(util::make_unique<Worker>(i, ssl_ctx, nreqs, nclients, auto worker = util::make_unique<Worker>(i, ssl_ctx, nreqs, nclients,
&config)); &config);
threads.emplace_back(&Worker::run, workers.back().get()); futures.push_back(std::async(std::launch::async, run, std::move(worker)));
} }
auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0); auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0);
auto nclients_last = nclients_per_thread + (nclients_rem-- > 0); auto nclients_last = nclients_per_thread + (nclients_rem-- > 0);
@@ -913,19 +923,23 @@ int main(int argc, char **argv)
&config); &config);
worker.run(); worker.run();
for(size_t i = 0; i < config.nthreads - 1; ++i) { for(auto& fut : futures) {
threads[i].join(); auto subworker = fut.get();
worker.stats.req_todo += workers[i]->stats.req_todo; auto& stats = subworker->stats;
worker.stats.req_started += workers[i]->stats.req_started;
worker.stats.req_done += workers[i]->stats.req_done; worker.stats.req_todo += stats.req_todo;
worker.stats.req_success += workers[i]->stats.req_success; worker.stats.req_started += stats.req_started;
worker.stats.req_failed += workers[i]->stats.req_failed; worker.stats.req_done += stats.req_done;
worker.stats.req_error += workers[i]->stats.req_error; worker.stats.req_success += stats.req_success;
worker.stats.bytes_total += workers[i]->stats.bytes_total; worker.stats.req_failed += stats.req_failed;
worker.stats.bytes_head += workers[i]->stats.bytes_head; worker.stats.req_error += stats.req_error;
worker.stats.bytes_body += workers[i]->stats.bytes_body; worker.stats.bytes_total += stats.bytes_total;
for(size_t j = 0; j < 6; ++j) { worker.stats.bytes_head += stats.bytes_head;
worker.stats.status[j] += workers[i]->stats.status[j]; worker.stats.bytes_body += stats.bytes_body;
for(size_t i = 0; i < sizeof(stats.status) / sizeof(stats.status[0]);
++i) {
worker.stats.status[i] += stats.status[i];
} }
} }

View File

@@ -153,16 +153,33 @@ void Http2Session::submit_request()
ssize_t Http2Session::on_read() ssize_t Http2Session::on_read()
{ {
int rv; int rv;
size_t nread = 0;
auto input = bufferevent_get_input(client_->bev); auto input = bufferevent_get_input(client_->bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1); for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return nread;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen); rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) { if(rv < 0) {
return -1; return -1;
} }
evbuffer_drain(input, rv);
return rv; nread += rv;
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
} }
int Http2Session::on_write() int Http2Session::on_write()

View File

@@ -24,6 +24,8 @@
*/ */
#include "h2load_spdy_session.h" #include "h2load_spdy_session.h"
#include <cassert>
#include "h2load.h" #include "h2load.h"
namespace h2load { namespace h2load {
@@ -163,16 +165,32 @@ void SpdySession::submit_request()
ssize_t SpdySession::on_read() ssize_t SpdySession::on_read()
{ {
int rv; int rv;
size_t nread = 0;
auto input = bufferevent_get_input(client_->bev); auto input = bufferevent_get_input(client_->bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1); for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return nread;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = spdylay_session_mem_recv(session_, mem, inputlen); rv = spdylay_session_mem_recv(session_, mem, inputlen);
if(rv < 0) { if(rv < 0) {
return -1; return -1;
} }
evbuffer_drain(input, rv);
return rv; nread += rv;
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
} }
int SpdySession::on_write() int SpdySession::on_write()

View File

@@ -651,55 +651,77 @@ struct HttpClient {
{ {
int rv; int rv;
auto input = bufferevent_get_input(bev); auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) { if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0; return 0;
} }
auto mem = evbuffer_pullup(input, -1);
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(htp.get(), &htp_hooks, auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char*>(mem), reinterpret_cast<const char*>(mem),
inputlen); inputlen);
if(config.verbose) { if(config.verbose) {
std::cout.write(reinterpret_cast<const char*>(mem), nread); std::cout.write(reinterpret_cast<const char*>(mem), nread);
} }
evbuffer_drain(input, nread);
auto htperr = HTTP_PARSER_ERRNO(htp.get()); if(evbuffer_drain(input, nread) != 0) {
if(htperr == HPE_OK) {
if(upgrade_response_complete) {
if(config.verbose) {
std::cout << std::endl;
}
if(upgrade_response_status_code == 101) {
if(config.verbose) {
print_timer();
std::cout << " HTTP Upgrade success" << std::endl;
}
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
rv = on_connect();
if(rv != 0) {
return rv;
}
// Read remaining data in the buffer because it is not
// notified callback anymore.
rv = on_read();
if(rv != 0) {
return rv;
}
} else {
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1; return -1;
} }
}
} else { auto htperr = HTTP_PARSER_ERRNO(htp.get());
if(htperr != HPE_OK) {
std::cerr << "Failed to parse HTTP Upgrade response header: " std::cerr << "Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") " << "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << std::endl; << http_errno_description(htperr) << std::endl;
return -1; return -1;
} }
if(upgrade_response_complete) {
if(config.verbose) {
std::cout << std::endl;
}
if(upgrade_response_status_code == 101) {
if(config.verbose) {
print_timer();
std::cout << " HTTP Upgrade success" << std::endl;
}
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
rv = on_connect();
if(rv != 0) {
return rv;
}
// Read remaining data in the buffer because it is not
// notified callback anymore.
rv = on_read();
if(rv != 0) {
return rv;
}
return 0; return 0;
} }
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1;
}
}
}
int on_connect() int on_connect()
{ {
int rv; int rv;
@@ -776,18 +798,30 @@ struct HttpClient {
{ {
int rv; int rv;
auto input = bufferevent_get_input(bev); auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1); for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return on_write();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session, mem, inputlen); rv = nghttp2_session_mem_recv(session, mem, inputlen);
if(rv < 0) { if(rv < 0) {
std::cerr << "nghttp2_session_mem_recv() returned error: " std::cerr << "nghttp2_session_mem_recv() returned error: "
<< nghttp2_strerror(rv) << std::endl; << nghttp2_strerror(rv) << std::endl;
return -1; return -1;
} }
evbuffer_drain(input, rv);
return on_write(); if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
} }
int on_write() int on_write()

View File

@@ -131,7 +131,6 @@ void evlistener_errorcb(evconnlistener *listener, void *ptr)
namespace { namespace {
evconnlistener* create_evlistener(ListenHandler *handler, int family) evconnlistener* create_evlistener(ListenHandler *handler, int family)
{ {
// TODO Listen both IPv4 and IPv6
addrinfo hints; addrinfo hints;
int fd = -1; int fd = -1;
int r; int r;
@@ -145,8 +144,10 @@ evconnlistener* create_evlistener(ListenHandler *handler, int family)
hints.ai_flags |= AI_ADDRCONFIG; hints.ai_flags |= AI_ADDRCONFIG;
#endif // AI_ADDRCONFIG #endif // AI_ADDRCONFIG
auto node = strcmp("*", get_config()->host) == 0 ? NULL : get_config()->host;
addrinfo *res, *rp; addrinfo *res, *rp;
r = getaddrinfo(get_config()->host, service.c_str(), &hints, &res); r = getaddrinfo(node, service.c_str(), &hints, &res);
if(r != 0) { if(r != 0) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
LOG(INFO) << "Unable to get IPv" << (family == AF_INET ? "4" : "6") LOG(INFO) << "Unable to get IPv" << (family == AF_INET ? "4" : "6")
@@ -346,7 +347,7 @@ void fill_default_config()
mod_config()->daemon = false; mod_config()->daemon = false;
mod_config()->server_name = "nghttpx nghttp2/" NGHTTP2_VERSION; mod_config()->server_name = "nghttpx nghttp2/" NGHTTP2_VERSION;
set_config_str(&mod_config()->host, "0.0.0.0"); set_config_str(&mod_config()->host, "*");
mod_config()->port = 3000; mod_config()->port = 3000;
mod_config()->private_key_file = 0; mod_config()->private_key_file = 0;
mod_config()->private_key_passwd = 0; mod_config()->private_key_passwd = 0;
@@ -499,7 +500,9 @@ Connections:
<< get_config()->downstream_host << "," << get_config()->downstream_host << ","
<< get_config()->downstream_port << R"(' << get_config()->downstream_port << R"('
-f, --frontend=<HOST,PORT> -f, --frontend=<HOST,PORT>
Set frontend host and port. Set frontend host and port. If <HOST> is '*', it
assumes all addresses including both IPv4 and
IPv6.
Default: ')" Default: ')"
<< get_config()->host << "," << get_config()->port << R"(' << get_config()->host << "," << get_config()->port << R"('
--backlog=<NUM> Set listen backlog size. If -1 is given, --backlog=<NUM> Set listen backlog size. If -1 is given,
@@ -646,12 +649,12 @@ SSL/TLS:
Path to file that contains DH parameters in PEM Path to file that contains DH parameters in PEM
format. Without this option, DHE cipher suites format. Without this option, DHE cipher suites
are not available. are not available.
--npn-list=<LIST> Comma delimited list of NPN/ALPN protocol sorted --npn-list=<LIST> Comma delimited list of ALPN protocol identifier
in the order of preference. That means most sorted in the order of preference. That means
desirable protocol comes first. The parameter most desirable protocol comes first. This is
must be delimited by a single comma only and any used in both ALPN and NPN. The parameter must be
white spaces are treated as a part of protocol delimited by a single comma only and any white
string. spaces are treated as a part of protocol string.
Default: )" << DEFAULT_NPN_LIST << R"( Default: )" << DEFAULT_NPN_LIST << R"(
--verify-client Require and verify client certificate. --verify-client Require and verify client certificate.
--verify-client-cacert=<PATH> --verify-client-cacert=<PATH>

View File

@@ -36,6 +36,6 @@
#define DIE() \ #define DIE() \
assert(0); assert(0);
#define SHRPX_READ_WARTER_MARK (64*1024) #define SHRPX_READ_WATERMARK (16 * 1024)
#endif // SHRPX_H #endif // SHRPX_H

View File

@@ -59,7 +59,7 @@ namespace {
void upstream_writecb(bufferevent *bev, void *arg) void upstream_writecb(bufferevent *bev, void *arg)
{ {
auto handler = static_cast<ClientHandler*>(arg); auto handler = static_cast<ClientHandler*>(arg);
// We actually depend on write low-warter mark == 0. // We actually depend on write low-water mark == 0.
if(handler->get_outbuf_length() > 0) { if(handler->get_outbuf_length() > 0) {
// Possibly because of deferred callback, we may get this callback // Possibly because of deferred callback, we may get this callback
// when the output buffer is not empty. // when the output buffer is not empty.
@@ -279,7 +279,7 @@ ClientHandler::ClientHandler(bufferevent *bev,
} }
bufferevent_enable(bev_, EV_READ | EV_WRITE); bufferevent_enable(bev_, EV_READ | EV_WRITE);
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WARTER_MARK); bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
set_upstream_timeouts(&get_config()->upstream_read_timeout, set_upstream_timeouts(&get_config()->upstream_read_timeout,
&get_config()->upstream_write_timeout); &get_config()->upstream_write_timeout);
if(ssl_) { if(ssl_) {
@@ -361,7 +361,13 @@ void ClientHandler::set_bev_cb
void ClientHandler::set_upstream_timeouts(const timeval *read_timeout, void ClientHandler::set_upstream_timeouts(const timeval *read_timeout,
const timeval *write_timeout) const timeval *write_timeout)
{ {
bufferevent_set_timeouts(bev_, read_timeout, write_timeout); auto bev = bufferevent_get_underlying(bev_);
if(!bev) {
bev = bev_;
}
bufferevent_set_timeouts(bev, read_timeout, write_timeout);
} }
int ClientHandler::validate_next_proto() int ClientHandler::validate_next_proto()

View File

@@ -272,19 +272,25 @@ void eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
int fd = bufferevent_getfd(bev); auto fd = bufferevent_getfd(bev);
int val = 1; int val = 1;
if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<char*>(&val), sizeof(val)) == -1) { reinterpret_cast<char*>(&val), sizeof(val)) == -1) {
SSLOG(WARNING, http2session) SSLOG(WARNING, http2session)
<< "Setting option TCP_NODELAY failed: errno=" << errno; << "Setting option TCP_NODELAY failed: errno=" << errno;
} }
} else if(events & BEV_EVENT_EOF) { return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "EOF"; SSLOG(INFO, http2session) << "EOF";
} }
http2session->disconnect(); http2session->disconnect();
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { return;
}
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) { if(events & BEV_EVENT_ERROR) {
SSLOG(INFO, http2session) << "Network error"; SSLOG(INFO, http2session) << "Network error";
@@ -293,6 +299,7 @@ void eventcb(bufferevent *bev, short events, void *ptr)
} }
} }
http2session->disconnect(); http2session->disconnect();
return;
} }
} }
} // namespace } // namespace
@@ -350,12 +357,18 @@ void proxy_eventcb(bufferevent *bev, short events, void *ptr)
SSLOG(ERROR, http2session) << "bufferevent_write() failed"; SSLOG(ERROR, http2session) << "bufferevent_write() failed";
http2session->disconnect(); http2session->disconnect();
} }
} else if(events & BEV_EVENT_EOF) { return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
SSLOG(INFO, http2session) << "Proxy EOF"; SSLOG(INFO, http2session) << "Proxy EOF";
} }
http2session->disconnect(); http2session->disconnect();
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { return;
}
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) { if(events & BEV_EVENT_ERROR) {
SSLOG(INFO, http2session) << "Network error"; SSLOG(INFO, http2session) << "Network error";
@@ -364,6 +377,7 @@ void proxy_eventcb(bufferevent *bev, short events, void *ptr)
} }
} }
http2session->disconnect(); http2session->disconnect();
return;
} }
} }
} // namespace } // namespace
@@ -409,7 +423,11 @@ int Http2Session::initiate_connection()
proxy_htp_->data = this; proxy_htp_->data = this;
state_ = PROXY_CONNECTING; state_ = PROXY_CONNECTING;
} else if(state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
return 0;
}
if(state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Connecting to downstream server"; SSLOG(INFO, this) << "Connecting to downstream server";
} }
@@ -481,7 +499,7 @@ int Http2Session::initiate_connection()
return SHRPX_ERR_NETWORK; return SHRPX_ERR_NETWORK;
} }
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WARTER_MARK); bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
bufferevent_enable(bev_, EV_READ); bufferevent_enable(bev_, EV_READ);
bufferevent_setcb(bev_, readcb, writecb, eventcb, this); bufferevent_setcb(bev_, readcb, writecb, eventcb, this);
// Set timeout for HTTP2 session // Set timeout for HTTP2 session
@@ -492,12 +510,13 @@ int Http2Session::initiate_connection()
if(state_ != CONNECTED) { if(state_ != CONNECTED) {
state_ = CONNECTING; state_ = CONNECTING;
} }
} else {
return 0;
}
// Unreachable // Unreachable
DIE(); DIE();
} }
return 0;
}
void Http2Session::unwrap_free_bev() void Http2Session::unwrap_free_bev()
{ {
@@ -517,10 +536,13 @@ int htp_hdrs_completecb(http_parser *htp)
SSLOG(INFO, http2session) << "Tunneling success"; SSLOG(INFO, http2session) << "Tunneling success";
} }
http2session->set_state(Http2Session::PROXY_CONNECTED); http2session->set_state(Http2Session::PROXY_CONNECTED);
} else {
return 0;
}
SSLOG(WARNING, http2session) << "Tunneling failed"; SSLOG(WARNING, http2session) << "Tunneling failed";
http2session->set_state(Http2Session::PROXY_FAILED); http2session->set_state(Http2Session::PROXY_FAILED);
}
return 0; return 0;
} }
} // namespace } // namespace
@@ -541,23 +563,34 @@ http_parser_settings htp_hooks = {
int Http2Session::on_read_proxy() int Http2Session::on_read_proxy()
{ {
auto input = bufferevent_get_input(bev_); auto input = bufferevent_get_input(bev_);
auto mem = evbuffer_pullup(input, -1);
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks, size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks,
reinterpret_cast<const char*>(mem), reinterpret_cast<const char*>(mem),
evbuffer_get_length(input)); inputlen);
if(evbuffer_drain(input, nread) != 0) { if(evbuffer_drain(input, nread) != 0) {
SSLOG(FATAL, this) << "evbuffer_drain() failed"; SSLOG(FATAL, this) << "evbuffer_drain() failed";
return -1; return -1;
} }
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get()); auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
if(htperr == HPE_OK) {
return 0; if(htperr != HPE_OK) {
} else {
return -1; return -1;
} }
} }
}
void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn)
{ {
@@ -1250,17 +1283,31 @@ int Http2Session::on_read()
{ {
ssize_t rv = 0; ssize_t rv = 0;
auto input = bufferevent_get_input(bev_); auto input = bufferevent_get_input(bev_);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1); for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return send();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen); rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) { if(rv < 0) {
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: " SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv); << nghttp2_strerror(rv);
return -1; return -1;
} }
evbuffer_drain(input, rv);
return send(); if(evbuffer_drain(input, rv) != 0) {
SSLOG(FATAL, this) << "evbuffer_drain() faild";
return -1;
}
}
} }
int Http2Session::on_write() int Http2Session::on_write()

View File

@@ -46,8 +46,8 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
namespace { namespace {
const size_t OUTBUF_MAX_THRES = 64*1024; const size_t OUTBUF_MAX_THRES = 16*1024;
const size_t INBUF_MAX_THRES = 64*1024; const size_t INBUF_MAX_THRES = 16*1024;
} // namespace } // namespace
namespace { namespace {
@@ -61,25 +61,40 @@ int on_stream_close_callback
<< " is being closed"; << " is being closed";
} }
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(!downstream) {
return 0;
}
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else {
return 0;
}
downstream->set_request_state(Downstream::STREAM_CLOSED); downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read // At this point, downstream response was read
if(!downstream->get_upgraded() && if(!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) { !downstream->get_response_connection_close()) {
// Keep-alive // Keep-alive
auto dconn = downstream->get_downstream_connection(); auto dconn = downstream->get_downstream_connection();
if(dconn) { if(dconn) {
dconn->detach_downstream(downstream); dconn->detach_downstream(downstream);
} }
} }
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else {
return 0;
}
// At this point, downstream read may be paused. // At this point, downstream read may be paused.
// If shrpx_downstream::push_request_headers() failed, the // If shrpx_downstream::push_request_headers() failed, the
@@ -89,9 +104,7 @@ int on_stream_close_callback
// How to test this case? Request sufficient large download // How to test this case? Request sufficient large download
// and make client send RST_STREAM after it gets first DATA // and make client send RST_STREAM after it gets first DATA
// frame chunk. // frame chunk.
}
}
}
return 0; return 0;
} }
} // namespace } // namespace
@@ -250,14 +263,11 @@ int on_begin_headers_callback(nghttp2_session *session,
namespace { namespace {
int on_request_headers(Http2Upstream *upstream, int on_request_headers(Http2Upstream *upstream,
Downstream *downstream,
nghttp2_session *session, nghttp2_session *session,
const nghttp2_frame *frame) const nghttp2_frame *frame)
{ {
int rv; int rv;
auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) {
return 0;
}
downstream->normalize_request_headers(); downstream->normalize_request_headers();
auto& nva = downstream->get_request_headers(); auto& nva = downstream->get_request_headers();
@@ -358,25 +368,38 @@ int on_frame_recv_callback
verbose_on_frame_recv_callback(session, frame, user_data); verbose_on_frame_recv_callback(session, frame, user_data);
} }
auto upstream = static_cast<Http2Upstream*>(user_data); auto upstream = static_cast<Http2Upstream*>(user_data);
switch(frame->hd.type) { switch(frame->hd.type) {
case NGHTTP2_DATA: { case NGHTTP2_DATA: {
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
auto downstream = upstream->find_downstream(frame->hd.stream_id); auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) { if(!downstream) {
break; return 0;
} }
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
downstream->end_upload_data(); downstream->end_upload_data();
downstream->set_request_state(Downstream::MSG_COMPLETE); downstream->set_request_state(Downstream::MSG_COMPLETE);
} }
break; break;
} }
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS: {
return on_request_headers(upstream, session, frame);
case NGHTTP2_PRIORITY: {
auto downstream = upstream->find_downstream(frame->hd.stream_id); auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) { if(!downstream) {
return 0;
}
if(frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
return on_request_headers(upstream, downstream, session, frame);
}
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
downstream->end_upload_data();
downstream->set_request_state(Downstream::MSG_COMPLETE);
}
break; break;
} }
case NGHTTP2_PRIORITY: {
// TODO comment out for now // TODO comment out for now
// rv = downstream->change_priority(frame->priority.pri); // rv = downstream->change_priority(frame->priority.pri);
// if(rv != 0) { // if(rv != 0) {
@@ -413,12 +436,16 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
{ {
auto upstream = static_cast<Http2Upstream*>(user_data); auto upstream = static_cast<Http2Upstream*>(user_data);
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(!downstream) {
return 0;
}
if(downstream->push_upload_data_chunk(data, len) != 0) { if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR); upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
return 0; return 0;
} }
}
return 0; return 0;
} }
} // namespace } // namespace
@@ -584,8 +611,17 @@ int Http2Upstream::on_read()
ssize_t rv = 0; ssize_t rv = 0;
auto bev = handler_->get_bev(); auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev); auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1); for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return send();
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen); rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) { if(rv < 0) {
@@ -593,8 +629,12 @@ int Http2Upstream::on_read()
<< nghttp2_strerror(rv); << nghttp2_strerror(rv);
return -1; return -1;
} }
evbuffer_drain(input, rv);
return send(); if(evbuffer_drain(input, rv) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
} }
int Http2Upstream::on_write() int Http2Upstream::on_write()
@@ -668,6 +708,7 @@ void downstream_readcb(bufferevent *bev, void *ptr)
auto dconn = static_cast<DownstreamConnection*>(ptr); auto dconn = static_cast<DownstreamConnection*>(ptr);
auto downstream = dconn->get_downstream(); auto downstream = dconn->get_downstream();
auto upstream = static_cast<Http2Upstream*>(downstream->get_upstream()); auto upstream = static_cast<Http2Upstream*>(downstream->get_upstream());
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If upstream HTTP2 stream was closed, we just close downstream, // If upstream HTTP2 stream was closed, we just close downstream,
// because there is no consumer now. Downstream connection is also // because there is no consumer now. Downstream connection is also
@@ -684,11 +725,11 @@ void downstream_readcb(bufferevent *bev, void *ptr)
// on_stream_close_callback. // on_stream_close_callback.
upstream->rst_stream(downstream, infer_upstream_rst_stream_error_code upstream->rst_stream(downstream, infer_upstream_rst_stream_error_code
(downstream->get_response_rst_stream_error_code())); (downstream->get_response_rst_stream_error_code()));
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
} else { } else {
int rv = downstream->on_read(); auto rv = downstream->on_read();
if(rv != 0) { if(rv != 0) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure"; DCLOG(INFO, dconn) << "HTTP parser failure";
@@ -705,9 +746,9 @@ void downstream_readcb(bufferevent *bev, void *ptr)
downstream->set_response_state(Downstream::MSG_COMPLETE); downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser // Clearly, we have to close downstream connection on http parser
// failure. // failure.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
} }
} }
if(upstream->send() != 0) { if(upstream->send() != 0) {
@@ -742,14 +783,18 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
DCLOG(INFO, dconn) << "Connection established. stream_id=" DCLOG(INFO, dconn) << "Connection established. stream_id="
<< downstream->get_stream_id(); << downstream->get_stream_id();
} }
int fd = bufferevent_getfd(bev); auto fd = bufferevent_getfd(bev);
int val = 1; int val = 1;
if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<char*>(&val), sizeof(val)) == -1) { reinterpret_cast<char*>(&val), sizeof(val)) == -1) {
DCLOG(WARNING, dconn) << "Setting option TCP_NODELAY failed: errno=" DCLOG(WARNING, dconn) << "Setting option TCP_NODELAY failed: errno="
<< errno; << errno;
} }
} else if(events & BEV_EVENT_EOF) {
return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
} }
@@ -758,12 +803,15 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
// the first place. We can delete downstream. // the first place. We can delete downstream.
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else {
return;
}
// Delete downstream connection. If we don't delete it here, it // Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback. // will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback. // downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF // Server may indicate the end of the request by EOF
@@ -791,8 +839,10 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
// At this point, downstream may be deleted. // At this point, downstream may be deleted.
return;
} }
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) { if(events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: " DCLOG(INFO, dconn) << "Downstream network error: "
@@ -805,15 +855,20 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
DCLOG(INFO, dconn) << "Note: this is tunnel connection"; DCLOG(INFO, dconn) << "Note: this is tunnel connection";
} }
} }
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else {
return;
}
// Delete downstream connection. If we don't delete it here, it // Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback. // will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of // For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was // stream, we don't have to do anything since response was
@@ -843,7 +898,7 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
// At this point, downstream may be deleted. // At this point, downstream may be deleted.
} return;
} }
} }
} // namespace } // namespace

View File

@@ -105,7 +105,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream)
http_parser_init(&response_htp_, HTTP_RESPONSE); http_parser_init(&response_htp_, HTTP_RESPONSE);
response_htp_.data = downstream_; response_htp_.data = downstream_;
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WARTER_MARK); bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
bufferevent_enable(bev_, EV_READ); bufferevent_enable(bev_, EV_READ);
bufferevent_setcb(bev_, bufferevent_setcb(bev_,
upstream->get_downstream_readcb(), upstream->get_downstream_readcb(),
@@ -502,10 +502,20 @@ http_parser_settings htp_hooks = {
int HttpDownstreamConnection::on_read() int HttpDownstreamConnection::on_read()
{ {
auto input = bufferevent_get_input(bev_); auto input = bufferevent_get_input(bev_);
size_t inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
if(downstream_->get_upgraded()) { if(downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream. // For upgraded connection, just pass data to the upstream.
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
int rv; int rv;
rv = downstream_->get_upstream()->on_downstream_body rv = downstream_->get_upstream()->on_downstream_body
(downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true); (downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true);
@@ -516,9 +526,21 @@ int HttpDownstreamConnection::on_read()
DCLOG(FATAL, this) << "evbuffer_drain() failed"; DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1; return -1;
} }
}
}
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0; return 0;
} }
size_t nread = http_parser_execute(&response_htp_, &htp_hooks,
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<const char*>(mem), reinterpret_cast<const char*>(mem),
inputlen); inputlen);
@@ -526,18 +548,20 @@ int HttpDownstreamConnection::on_read()
DCLOG(FATAL, this) << "evbuffer_drain() failed"; DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1; return -1;
} }
auto htperr = HTTP_PARSER_ERRNO(&response_htp_); auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if(htperr == HPE_OK) {
return 0; if(htperr != HPE_OK) {
} else {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: " DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") " << "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr); << http_errno_description(htperr);
} }
return SHRPX_ERR_HTTP_PARSE; return SHRPX_ERR_HTTP_PARSE;
} }
} }
}
int HttpDownstreamConnection::on_write() int HttpDownstreamConnection::on_write()
{ {

View File

@@ -44,7 +44,7 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
namespace { namespace {
const size_t OUTBUF_MAX_THRES = 64*1024; const size_t OUTBUF_MAX_THRES = 16*1024;
} // namespace } // namespace
HttpsUpstream::HttpsUpstream(ClientHandler *handler) HttpsUpstream::HttpsUpstream(ClientHandler *handler)
@@ -193,20 +193,24 @@ int htp_hdrs_completecb(http_parser *htp)
} }
rv = dconn->attach_downstream(downstream); rv = dconn->attach_downstream(downstream);
if(rv != 0) { if(rv != 0) {
downstream->set_request_state(Downstream::CONNECT_FAIL); downstream->set_request_state(Downstream::CONNECT_FAIL);
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
return -1; return -1;
} else { }
rv = downstream->push_request_headers(); rv = downstream->push_request_headers();
if(rv != 0) { if(rv != 0) {
return -1; return -1;
} }
downstream->set_request_state(Downstream::HEADER_COMPLETE); downstream->set_request_state(Downstream::HEADER_COMPLETE);
return 0; return 0;
} }
}
} // namespace } // namespace
namespace { namespace {
@@ -264,49 +268,75 @@ int HttpsUpstream::on_read()
{ {
auto bev = handler_->get_bev(); auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev); auto input = bufferevent_get_input(bev);
size_t inputlen = evbuffer_get_length(input); auto downstream = get_downstream();
auto mem = evbuffer_pullup(input, -1);
// downstream can be nullptr here, because it is initialized in the
// callback chain called by http_parser_execute()
if(downstream && downstream->get_upgraded()) {
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) { if(inputlen == 0) {
return 0; return 0;
} }
auto downstream = get_downstream();
// downstream can be nullptr here, because it is initialized in the auto mem = evbuffer_pullup(input, inputlen);
// callback chain called by http_parser_execute()
if(downstream && downstream->get_upgraded()) { auto rv = downstream->push_upload_data_chunk
int rv = downstream->push_upload_data_chunk
(reinterpret_cast<const uint8_t*>(mem), inputlen); (reinterpret_cast<const uint8_t*>(mem), inputlen);
if(rv != 0) { if(rv != 0) {
return -1; return -1;
} }
if(evbuffer_drain(input, inputlen) != 0) { if(evbuffer_drain(input, inputlen) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed"; ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1; return -1;
} }
if(downstream->get_output_buffer_full()) { if(downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full"; ULOG(INFO, this) << "Downstream output buffer is full";
} }
pause_read(SHRPX_NO_BUFFER); pause_read(SHRPX_NO_BUFFER);
return 0;
} }
}
}
for(;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if(inputlen == 0) {
return 0; return 0;
} }
size_t nread = http_parser_execute(&htp_, &htp_hooks, auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(&htp_, &htp_hooks,
reinterpret_cast<const char*>(mem), reinterpret_cast<const char*>(mem),
inputlen); inputlen);
if(evbuffer_drain(input, nread) != 0) { if(evbuffer_drain(input, nread) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed"; ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1; return -1;
} }
// Well, actually header length + some body bytes // Well, actually header length + some body bytes
current_header_length_ += nread; current_header_length_ += nread;
// Get downstream again because it may be initialized in http parser // Get downstream again because it may be initialized in http parser
// execution // execution
downstream = get_downstream(); downstream = get_downstream();
auto handler = get_client_handler(); auto handler = get_client_handler();
auto htperr = HTTP_PARSER_ERRNO(&htp_); auto htperr = HTTP_PARSER_ERRNO(&htp_);
if(htperr == HPE_PAUSED) { if(htperr == HPE_PAUSED) {
assert(downstream);
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
handler->set_should_close_after_write(true); handler->set_should_close_after_write(true);
// Following paues_read is needed to avoid reading next data. // Following paues_read is needed to avoid reading next data.
@@ -315,48 +345,64 @@ int HttpsUpstream::on_read()
return -1; return -1;
} }
// Downstream gets deleted after response body is read. // Downstream gets deleted after response body is read.
} else { return 0;
}
assert(downstream->get_request_state() == Downstream::MSG_COMPLETE); assert(downstream->get_request_state() == Downstream::MSG_COMPLETE);
if(downstream->get_downstream_connection() == 0) {
if(downstream->get_downstream_connection() == nullptr) {
// Error response has already be sent // Error response has already be sent
assert(downstream->get_response_state() == Downstream::MSG_COMPLETE); assert(downstream->get_response_state() == Downstream::MSG_COMPLETE);
delete_downstream(); delete_downstream();
} else {
return 0;
}
if(handler->get_http2_upgrade_allowed() && if(handler->get_http2_upgrade_allowed() &&
downstream->http2_upgrade_request()) { downstream->http2_upgrade_request()) {
if(handler->perform_http2_upgrade(this) != 0) { if(handler->perform_http2_upgrade(this) != 0) {
return -1; return -1;
} }
return 0; return 0;
} }
pause_read(SHRPX_MSG_BLOCK); pause_read(SHRPX_MSG_BLOCK);
return 0;
} }
}
} else if(htperr == HPE_OK) { if(htperr != HPE_OK) {
// downstream can be NULL here.
if(downstream) {
if(downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
}
}
} else {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "HTTP parse failure: " ULOG(INFO, this) << "HTTP parse failure: "
<< "(" << http_errno_name(htperr) << ") " << "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr); << http_errno_description(htperr);
} }
handler->set_should_close_after_write(true); handler->set_should_close_after_write(true);
pause_read(SHRPX_MSG_BLOCK); pause_read(SHRPX_MSG_BLOCK);
if(error_reply(400) != 0) { if(error_reply(400) != 0) {
return -1; return -1;
} }
}
return 0; return 0;
} }
// downstream can be NULL here.
if(downstream && downstream->get_output_buffer_full()) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
}
int HttpsUpstream::on_write() int HttpsUpstream::on_write()
{ {
int rv = 0; int rv = 0;
@@ -389,9 +435,9 @@ int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream)
// are not notified by readcb until new data arrive. // are not notified by readcb until new data arrive.
http_parser_pause(&htp_, 0); http_parser_pause(&htp_, 0);
return on_read(); return on_read();
} else {
return 0;
} }
return 0;
} }
namespace { namespace {
@@ -401,22 +447,67 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
auto downstream = dconn->get_downstream(); auto downstream = dconn->get_downstream();
auto upstream = static_cast<HttpsUpstream*>(downstream->get_upstream()); auto upstream = static_cast<HttpsUpstream*>(downstream->get_upstream());
int rv; int rv;
rv = downstream->on_read(); rv = downstream->on_read();
if(downstream->get_response_state() == Downstream::MSG_RESET) { if(downstream->get_response_state() == Downstream::MSG_RESET) {
delete upstream->get_client_handler(); delete upstream->get_client_handler();
} else if(rv == 0) {
return;
}
if(rv != 0) {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// We already sent HTTP response headers to upstream
// client. Just close the upstream connection.
delete upstream->get_client_handler();
return;
}
// We did not sent any HTTP response, so sent error
// response. Cannot reuse downstream connection in this case.
if(upstream->error_reply(502) != 0) {
delete upstream->get_client_handler();
return;
}
if(downstream->get_request_state() == Downstream::MSG_COMPLETE) {
upstream->delete_downstream();
// Process next HTTP request
if(upstream->resume_read(SHRPX_MSG_BLOCK, 0) == -1) {
return;
}
}
return;
}
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
if(handler->get_outbuf_length() >= OUTBUF_MAX_THRES) {
downstream->pause_read(SHRPX_NO_BUFFER);
}
return;
}
if(downstream->get_response_connection_close()) { if(downstream->get_response_connection_close()) {
// Connection close // Connection close
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0;
dconn = nullptr;
} else { } else {
// Keep-alive // Keep-alive
dconn->detach_downstream(downstream); dconn->detach_downstream(downstream);
} }
if(downstream->get_request_state() == Downstream::MSG_COMPLETE) { if(downstream->get_request_state() == Downstream::MSG_COMPLETE) {
if(handler->get_should_close_after_write() && if(handler->get_should_close_after_write() &&
handler->get_outbuf_length() == 0) { handler->get_outbuf_length() == 0) {
@@ -424,15 +515,21 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
// the peer, we cannot use writecb for ClientHandler. In // the peer, we cannot use writecb for ClientHandler. In
// this case, we just delete handler here. // this case, we just delete handler here.
delete handler; delete handler;
return; return;
} else { }
upstream->delete_downstream(); upstream->delete_downstream();
// Process next HTTP request // Process next HTTP request
if(upstream->resume_read(SHRPX_MSG_BLOCK, 0) == -1) { if(upstream->resume_read(SHRPX_MSG_BLOCK, 0) == -1) {
return; return;
} }
return;
} }
} else if(downstream->get_upgraded()) {
if(downstream->get_upgraded()) {
// This path is effectively only taken for HTTP2 downstream // This path is effectively only taken for HTTP2 downstream
// because only HTTP2 downstream sets response_state to // because only HTTP2 downstream sets response_state to
// MSG_COMPLETE and this function. For HTTP downstream, EOF // MSG_COMPLETE and this function. For HTTP downstream, EOF
@@ -444,38 +541,16 @@ void https_downstream_readcb(bufferevent *bev, void *ptr)
// For tunneled connection, if there is no pending data, // For tunneled connection, if there is no pending data,
// delete handler because on_write will not be called. // delete handler because on_write will not be called.
delete handler; delete handler;
} else {
return;
}
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Tunneled connection has pending data"; DLOG(INFO, downstream) << "Tunneled connection has pending data";
} }
}
}
} else {
if(handler->get_outbuf_length() >= OUTBUF_MAX_THRES) {
downstream->pause_read(SHRPX_NO_BUFFER);
}
}
} else {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// We already sent HTTP response headers to upstream
// client. Just close the upstream connection.
delete upstream->get_client_handler();
} else {
// We did not sent any HTTP response, so sent error
// response. Cannot reuse downstream connection in this case.
if(upstream->error_reply(502) != 0) {
delete upstream->get_client_handler();
return; return;
} }
if(downstream->get_request_state() == Downstream::MSG_COMPLETE) {
upstream->delete_downstream();
// Process next HTTP request
if(upstream->resume_read(SHRPX_MSG_BLOCK, 0) == -1) {
return;
}
}
}
}
} }
} // namespace } // namespace
@@ -503,7 +578,11 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr)
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Connection established"; DCLOG(INFO, dconn) << "Connection established";
} }
} else if(events & BEV_EVENT_EOF) {
return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF"; DCLOG(INFO, dconn) << "EOF";
} }
@@ -525,9 +604,7 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr)
delete handler; delete handler;
return; return;
} }
} else if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { } else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// Nothing to do
} else {
// error // error
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Treated as error"; DCLOG(INFO, dconn) << "Treated as error";
@@ -543,7 +620,11 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
} }
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
return;
}
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) { if(events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Network error"; DCLOG(INFO, dconn) << "Network error";

View File

@@ -45,8 +45,8 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
namespace { namespace {
const size_t OUTBUF_MAX_THRES = 64*1024; const size_t OUTBUF_MAX_THRES = 16*1024;
const size_t INBUF_MAX_THRES = 64*1024; const size_t INBUF_MAX_THRES = 16*1024;
} // namespace } // namespace
namespace { namespace {
@@ -103,11 +103,16 @@ void on_stream_close_callback
<< " is being closed"; << " is being closed";
} }
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(downstream) { if(!downstream) {
return;
}
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) { if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else { return;
}
downstream->set_request_state(Downstream::STREAM_CLOSED); downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read // At this point, downstream response was read
@@ -121,7 +126,9 @@ void on_stream_close_callback
} }
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else { return;
}
// At this point, downstream read may be paused. // At this point, downstream read may be paused.
// If shrpx_downstream::push_request_headers() failed, the // If shrpx_downstream::push_request_headers() failed, the
@@ -132,9 +139,6 @@ void on_stream_close_callback
// and make client send RST_STREAM after it gets first DATA // and make client send RST_STREAM after it gets first DATA
// frame chunk. // frame chunk.
} }
}
}
}
} // namespace } // namespace
namespace { namespace {
@@ -245,12 +249,20 @@ void on_data_chunk_recv_callback(spdylay_session *session,
{ {
auto upstream = static_cast<SpdyUpstream*>(user_data); auto upstream = static_cast<SpdyUpstream*>(user_data);
auto downstream = upstream->find_downstream(stream_id); auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(!downstream) {
return;
}
if(downstream->push_upload_data_chunk(data, len) != 0) { if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return; return;
} }
if(upstream->get_flow_control()) {
if(!upstream->get_flow_control()) {
return;
}
// If connection-level window control is not enabled (e.g, // If connection-level window control is not enabled (e.g,
// spdy/3), spdylay_session_get_recv_data_length() is always // spdy/3), spdylay_session_get_recv_data_length() is always
// returns 0. // returns 0.
@@ -282,8 +294,6 @@ void on_data_chunk_recv_callback(spdylay_session *session,
return; return;
} }
} }
}
}
} // namespace } // namespace
namespace { namespace {
@@ -520,11 +530,11 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr)
// on_stream_close_callback. // on_stream_close_callback.
upstream->rst_stream(downstream, infer_upstream_rst_stream_status_code upstream->rst_stream(downstream, infer_upstream_rst_stream_status_code
(downstream->get_response_rst_stream_error_code())); (downstream->get_response_rst_stream_error_code()));
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
} else { } else {
int rv = downstream->on_read(); auto rv = downstream->on_read();
if(rv != 0) { if(rv != 0) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure"; DCLOG(INFO, dconn) << "HTTP parser failure";
@@ -541,9 +551,9 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr)
downstream->set_response_state(Downstream::MSG_COMPLETE); downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser // Clearly, we have to close downstream connection on http parser
// failure. // failure.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
} }
} }
if(upstream->send() != 0) { if(upstream->send() != 0) {
@@ -573,6 +583,7 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
auto dconn = static_cast<DownstreamConnection*>(ptr); auto dconn = static_cast<DownstreamConnection*>(ptr);
auto downstream = dconn->get_downstream(); auto downstream = dconn->get_downstream();
auto upstream = static_cast<SpdyUpstream*>(downstream->get_upstream()); auto upstream = static_cast<SpdyUpstream*>(downstream->get_upstream());
if(events & BEV_EVENT_CONNECTED) { if(events & BEV_EVENT_CONNECTED) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Connection established. stream_id=" DCLOG(INFO, dconn) << "Connection established. stream_id="
@@ -585,7 +596,10 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
DCLOG(WARNING, dconn) << "Setting option TCP_NODELAY failed: errno=" DCLOG(WARNING, dconn) << "Setting option TCP_NODELAY failed: errno="
<< errno; << errno;
} }
} else if(events & BEV_EVENT_EOF) { return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
} }
@@ -594,12 +608,14 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
// the first place. We can delete downstream. // the first place. We can delete downstream.
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else { return;
}
// Delete downstream connection. If we don't delete it here, it // Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback. // will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback. // downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) { if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF // Server may indicate the end of the request by EOF
@@ -627,8 +643,11 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
// At this point, downstream may be deleted. // At this point, downstream may be deleted.
return;
} }
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) { if(events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: " DCLOG(INFO, dconn) << "Downstream network error: "
@@ -644,12 +663,15 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) { if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream); upstream->remove_downstream(downstream);
delete downstream; delete downstream;
} else { return;
}
// Delete downstream connection. If we don't delete it here, it // Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback. // will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0); downstream->set_downstream_connection(nullptr);
delete dconn; delete dconn;
dconn = 0; dconn = nullptr;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) { if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of // For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was // stream, we don't have to do anything since response was
@@ -679,7 +701,7 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
return; return;
} }
// At this point, downstream may be deleted. // At this point, downstream may be deleted.
} return;
} }
} }
} // namespace } // namespace

View File

@@ -156,24 +156,6 @@ void info_callback(const SSL *ssl, int where, int ret)
} // namespace } // namespace
#if OPENSSL_VERSION_NUMBER >= 0x10002000L #if OPENSSL_VERSION_NUMBER >= 0x10002000L
namespace {
// Returns true if ALPN identifier list |in| of length |inlen|
// contains http/1.1.
bool check_http1_available_in_alpn_list(const unsigned char *in,
unsigned int inlen)
{
for(unsigned int i = 0; i < inlen; i += 1 + in[i]) {
if(in[i] == 8 && i + 1 + in[i] <= inlen &&
memcmp("http/1.1", &in[i + 1], in[i]) == 0) {
return true;
}
}
return false;
}
} // namespace
namespace { namespace {
int alpn_select_proto_cb(SSL* ssl, int alpn_select_proto_cb(SSL* ssl,
const unsigned char **out, const unsigned char **out,
@@ -181,39 +163,45 @@ int alpn_select_proto_cb(SSL* ssl,
const unsigned char *in, unsigned int inlen, const unsigned char *in, unsigned int inlen,
void *arg) void *arg)
{ {
int rv; // We assume that get_config()->npn_list contains ALPN protocol
// identifier sorted by preference order. So we just break when we
// found the first overlap.
for(auto needle_ptr = get_config()->npn_list,
end_needle_ptr = needle_ptr + get_config()->npn_list_len;
needle_ptr < end_needle_ptr; ++needle_ptr) {
if(check_http2_requirement(ssl)) { auto target_proto_id = *needle_ptr;
auto target_proto_len =
strlen(reinterpret_cast<const char*>(target_proto_id));
rv = nghttp2_select_next_protocol if(target_proto_len == NGHTTP2_PROTO_VERSION_ID_LEN &&
(const_cast<unsigned char**>(out), outlen, in, inlen); memcmp(target_proto_id, NGHTTP2_PROTO_VERSION_ID,
NGHTTP2_PROTO_VERSION_ID_LEN) == 0) {
if(!check_http2_requirement(ssl)) {
continue;
}
}
for(auto p = in, end = in + inlen; p < end;) {
auto proto_id = p + 1;
auto proto_len = *p;
if(proto_id + proto_len <= end &&
util::streq(target_proto_id, target_proto_len, proto_id, proto_len)) {
*out = reinterpret_cast<const unsigned char*>(proto_id);
*outlen = proto_len;
if(rv == 1) {
// HTTP/2 was selected
return SSL_TLSEXT_ERR_OK; return SSL_TLSEXT_ERR_OK;
} }
} else if(check_http1_available_in_alpn_list(in, inlen)) {
*out = reinterpret_cast<const unsigned char*>("http/1.1");
*outlen = strlen("http/1.1");
rv = 0; p += 1 + proto_len;
} else { }
rv = -1;
} }
#ifdef HAVE_SPDYLAY
rv = spdylay_select_next_protocol
(const_cast<unsigned char**>(out), outlen, in, inlen);
#endif // HAVE_SPDYLAY
if(rv == -1) {
// No selection was made
return SSL_TLSEXT_ERR_NOACK; return SSL_TLSEXT_ERR_NOACK;
} }
// We selected http/1.1
return SSL_TLSEXT_ERR_OK;
}
} // namespace } // namespace
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L

View File

@@ -173,7 +173,7 @@ void test_nghttp2_frame_pack_headers_frame_too_large(void)
nghttp2_bufs bufs; nghttp2_bufs bufs;
nghttp2_nv *nva; nghttp2_nv *nva;
ssize_t nvlen; ssize_t nvlen;
size_t big_vallen = NGHTTP2_HD_MAX_VALUE; size_t big_vallen = NGHTTP2_HD_MAX_NV;
nghttp2_nv big_hds[16]; nghttp2_nv big_hds[16];
size_t big_hdslen = ARRLEN(big_hds); size_t big_hdslen = ARRLEN(big_hds);
size_t i; size_t i;

View File

@@ -2374,7 +2374,7 @@ void test_nghttp2_session_send_headers_header_comp_error(void)
nghttp2_frame *frame = malloc(sizeof(nghttp2_frame)); nghttp2_frame *frame = malloc(sizeof(nghttp2_frame));
nghttp2_nv *nva; nghttp2_nv *nva;
ssize_t nvlen; ssize_t nvlen;
size_t vallen = NGHTTP2_HD_MAX_VALUE; size_t vallen = NGHTTP2_HD_MAX_NV;
nghttp2_nv nv[28]; nghttp2_nv nv[28];
size_t nnv = ARRLEN(nv); size_t nnv = ARRLEN(nv);
size_t i; size_t i;