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
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)
@@ -122,6 +123,13 @@ used::
$ ./configure
$ 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
----------------------

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 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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_INIT()
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 3)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_REVISION, 1)
AC_SUBST(LT_AGE, 0)
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
AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes])
LIBS=$LIBS_OLD
if test "x${have_jemalloc}" = "xyes"; then
JEMALLOC_LIBS="-ljemalloc"
if test "x${have_jemalloc}" = "xyes" &&
test "x${ac_cv_search_malloc_stats_print}" != "xnone required"; then
JEMALLOC_LIBS=${ac_cv_search_malloc_stats_print}
AC_SUBST([JEMALLOC_LIBS])
fi
fi

View File

@@ -1,5 +1,5 @@
.\" 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
h2load \- HTTP/2 benchmarking tool
.SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" 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
nghttp \- HTTP/2 experimental client
.SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" 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
nghttpd \- HTTP/2 experimental server
.SH SYNOPSIS

View File

@@ -1,5 +1,5 @@
.\" 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
nghttpx \- HTTP/2 experimental proxy
.SH SYNOPSIS
@@ -27,8 +27,10 @@ Set backend host and port.
Default: '127.0.0.1,80'
.TP
\fB\-f\fR, \fB\-\-frontend=\fR<HOST,PORT>
Set frontend host and port.
Default: '0.0.0.0,3000'
Set frontend host and port. If <HOST> is '*', it
assumes all addresses including both IPv4 and
IPv6.
Default: '*,3000'
.TP
\fB\-\-backlog=\fR<NUM>
Set listen backlog size. If \fB\-1\fR is given,
@@ -188,12 +190,12 @@ format. Without this option, DHE cipher suites
are not available.
.TP
\fB\-\-npn\-list=\fR<LIST>
Comma delimited list of NPN/ALPN protocol sorted
in the order of preference. That means most
desirable protocol comes first. The parameter
must be delimited by a single comma only and any
white spaces are treated as a part of protocol
string.
Comma delimited list of ALPN protocol identifier
sorted in the order of preference. That means
most desirable protocol comes first. This is
used in both ALPN and NPN. The parameter must be
delimited by a single comma only and any white
spaces are treated as a part of protocol string.
Default: h2\-12,spdy/3.1,spdy/3,spdy/2,http/1.1
.TP
\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`
* No stream ID is available because maximum stream ID was
* 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,
const nghttp2_priority_spec *pri_spec,
@@ -2309,6 +2318,15 @@ int nghttp2_submit_response(nghttp2_session *session,
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was
* 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 stream_id,
@@ -2469,6 +2487,15 @@ int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
* :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
* No stream ID is available because maximum stream ID was
* 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 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,
const nghttp2_priority_spec *pri_spec)
{
frame_set_hd(&frame->hd, 5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE,
stream_id);
frame_set_hd(&frame->hd, NGHTTP2_PRIORITY_SPECLEN, NGHTTP2_PRIORITY,
NGHTTP2_FLAG_NONE, stream_id);
frame->pri_spec = *pri_spec;
}
@@ -196,8 +196,7 @@ void nghttp2_frame_altsvc_init(nghttp2_altsvc *frame, int32_t stream_id,
{
size_t payloadlen;
/* 8 for Max-Age, Port, Reserved and PID_LEN. 1 for HOST_LEN. */
payloadlen = 8 + protocol_id_len + 1 + host_len + origin_len;
payloadlen = NGHTTP2_ALTSVC_MINLEN + protocol_id_len + host_len + origin_len;
frame_set_hd(&frame->hd, payloadlen, NGHTTP2_ALTSVC, NGHTTP2_FLAG_NONE,
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)
{
if(flags & NGHTTP2_FLAG_PRIORITY) {
return 5;
return NGHTTP2_PRIORITY_SPECLEN;
}
return 0;
@@ -437,7 +436,7 @@ int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame)
buf = &bufs->head->buf;
assert(nghttp2_buf_avail(buf) >= 5);
assert(nghttp2_buf_avail(buf) >= NGHTTP2_PRIORITY_SPECLEN);
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);
buf->last += 5;
buf->last += NGHTTP2_PRIORITY_SPECLEN;
return 0;
}

View File

@@ -60,6 +60,18 @@
/* The maximum header table size in SETTINGS_HEADER_TABLE_SIZE */
#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. */
typedef enum {
/* non-DATA frame */

View File

@@ -352,37 +352,29 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater)
NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
inflater->ent_keep = NULL;
inflater->name_keep = NULL;
inflater->value_keep = NULL;
inflater->nv_keep = NULL;
inflater->end_headers_index = 0;
inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
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) {
goto namebuf_fail;
}
rv = nghttp2_bufs_init(&inflater->valuebufs, NGHTTP2_HD_MAX_VALUE / 2, 2);
if(rv != 0) {
goto valuebuf_fail;
goto nvbufs_fail;
}
inflater->huffman_encoded = 0;
inflater->index = 0;
inflater->left = 0;
inflater->newnamelen = 0;
inflater->index_required = 0;
inflater->no_index = 0;
inflater->ent_name = NULL;
return 0;
valuebuf_fail:
nghttp2_bufs_free(&inflater->namebufs);
namebuf_fail:
nvbufs_fail:
hd_context_free(&inflater->ctx);
fail:
return rv;
@@ -397,10 +389,9 @@ static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater)
}
inflater->ent_keep = NULL;
}
free(inflater->name_keep);
free(inflater->value_keep);
inflater->name_keep = NULL;
inflater->value_keep = NULL;
free(inflater->nv_keep);
inflater->nv_keep = NULL;
}
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)
{
hd_inflate_keep_free(inflater);
nghttp2_bufs_free(&inflater->namebufs);
nghttp2_bufs_free(&inflater->valuebufs);
nghttp2_bufs_free(&inflater->nvbufs);
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)
{
ssize_t rv;
size_t buflen;
uint8_t *buf;
if(value_only) {
nv->name = NULL;
} else {
rv = nghttp2_bufs_remove(&inflater->namebufs, &nv->name);
rv = nghttp2_bufs_remove(&inflater->nvbufs, &buf);
if(rv < 0) {
return NGHTTP2_ERR_NOMEM;
}
nv->namelen = rv;
}
rv = nghttp2_bufs_remove(&inflater->valuebufs, &nv->value);
if(rv < 0) {
free(nv->name);
return NGHTTP2_ERR_NOMEM;
}
nv->valuelen = rv;
buflen = rv;
if(value_only) {
nv->name = NULL;
nv->namelen = 0;
} else {
nv->name = buf;
nv->namelen = inflater->newnamelen;
}
nv->value = buf + nv->namelen;
nv->valuelen = buflen - nv->namelen;
return 0;
}
@@ -1545,9 +1535,10 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
nghttp2_hd_entry *new_ent;
uint8_t ent_flags;
ent_flags =
NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC |
NGHTTP2_HD_FLAG_NAME_GIFT | NGHTTP2_HD_FLAG_VALUE_GIFT;
/* nv->value points to the middle of the buffer pointed by
nv->name. So we just need to keep track of nv->name for memory
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);
@@ -1559,15 +1550,13 @@ static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
}
free(nv.name);
free(nv.value);
return NGHTTP2_ERR_NOMEM;
}
emit_literal_header(nv_out, &nv);
inflater->name_keep = nv.name;
inflater->value_keep = nv.value;
inflater->nv_keep = nv.name;
return 0;
}
@@ -1642,7 +1631,7 @@ static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
emit_literal_header(nv_out, &nv);
inflater->value_keep = nv.value;
inflater->nv_keep = nv.value;
return 0;
}
@@ -1793,7 +1782,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN:
rfin = 0;
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
NGHTTP2_HD_MAX_NAME);
NGHTTP2_HD_MAX_NV);
if(rv < 0) {
goto fail;
}
@@ -1815,7 +1804,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
}
break;
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) {
goto fail;
}
@@ -1831,11 +1820,13 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto almost_ok;
}
inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
break;
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) {
goto fail;
}
@@ -1850,6 +1841,8 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
goto almost_ok;
}
inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
break;
@@ -1863,7 +1856,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
case NGHTTP2_HD_STATE_READ_VALUELEN:
rfin = 0;
rv = hd_inflate_read_len(inflater, &rfin, in, last, 7,
NGHTTP2_HD_MAX_VALUE);
NGHTTP2_HD_MAX_NV);
if(rv < 0) {
goto fail;
}
@@ -1898,7 +1891,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
}
break;
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) {
goto fail;
}
@@ -1929,7 +1922,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
return in - first;
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) {
DEBUGF(fprintf(stderr, "inflatehd: value read failure %zd: %s\n",
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_ENTRY_OVERHEAD 32
/* The maximum value length of name/value pair. This is not specified
by the spec. We just chose the arbitrary size */
#define NGHTTP2_HD_MAX_NAME 256
#define NGHTTP2_HD_MAX_VALUE 8192
/* The maximum length of one name/value pair. This is the sum of the
length of name and value. This is not specified by the spec. We
just chose the arbitrary size */
#define NGHTTP2_HD_MAX_NV 8192
/* Default size of maximum table buffer size for encoder. Even if
remote decoder notifies larger buffer size for its decoding,
@@ -146,20 +146,17 @@ struct nghttp2_hd_deflater {
struct nghttp2_hd_inflater {
nghttp2_hd_context ctx;
/* header name buffer */
nghttp2_bufs namebufs;
/* header value buffer */
nghttp2_bufs valuebufs;
/* header buffer */
nghttp2_bufs nvbufs;
/* Stores current state of huffman decoding */
nghttp2_hd_huff_decode_context huff_decode_ctx;
/* Pointer to the nghttp2_hd_entry which is used current header
emission. This is required because in some cases the
ent_keep->ref == 0 and we have to keep track of it. */
nghttp2_hd_entry *ent_keep;
/* Pointers to the name/value pair which are used current header
emission. They are usually used to keep track of malloc'ed memory
for huffman decoding. */
uint8_t *name_keep, *value_keep;
/* Pointer to the name/value pair buffer which is used in the
current header emission. */
uint8_t *nv_keep;
/* Pointers to the name/value pair which is referred as indexed
name. This entry must be in header table. */
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
set at the end of decompression. */
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
same value transmitted in SETTINGS_HEADER_TABLE_SIZE */
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) {
uint8_t in = src[i] >> 4;
for(j = 0; j < 2; ++j) {
const nghttp2_huff_decode *t = &huff_decode_table[ctx->state][in];
if(t->state == -1) {
const nghttp2_huff_decode *t;
t = &huff_decode_table[ctx->state][in];
if(t->flags & NGHTTP2_HUFF_FAIL) {
return NGHTTP2_ERR_HEADER_COMP;
}
if(t->flags & NGHTTP2_HUFF_SYM) {

View File

@@ -36,13 +36,17 @@ typedef enum {
sequence. */
NGHTTP2_HUFF_ACCEPTED = 1,
/* 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;
typedef struct {
/* huffman decoding state, which is actually the node ID of internal
huffman tree */
int16_t state;
huffman tree. We have 257 leaf nodes, but they are identical to
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 */
uint8_t flags;
/* 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, 254},
{0, 0x03, 255},
{-1, 0x00, 0},
{0, 0x04, 0},
{189, 0x00, 0},
{191, 0x00, 0},
{192, 0x00, 0},
@@ -3772,8 +3772,8 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
{14, 0x03, 254},
{1, 0x02, 255},
{14, 0x03, 255},
{-1, 0x00, 0},
{-1, 0x00, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x03, 0},
{0, 0x03, 1},
{0, 0x03, 2},
@@ -3840,10 +3840,10 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
},
/* 187 */
{
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{1, 0x02, 0},
{14, 0x03, 0},
{1, 0x02, 1},
@@ -3859,14 +3859,14 @@ const nghttp2_huff_decode huff_decode_table[][16] = {
},
/* 188 */
{
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{-1, 0x00, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{0, 0x04, 0},
{2, 0x02, 0},
{6, 0x02, 0},
{15, 0x02, 0},

View File

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

View File

@@ -4342,7 +4342,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
if(iframe->payloadleft != 5) {
if(iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) {
busy = 1;
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;
inbound_frame_set_mark(iframe, 5);
inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN);
break;
case NGHTTP2_RST_STREAM:
@@ -4490,7 +4490,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
break;
}
if(iframe->payloadleft < 9) {
if(iframe->payloadleft < NGHTTP2_ALTSVC_MINLEN) {
busy = 1;
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;
inbound_frame_set_mark(iframe, 8);
inbound_frame_set_mark(iframe, NGHTTP2_ALTSVC_FIXED_PARTLEN);
break;
case NGHTTP2_BLOCKED:
@@ -4711,7 +4711,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
case NGHTTP2_ALTSVC: {
size_t varlen;
varlen = iframe->frame.hd.length - 8;
varlen = iframe->frame.hd.length - NGHTTP2_ALTSVC_FIXED_PARTLEN;
if(varlen > 0) {
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));
if(data_readlen > 0 &&
if(stream && data_readlen > 0 &&
session->callbacks.on_data_chunk_recv_callback) {
rv = session->callbacks.on_data_chunk_recv_callback
(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)
{
size_t num_active_streams;
/* If these flags are set, we don't want to read. The application
should drop the connection. */
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0;
}
num_active_streams = session_get_num_active_streams(session);
/* Unless GOAWAY is sent or received, we always want to read
incoming frames. After GOAWAY is sent or received, we are only
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)
{
size_t num_active_streams;
/* If these flags are set, we don't want to write any data. The
application should drop the connection. */
if((session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_SEND)) {
return 0;
}
num_active_streams = session_get_num_active_streams(session);
/*
* Unless GOAWAY is sent or received, we want to write frames if
* 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) ||
(!nghttp2_pq_empty(&session->ob_ss_pq) &&
!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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -651,53 +651,75 @@ struct HttpClient {
{
int rv;
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
if(inputlen == 0) {
return 0;
}
auto mem = evbuffer_pullup(input, -1);
auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(config.verbose) {
std::cout.write(reinterpret_cast<const char*>(mem), nread);
}
evbuffer_drain(input, nread);
auto htperr = HTTP_PARSER_ERRNO(htp.get());
if(htperr == HPE_OK) {
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);
auto nread = http_parser_execute(htp.get(), &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(config.verbose) {
std::cout.write(reinterpret_cast<const char*>(mem), nread);
}
if(evbuffer_drain(input, nread) != 0) {
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(htp.get());
if(htperr != HPE_OK) {
std::cerr << "Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << std::endl;
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;
}
} else {
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1;
return 0;
}
std::cerr << "HTTP Upgrade failed" << std::endl;
return -1;
}
} else {
std::cerr << "Failed to parse HTTP Upgrade response header: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr) << std::endl;
return -1;
}
return 0;
}
int on_connect()
@@ -776,18 +798,30 @@ struct HttpClient {
{
int rv;
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = nghttp2_session_mem_recv(session, mem, inputlen);
if(rv < 0) {
std::cerr << "nghttp2_session_mem_recv() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -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);
if(rv < 0) {
std::cerr << "nghttp2_session_mem_recv() returned error: "
<< nghttp2_strerror(rv) << std::endl;
return -1;
}
if(evbuffer_drain(input, rv) != 0) {
return -1;
}
}
evbuffer_drain(input, rv);
return on_write();
}
int on_write()

View File

@@ -131,7 +131,6 @@ void evlistener_errorcb(evconnlistener *listener, void *ptr)
namespace {
evconnlistener* create_evlistener(ListenHandler *handler, int family)
{
// TODO Listen both IPv4 and IPv6
addrinfo hints;
int fd = -1;
int r;
@@ -145,8 +144,10 @@ evconnlistener* create_evlistener(ListenHandler *handler, int family)
hints.ai_flags |= AI_ADDRCONFIG;
#endif // AI_ADDRCONFIG
auto node = strcmp("*", get_config()->host) == 0 ? NULL : get_config()->host;
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(LOG_ENABLED(INFO)) {
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()->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()->private_key_file = 0;
mod_config()->private_key_passwd = 0;
@@ -499,7 +500,9 @@ Connections:
<< get_config()->downstream_host << ","
<< get_config()->downstream_port << R"('
-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: ')"
<< get_config()->host << "," << get_config()->port << R"('
--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
format. Without this option, DHE cipher suites
are not available.
--npn-list=<LIST> Comma delimited list of NPN/ALPN protocol sorted
in the order of preference. That means most
desirable protocol comes first. The parameter
must be delimited by a single comma only and any
white spaces are treated as a part of protocol
string.
--npn-list=<LIST> Comma delimited list of ALPN protocol identifier
sorted in the order of preference. That means
most desirable protocol comes first. This is
used in both ALPN and NPN. The parameter must be
delimited by a single comma only and any white
spaces are treated as a part of protocol string.
Default: )" << DEFAULT_NPN_LIST << R"(
--verify-client Require and verify client certificate.
--verify-client-cacert=<PATH>

View File

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

View File

@@ -59,7 +59,7 @@ namespace {
void upstream_writecb(bufferevent *bev, void *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) {
// Possibly because of deferred callback, we may get this callback
// when the output buffer is not empty.
@@ -279,7 +279,7 @@ ClientHandler::ClientHandler(bufferevent *bev,
}
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,
&get_config()->upstream_write_timeout);
if(ssl_) {
@@ -361,7 +361,13 @@ void ClientHandler::set_bev_cb
void ClientHandler::set_upstream_timeouts(const timeval *read_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()

View File

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

View File

@@ -46,8 +46,8 @@ using namespace nghttp2;
namespace shrpx {
namespace {
const size_t OUTBUF_MAX_THRES = 64*1024;
const size_t INBUF_MAX_THRES = 64*1024;
const size_t OUTBUF_MAX_THRES = 16*1024;
const size_t INBUF_MAX_THRES = 16*1024;
} // namespace
namespace {
@@ -61,37 +61,50 @@ int on_stream_close_callback
<< " is being closed";
}
auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream);
delete downstream;
} else {
downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if(!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
// Keep-alive
auto dconn = downstream->get_downstream_connection();
if(dconn) {
dconn->detach_downstream(downstream);
}
}
upstream->remove_downstream(downstream);
delete downstream;
} else {
// At this point, downstream read may be paused.
// If shrpx_downstream::push_request_headers() failed, the
// error is handled here.
upstream->remove_downstream(downstream);
delete downstream;
// How to test this case? Request sufficient large download
// and make client send RST_STREAM after it gets first DATA
// frame chunk.
if(!downstream) {
return 0;
}
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream);
delete downstream;
return 0;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if(!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
// Keep-alive
auto dconn = downstream->get_downstream_connection();
if(dconn) {
dconn->detach_downstream(downstream);
}
}
upstream->remove_downstream(downstream);
delete downstream;
return 0;
}
// At this point, downstream read may be paused.
// If shrpx_downstream::push_request_headers() failed, the
// error is handled here.
upstream->remove_downstream(downstream);
delete downstream;
// How to test this case? Request sufficient large download
// and make client send RST_STREAM after it gets first DATA
// frame chunk.
return 0;
}
} // namespace
@@ -250,14 +263,11 @@ int on_begin_headers_callback(nghttp2_session *session,
namespace {
int on_request_headers(Http2Upstream *upstream,
Downstream *downstream,
nghttp2_session *session,
const nghttp2_frame *frame)
{
int rv;
auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) {
return 0;
}
downstream->normalize_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);
}
auto upstream = static_cast<Http2Upstream*>(user_data);
switch(frame->hd.type) {
case NGHTTP2_DATA: {
auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) {
break;
}
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) {
return 0;
}
downstream->end_upload_data();
downstream->set_request_state(Downstream::MSG_COMPLETE);
}
break;
}
case NGHTTP2_HEADERS:
return on_request_headers(upstream, session, frame);
case NGHTTP2_PRIORITY: {
case NGHTTP2_HEADERS: {
auto downstream = upstream->find_downstream(frame->hd.stream_id);
if(!downstream) {
break;
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;
}
case NGHTTP2_PRIORITY: {
// TODO comment out for now
// rv = downstream->change_priority(frame->priority.pri);
// if(rv != 0) {
@@ -413,12 +436,16 @@ int on_data_chunk_recv_callback(nghttp2_session *session,
{
auto upstream = static_cast<Http2Upstream*>(user_data);
auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
return 0;
}
if(!downstream) {
return 0;
}
if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
return 0;
}
return 0;
}
} // namespace
@@ -584,17 +611,30 @@ int Http2Upstream::on_read()
ssize_t rv = 0;
auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev);
auto inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if(rv < 0) {
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -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);
if(rv < 0) {
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
if(evbuffer_drain(input, rv) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
evbuffer_drain(input, rv);
return send();
}
int Http2Upstream::on_write()
@@ -668,6 +708,7 @@ void downstream_readcb(bufferevent *bev, void *ptr)
auto dconn = static_cast<DownstreamConnection*>(ptr);
auto downstream = dconn->get_downstream();
auto upstream = static_cast<Http2Upstream*>(downstream->get_upstream());
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If upstream HTTP2 stream was closed, we just close downstream,
// 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.
upstream->rst_stream(downstream, infer_upstream_rst_stream_error_code
(downstream->get_response_rst_stream_error_code()));
downstream->set_downstream_connection(0);
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = 0;
dconn = nullptr;
} else {
int rv = downstream->on_read();
auto rv = downstream->on_read();
if(rv != 0) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure";
@@ -705,9 +746,9 @@ void downstream_readcb(bufferevent *bev, void *ptr)
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser
// failure.
downstream->set_downstream_connection(0);
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = 0;
dconn = nullptr;
}
}
if(upstream->send() != 0) {
@@ -742,14 +783,18 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
DCLOG(INFO, dconn) << "Connection established. stream_id="
<< downstream->get_stream_id();
}
int fd = bufferevent_getfd(bev);
auto fd = bufferevent_getfd(bev);
int val = 1;
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="
<< errno;
}
} else if(events & BEV_EVENT_EOF) {
return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
}
@@ -758,41 +803,46 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
// the first place. We can delete downstream.
upstream->remove_downstream(downstream);
delete downstream;
} else {
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0);
delete dconn;
dconn = 0;
// downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// For tunneled connection, MSG_COMPLETE signals
// downstream_data_read_callback to send RST_STREAM after
// pending response body is sent. This is needed to ensure
// that RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if(upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
return;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
if(upstream->send() != 0) {
downstream->set_response_state(Downstream::MSG_COMPLETE);
// For tunneled connection, MSG_COMPLETE signals
// downstream_data_read_callback to send RST_STREAM after
// pending response body is sent. This is needed to ensure
// that RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if(upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: "
@@ -805,45 +855,50 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr)
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
delete downstream;
} else {
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0);
delete dconn;
dconn = 0;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if(downstream->get_upgraded()) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
} else {
unsigned int status;
if(events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if(upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = nullptr;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if(downstream->get_upgraded()) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
} else {
unsigned int status;
if(events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if(upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
}
} // namespace

View File

@@ -105,7 +105,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream)
http_parser_init(&response_htp_, HTTP_RESPONSE);
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_setcb(bev_,
upstream->get_downstream_readcb(),
@@ -502,40 +502,64 @@ http_parser_settings htp_hooks = {
int HttpDownstreamConnection::on_read()
{
auto input = bufferevent_get_input(bev_);
size_t inputlen = evbuffer_get_length(input);
auto mem = evbuffer_pullup(input, -1);
if(downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream.
int rv;
rv = downstream_->get_upstream()->on_downstream_body
(downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true);
if(rv != 0) {
return rv;
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;
rv = downstream_->get_upstream()->on_downstream_body
(downstream_, reinterpret_cast<const uint8_t*>(mem), inputlen, true);
if(rv != 0) {
return rv;
}
if(evbuffer_drain(input, inputlen) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
}
if(evbuffer_drain(input, inputlen) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
return 0;
}
size_t nread = http_parser_execute(&response_htp_, &htp_hooks,
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);
auto nread = http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<const char*>(mem),
inputlen);
if(evbuffer_drain(input, nread) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if(htperr == HPE_OK) {
return 0;
} else {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
if(evbuffer_drain(input, nread) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
if(htperr != HPE_OK) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "HTTP parser failure: "
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
return SHRPX_ERR_HTTP_PARSE;
}
return SHRPX_ERR_HTTP_PARSE;
}
}

View File

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

View File

@@ -45,8 +45,8 @@ using namespace nghttp2;
namespace shrpx {
namespace {
const size_t OUTBUF_MAX_THRES = 64*1024;
const size_t INBUF_MAX_THRES = 64*1024;
const size_t OUTBUF_MAX_THRES = 16*1024;
const size_t INBUF_MAX_THRES = 16*1024;
} // namespace
namespace {
@@ -103,37 +103,41 @@ void on_stream_close_callback
<< " is being closed";
}
auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream);
delete downstream;
} else {
downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if(!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
// Keep-alive
auto dconn = downstream->get_downstream_connection();
if(dconn) {
dconn->detach_downstream(downstream);
}
}
upstream->remove_downstream(downstream);
delete downstream;
} else {
// At this point, downstream read may be paused.
if(!downstream) {
return;
}
// If shrpx_downstream::push_request_headers() failed, the
// error is handled here.
upstream->remove_downstream(downstream);
delete downstream;
// How to test this case? Request sufficient large download
// and make client send RST_STREAM after it gets first DATA
// frame chunk.
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
upstream->remove_downstream(downstream);
delete downstream;
return;
}
downstream->set_request_state(Downstream::STREAM_CLOSED);
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// At this point, downstream response was read
if(!downstream->get_upgraded() &&
!downstream->get_response_connection_close()) {
// Keep-alive
auto dconn = downstream->get_downstream_connection();
if(dconn) {
dconn->detach_downstream(downstream);
}
}
upstream->remove_downstream(downstream);
delete downstream;
return;
}
// At this point, downstream read may be paused.
// If shrpx_downstream::push_request_headers() failed, the
// error is handled here.
upstream->remove_downstream(downstream);
delete downstream;
// How to test this case? Request sufficient large download
// and make client send RST_STREAM after it gets first DATA
// frame chunk.
}
} // namespace
@@ -245,43 +249,49 @@ void on_data_chunk_recv_callback(spdylay_session *session,
{
auto upstream = static_cast<SpdyUpstream*>(user_data);
auto downstream = upstream->find_downstream(stream_id);
if(downstream) {
if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return;
if(!downstream) {
return;
}
if(downstream->push_upload_data_chunk(data, len) != 0) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return;
}
if(!upstream->get_flow_control()) {
return;
}
// If connection-level window control is not enabled (e.g,
// spdy/3), spdylay_session_get_recv_data_length() is always
// returns 0.
if(spdylay_session_get_recv_data_length(session) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_connection_window_bits)) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream)
<< "Flow control error on connection: "
<< "recv_window_size="
<< spdylay_session_get_recv_data_length(session)
<< ", window_size="
<< (1 << get_config()->http2_upstream_connection_window_bits);
}
if(upstream->get_flow_control()) {
// If connection-level window control is not enabled (e.g,
// spdy/3), spdylay_session_get_recv_data_length() is always
// returns 0.
if(spdylay_session_get_recv_data_length(session) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_connection_window_bits)) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream)
<< "Flow control error on connection: "
<< "recv_window_size="
<< spdylay_session_get_recv_data_length(session)
<< ", window_size="
<< (1 << get_config()->http2_upstream_connection_window_bits);
}
spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR);
return;
}
if(spdylay_session_get_stream_recv_data_length(session, stream_id) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_window_bits)) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream)
<< "Flow control error: recv_window_size="
<< spdylay_session_get_stream_recv_data_length(session, stream_id)
<< ", initial_window_size="
<< (1 << get_config()->http2_upstream_window_bits);
}
upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR);
return;
}
spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR);
return;
}
if(spdylay_session_get_stream_recv_data_length(session, stream_id) >
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
1 << get_config()->http2_upstream_window_bits)) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream)
<< "Flow control error: recv_window_size="
<< spdylay_session_get_stream_recv_data_length(session, stream_id)
<< ", initial_window_size="
<< (1 << get_config()->http2_upstream_window_bits);
}
upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR);
return;
}
}
} // namespace
@@ -520,11 +530,11 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr)
// on_stream_close_callback.
upstream->rst_stream(downstream, infer_upstream_rst_stream_status_code
(downstream->get_response_rst_stream_error_code()));
downstream->set_downstream_connection(0);
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = 0;
dconn = nullptr;
} else {
int rv = downstream->on_read();
auto rv = downstream->on_read();
if(rv != 0) {
if(LOG_ENABLED(INFO)) {
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);
// Clearly, we have to close downstream connection on http parser
// failure.
downstream->set_downstream_connection(0);
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = 0;
dconn = nullptr;
}
}
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 downstream = dconn->get_downstream();
auto upstream = static_cast<SpdyUpstream*>(downstream->get_upstream());
if(events & BEV_EVENT_CONNECTED) {
if(LOG_ENABLED(INFO)) {
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="
<< errno;
}
} else if(events & BEV_EVENT_EOF) {
return;
}
if(events & BEV_EVENT_EOF) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
}
@@ -594,41 +608,46 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
// the first place. We can delete downstream.
upstream->remove_downstream(downstream);
delete downstream;
} else {
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0);
delete dconn;
dconn = 0;
// downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
return;
}
// For tunneled connection, MSG_COMPLETE signals
// spdy_data_read_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that
// RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if(upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
if(upstream->send() != 0) {
downstream->set_response_state(Downstream::MSG_COMPLETE);
// For tunneled connection, MSG_COMPLETE signals
// spdy_data_read_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that
// RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if(downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if(upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
} else if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
if(events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if(LOG_ENABLED(INFO)) {
if(events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: "
@@ -644,42 +663,45 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr)
if(downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
delete downstream;
} else {
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(0);
delete dconn;
dconn = 0;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if(downstream->get_upgraded()) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
}
} else {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
} else {
unsigned int status;
if(events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if(upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->set_downstream_connection(nullptr);
delete dconn;
dconn = nullptr;
if(downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if(downstream->get_upgraded()) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
}
} else {
if(downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
} else {
unsigned int status;
if(events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if(upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if(upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
}
} // namespace

View File

@@ -156,24 +156,6 @@ void info_callback(const SSL *ssl, int where, int ret)
} // namespace
#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 {
int alpn_select_proto_cb(SSL* ssl,
const unsigned char **out,
@@ -181,38 +163,44 @@ int alpn_select_proto_cb(SSL* ssl,
const unsigned char *in, unsigned int inlen,
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
(const_cast<unsigned char**>(out), outlen, in, inlen);
if(target_proto_len == NGHTTP2_PROTO_VERSION_ID_LEN &&
memcmp(target_proto_id, NGHTTP2_PROTO_VERSION_ID,
NGHTTP2_PROTO_VERSION_ID_LEN) == 0) {
if(rv == 1) {
// HTTP/2 was selected
return SSL_TLSEXT_ERR_OK;
if(!check_http2_requirement(ssl)) {
continue;
}
}
} 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;
} else {
rv = -1;
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;
return SSL_TLSEXT_ERR_OK;
}
p += 1 + proto_len;
}
}
#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;
}
// We selected http/1.1
return SSL_TLSEXT_ERR_OK;
return SSL_TLSEXT_ERR_NOACK;
}
} // namespace
#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_nv *nva;
ssize_t nvlen;
size_t big_vallen = NGHTTP2_HD_MAX_VALUE;
size_t big_vallen = NGHTTP2_HD_MAX_NV;
nghttp2_nv big_hds[16];
size_t big_hdslen = ARRLEN(big_hds);
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_nv *nva;
ssize_t nvlen;
size_t vallen = NGHTTP2_HD_MAX_VALUE;
size_t vallen = NGHTTP2_HD_MAX_NV;
nghttp2_nv nv[28];
size_t nnv = ARRLEN(nv);
size_t i;