mirror of
https://github.com/nghttp2/nghttp2.git
synced 2025-12-07 18:48:54 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7146954de2 | ||
|
|
75b9be2d5a | ||
|
|
99aaaccf03 | ||
|
|
7e217511bf | ||
|
|
8c67bbe3a8 | ||
|
|
cc250386df | ||
|
|
3b7c733246 | ||
|
|
7e5567341f | ||
|
|
4f7223e89f | ||
|
|
88b69bb669 | ||
|
|
7a797b2c11 | ||
|
|
832f2fc00f | ||
|
|
d113055899 | ||
|
|
53e75ff0d0 | ||
|
|
3921af434a | ||
|
|
5e6a2fa256 | ||
|
|
86ab9f33de | ||
|
|
d844b0acd0 | ||
|
|
d733c87567 | ||
|
|
589d3e71a3 | ||
|
|
db354b228a | ||
|
|
1fa5852f8f | ||
|
|
ebf0e4d787 | ||
|
|
9677788317 | ||
|
|
78a55935ac | ||
|
|
2aa84019c7 | ||
|
|
6d942dc0a6 | ||
|
|
d408ee1783 | ||
|
|
672ad82849 | ||
|
|
896717f5d4 | ||
|
|
eba2825286 | ||
|
|
a4a06947a5 | ||
|
|
e54e86375b |
36
.travis.yml
Normal file
36
.travis.yml
Normal 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
|
||||
12
README.rst
12
README.rst
@@ -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
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
# include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Macros, types and constants for internal use */
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
|
||||
@@ -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,
|
||||
|
||||
11
mkhufftbl.py
11
mkhufftbl.py
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
100
src/nghttp.cc
100
src/nghttp.cc
@@ -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()
|
||||
|
||||
23
src/shrpx.cc
23
src/shrpx.cc
@@ -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>
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
#define DIE() \
|
||||
assert(0);
|
||||
|
||||
#define SHRPX_READ_WARTER_MARK (64*1024)
|
||||
#define SHRPX_READ_WATERMARK (16 * 1024)
|
||||
|
||||
#endif // SHRPX_H
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user