Merge branch 'libev'
This commit is contained in:
commit
1474f755cf
|
@ -21,6 +21,7 @@ before_install:
|
||||||
libcunit1-dev
|
libcunit1-dev
|
||||||
libssl-dev
|
libssl-dev
|
||||||
libxml2-dev
|
libxml2-dev
|
||||||
|
libev-dev
|
||||||
libevent-dev
|
libevent-dev
|
||||||
libjansson-dev
|
libjansson-dev
|
||||||
libjemalloc-dev
|
libjemalloc-dev
|
||||||
|
|
|
@ -71,7 +71,7 @@ To build and run the application programs (``nghttp``, ``nghttpd`` and
|
||||||
required:
|
required:
|
||||||
|
|
||||||
* OpenSSL >= 1.0.1
|
* OpenSSL >= 1.0.1
|
||||||
* libevent-openssl >= 2.0.8
|
* libev >= 4.15
|
||||||
* zlib >= 1.2.3
|
* zlib >= 1.2.3
|
||||||
|
|
||||||
ALPN support requires unreleased version OpenSSL >= 1.0.2.
|
ALPN support requires unreleased version OpenSSL >= 1.0.2.
|
||||||
|
@ -90,6 +90,10 @@ The HPACK tools require the following package:
|
||||||
|
|
||||||
* jansson >= 2.5
|
* jansson >= 2.5
|
||||||
|
|
||||||
|
To build sources under examples directory, libevent is required:
|
||||||
|
|
||||||
|
* libevent-openssl >= 2.0.8
|
||||||
|
|
||||||
To mitigate heap fragmentation in long running server programs
|
To mitigate heap fragmentation in long running server programs
|
||||||
(``nghttpd`` and ``nghttpx``), jemalloc is recommended:
|
(``nghttpd`` and ``nghttpx``), jemalloc is recommended:
|
||||||
|
|
||||||
|
@ -119,6 +123,7 @@ installed:
|
||||||
* libcunit1-dev
|
* libcunit1-dev
|
||||||
* libssl-dev
|
* libssl-dev
|
||||||
* libxml2-dev
|
* libxml2-dev
|
||||||
|
* libev-dev
|
||||||
* libevent-dev
|
* libevent-dev
|
||||||
* libjansson-dev
|
* libjansson-dev
|
||||||
* libjemalloc-dev
|
* libjemalloc-dev
|
||||||
|
|
23
configure.ac
23
configure.ac
|
@ -275,6 +275,21 @@ fi
|
||||||
|
|
||||||
AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
|
AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
|
||||||
|
|
||||||
|
# libev (for src)
|
||||||
|
# libev does not have pkg-config file. Check it in an old way.
|
||||||
|
LIBS_OLD=$LIBS
|
||||||
|
AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no])
|
||||||
|
if test "x${have_libev}" = "xyes"; then
|
||||||
|
AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no])
|
||||||
|
if test "x${have_libev}" = "xyes"; then
|
||||||
|
LIBEV_LIBS=-lev
|
||||||
|
LIBEV_CFLAGS=
|
||||||
|
AC_SUBST([LIBEV_LIBS])
|
||||||
|
AC_SUBST([LIBEV_CFLAGS])
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LIBS=$LIBS_OLD
|
||||||
|
|
||||||
# openssl (for src)
|
# openssl (for src)
|
||||||
PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
|
PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
|
||||||
[have_openssl=yes], [have_openssl=no])
|
[have_openssl=yes], [have_openssl=no])
|
||||||
|
@ -282,7 +297,7 @@ if test "x${have_openssl}" = "xno"; then
|
||||||
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
|
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# libevent_openssl (for src)
|
# libevent_openssl (for examples)
|
||||||
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
||||||
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
|
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
|
||||||
[have_libevent_openssl=yes], [have_libevent_openssl=no])
|
[have_libevent_openssl=yes], [have_libevent_openssl=no])
|
||||||
|
@ -375,12 +390,12 @@ if test "x${request_asio_lib}" = "xyes"; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
|
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
|
||||||
# and libevent_openssl
|
# and libev
|
||||||
enable_app=no
|
enable_app=no
|
||||||
if test "x${request_app}" != "xno" &&
|
if test "x${request_app}" != "xno" &&
|
||||||
test "x${have_zlib}" = "xyes" &&
|
test "x${have_zlib}" = "xyes" &&
|
||||||
test "x${have_openssl}" = "xyes" &&
|
test "x${have_openssl}" = "xyes" &&
|
||||||
test "x${have_libevent_openssl}" = "xyes"; then
|
test "x${have_libev}" = "xyes"; then
|
||||||
enable_app=yes
|
enable_app=yes
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -505,6 +520,7 @@ if test "x$cross_compiling" != "xyes"; then
|
||||||
fi
|
fi
|
||||||
AC_CHECK_FUNCS([ \
|
AC_CHECK_FUNCS([ \
|
||||||
_Exit \
|
_Exit \
|
||||||
|
accept4 \
|
||||||
getpwnam \
|
getpwnam \
|
||||||
memmove \
|
memmove \
|
||||||
memset \
|
memset \
|
||||||
|
@ -649,6 +665,7 @@ AC_MSG_NOTICE([summary of build options:
|
||||||
Libs:
|
Libs:
|
||||||
OpenSSL: ${have_openssl}
|
OpenSSL: ${have_openssl}
|
||||||
Libxml2: ${have_libxml2}
|
Libxml2: ${have_libxml2}
|
||||||
|
Libev: ${have_libev}
|
||||||
Libevent(SSL): ${have_libevent_openssl}
|
Libevent(SSL): ${have_libevent_openssl}
|
||||||
Spdylay: ${have_spdylay}
|
Spdylay: ${have_spdylay}
|
||||||
Jansson: ${have_jansson}
|
Jansson: ${have_jansson}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
HEADERS = [
|
||||||
|
':authority',
|
||||||
|
':method',
|
||||||
|
':path',
|
||||||
|
':scheme',
|
||||||
|
':status',
|
||||||
|
':host', # for spdy
|
||||||
|
'expect',
|
||||||
|
'host',
|
||||||
|
'if-modified-since',
|
||||||
|
"te",
|
||||||
|
"cookie",
|
||||||
|
"http2-settings",
|
||||||
|
"server",
|
||||||
|
"via",
|
||||||
|
"x-forwarded-for",
|
||||||
|
"x-forwarded-proto",
|
||||||
|
"alt-svc",
|
||||||
|
"content-length",
|
||||||
|
"location",
|
||||||
|
# disallowed h1 headers
|
||||||
|
'connection',
|
||||||
|
'keep-alive',
|
||||||
|
'proxy-connection',
|
||||||
|
'transfer-encoding',
|
||||||
|
'upgrade'
|
||||||
|
]
|
||||||
|
|
||||||
|
def to_enum_hd(k):
|
||||||
|
res = 'HD_'
|
||||||
|
for c in k.upper():
|
||||||
|
if c == ':' or c == '-':
|
||||||
|
res += '_'
|
||||||
|
continue
|
||||||
|
res += c
|
||||||
|
return res
|
||||||
|
|
||||||
|
def build_header(headers):
|
||||||
|
res = {}
|
||||||
|
for k in headers:
|
||||||
|
size = len(k)
|
||||||
|
if size not in res:
|
||||||
|
res[size] = {}
|
||||||
|
ent = res[size]
|
||||||
|
c = k[-1]
|
||||||
|
if c not in ent:
|
||||||
|
ent[c] = []
|
||||||
|
ent[c].append(k)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def gen_enum():
|
||||||
|
print '''\
|
||||||
|
enum {'''
|
||||||
|
for k in sorted(HEADERS):
|
||||||
|
print '''\
|
||||||
|
{},'''.format(to_enum_hd(k))
|
||||||
|
print '''\
|
||||||
|
HD_MAXIDX,
|
||||||
|
};'''
|
||||||
|
|
||||||
|
def gen_index_header():
|
||||||
|
print '''\
|
||||||
|
int lookup_token(const uint8_t *name, size_t namelen) {
|
||||||
|
switch (namelen) {'''
|
||||||
|
b = build_header(HEADERS)
|
||||||
|
for size in sorted(b.keys()):
|
||||||
|
ents = b[size]
|
||||||
|
print '''\
|
||||||
|
case {}:'''.format(size)
|
||||||
|
print '''\
|
||||||
|
switch (name[namelen - 1]) {'''
|
||||||
|
for c in sorted(ents.keys()):
|
||||||
|
headers = sorted(ents[c])
|
||||||
|
print '''\
|
||||||
|
case '{}':'''.format(c)
|
||||||
|
for k in headers:
|
||||||
|
print '''\
|
||||||
|
if (util::streq("{}", name, {})) {{
|
||||||
|
return {};
|
||||||
|
}}'''.format(k[:-1], size - 1, to_enum_hd(k))
|
||||||
|
print '''\
|
||||||
|
break;'''
|
||||||
|
print '''\
|
||||||
|
}
|
||||||
|
break;'''
|
||||||
|
print '''\
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}'''
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
gen_enum()
|
||||||
|
print ''
|
||||||
|
gen_index_header()
|
File diff suppressed because it is too large
Load Diff
|
@ -39,22 +39,12 @@
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "nghttp2_buf.h"
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
namespace nghttp2 {
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
@ -65,8 +55,8 @@ struct Config {
|
||||||
std::string private_key_file;
|
std::string private_key_file;
|
||||||
std::string cert_file;
|
std::string cert_file;
|
||||||
std::string dh_param_file;
|
std::string dh_param_file;
|
||||||
timeval stream_read_timeout;
|
ev_tstamp stream_read_timeout;
|
||||||
timeval stream_write_timeout;
|
ev_tstamp stream_write_timeout;
|
||||||
nghttp2_option *session_option;
|
nghttp2_option *session_option;
|
||||||
void *data_ptr;
|
void *data_ptr;
|
||||||
size_t padding;
|
size_t padding;
|
||||||
|
@ -88,11 +78,12 @@ class Http2Handler;
|
||||||
struct Stream {
|
struct Stream {
|
||||||
Headers headers;
|
Headers headers;
|
||||||
Http2Handler *handler;
|
Http2Handler *handler;
|
||||||
event *rtimer;
|
ev_timer rtimer;
|
||||||
event *wtimer;
|
ev_timer wtimer;
|
||||||
int64_t body_left;
|
int64_t body_left;
|
||||||
int32_t stream_id;
|
int32_t stream_id;
|
||||||
int file;
|
int file;
|
||||||
|
int hdidx[http2::HD_MAXIDX];
|
||||||
Stream(Http2Handler *handler, int32_t stream_id);
|
Stream(Http2Handler *handler, int32_t stream_id);
|
||||||
~Stream();
|
~Stream();
|
||||||
};
|
};
|
||||||
|
@ -106,7 +97,6 @@ public:
|
||||||
|
|
||||||
void remove_self();
|
void remove_self();
|
||||||
int setup_bev();
|
int setup_bev();
|
||||||
int send();
|
|
||||||
int on_read();
|
int on_read();
|
||||||
int on_write();
|
int on_write();
|
||||||
int on_connect();
|
int on_connect();
|
||||||
|
@ -137,14 +127,29 @@ public:
|
||||||
void remove_settings_timer();
|
void remove_settings_timer();
|
||||||
void terminate_session(uint32_t error_code);
|
void terminate_session(uint32_t error_code);
|
||||||
|
|
||||||
|
int fill_wb();
|
||||||
|
|
||||||
|
int read_clear();
|
||||||
|
int write_clear();
|
||||||
|
int tls_handshake();
|
||||||
|
int read_tls();
|
||||||
|
int write_tls();
|
||||||
|
|
||||||
|
struct ev_loop *get_loop() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ev_io wev_;
|
||||||
|
ev_io rev_;
|
||||||
|
ev_timer settings_timerev_;
|
||||||
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
||||||
|
RingBuf<65536> wb_;
|
||||||
|
std::function<int(Http2Handler &)> read_, write_;
|
||||||
int64_t session_id_;
|
int64_t session_id_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
Sessions *sessions_;
|
Sessions *sessions_;
|
||||||
SSL *ssl_;
|
SSL *ssl_;
|
||||||
bufferevent *bev_;
|
const uint8_t *data_pending_;
|
||||||
event *settings_timerev_;
|
size_t data_pendinglen_;
|
||||||
int fd_;
|
int fd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ AM_CPPFLAGS = \
|
||||||
-I$(top_srcdir)/third-party \
|
-I$(top_srcdir)/third-party \
|
||||||
@LIBSPDYLAY_CFLAGS@ \
|
@LIBSPDYLAY_CFLAGS@ \
|
||||||
@XML_CPPFLAGS@ \
|
@XML_CPPFLAGS@ \
|
||||||
@LIBEVENT_OPENSSL_CFLAGS@ \
|
@LIBEV_CFLAGS@ \
|
||||||
@OPENSSL_CFLAGS@ \
|
@OPENSSL_CFLAGS@ \
|
||||||
@JANSSON_CFLAGS@ \
|
@JANSSON_CFLAGS@ \
|
||||||
@ZLIB_CFLAGS@ \
|
@ZLIB_CFLAGS@ \
|
||||||
|
@ -45,7 +45,7 @@ AM_LDFLAGS = \
|
||||||
@JEMALLOC_LIBS@ \
|
@JEMALLOC_LIBS@ \
|
||||||
@LIBSPDYLAY_LIBS@ \
|
@LIBSPDYLAY_LIBS@ \
|
||||||
@XML_LIBS@ \
|
@XML_LIBS@ \
|
||||||
@LIBEVENT_OPENSSL_LIBS@ \
|
@LIBEV_LIBS@ \
|
||||||
@OPENSSL_LIBS@ \
|
@OPENSSL_LIBS@ \
|
||||||
@JANSSON_LIBS@ \
|
@JANSSON_LIBS@ \
|
||||||
@ZLIB_LIBS@ \
|
@ZLIB_LIBS@ \
|
||||||
|
@ -59,9 +59,9 @@ if ENABLE_APP
|
||||||
|
|
||||||
bin_PROGRAMS += nghttp nghttpd nghttpx
|
bin_PROGRAMS += nghttp nghttpd nghttpx
|
||||||
|
|
||||||
HELPER_OBJECTS = util.cc libevent_util.cc \
|
HELPER_OBJECTS = util.cc \
|
||||||
http2.cc timegm.c app_helper.cc nghttp2_gzip.c
|
http2.cc timegm.c app_helper.cc nghttp2_gzip.c
|
||||||
HELPER_HFILES = util.h libevent_util.h \
|
HELPER_HFILES = util.h \
|
||||||
http2.h timegm.h app_helper.h nghttp2_config.h \
|
http2.h timegm.h app_helper.h nghttp2_config.h \
|
||||||
nghttp2_gzip.h
|
nghttp2_gzip.h
|
||||||
|
|
||||||
|
@ -78,11 +78,12 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
|
||||||
|
|
||||||
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
||||||
ssl.cc ssl.h \
|
ssl.cc ssl.h \
|
||||||
HttpServer.cc HttpServer.h
|
HttpServer.cc HttpServer.h \
|
||||||
|
ringbuf.h
|
||||||
|
|
||||||
bin_PROGRAMS += h2load
|
bin_PROGRAMS += h2load
|
||||||
|
|
||||||
h2load_SOURCES = util.cc util.h libevent_util.cc libevent_util.h \
|
h2load_SOURCES = util.cc util.h \
|
||||||
http2.cc http2.h h2load.cc h2load.h \
|
http2.cc http2.h h2load.cc h2load.h \
|
||||||
timegm.c timegm.h \
|
timegm.c timegm.h \
|
||||||
ssl.cc ssl.h \
|
ssl.cc ssl.h \
|
||||||
|
@ -95,31 +96,32 @@ endif # HAVE_SPDYLAY
|
||||||
|
|
||||||
NGHTTPX_SRCS = \
|
NGHTTPX_SRCS = \
|
||||||
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
||||||
libevent_util.cc libevent_util.h \
|
|
||||||
app_helper.cc app_helper.h \
|
app_helper.cc app_helper.h \
|
||||||
ssl.cc ssl.h \
|
ssl.cc ssl.h \
|
||||||
shrpx_config.cc shrpx_config.h \
|
shrpx_config.cc shrpx_config.h \
|
||||||
shrpx_error.h \
|
shrpx_error.h \
|
||||||
|
shrpx_accept_handler.cc shrpx_accept_handler.h \
|
||||||
shrpx_listen_handler.cc shrpx_listen_handler.h \
|
shrpx_listen_handler.cc shrpx_listen_handler.h \
|
||||||
shrpx_client_handler.cc shrpx_client_handler.h \
|
shrpx_client_handler.cc shrpx_client_handler.h \
|
||||||
shrpx_upstream.h \
|
shrpx_upstream.h \
|
||||||
shrpx_http2_upstream.cc shrpx_http2_upstream.h \
|
shrpx_http2_upstream.cc shrpx_http2_upstream.h \
|
||||||
shrpx_https_upstream.cc shrpx_https_upstream.h \
|
shrpx_https_upstream.cc shrpx_https_upstream.h \
|
||||||
shrpx_downstream_queue.cc shrpx_downstream_queue.h \
|
|
||||||
shrpx_downstream.cc shrpx_downstream.h \
|
shrpx_downstream.cc shrpx_downstream.h \
|
||||||
shrpx_downstream_connection.cc shrpx_downstream_connection.h \
|
shrpx_downstream_connection.cc shrpx_downstream_connection.h \
|
||||||
shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \
|
shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \
|
||||||
shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \
|
shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \
|
||||||
shrpx_http2_session.cc shrpx_http2_session.h \
|
shrpx_http2_session.cc shrpx_http2_session.h \
|
||||||
|
shrpx_downstream_queue.cc shrpx_downstream_queue.h \
|
||||||
shrpx_log.cc shrpx_log.h \
|
shrpx_log.cc shrpx_log.h \
|
||||||
shrpx_http.cc shrpx_http.h \
|
shrpx_http.cc shrpx_http.h \
|
||||||
shrpx_io_control.cc shrpx_io_control.h \
|
shrpx_io_control.cc shrpx_io_control.h \
|
||||||
shrpx_ssl.cc shrpx_ssl.h \
|
shrpx_ssl.cc shrpx_ssl.h \
|
||||||
shrpx_thread_event_receiver.cc shrpx_thread_event_receiver.h \
|
|
||||||
shrpx_worker.cc shrpx_worker.h \
|
shrpx_worker.cc shrpx_worker.h \
|
||||||
shrpx_worker_config.cc shrpx_worker_config.h \
|
shrpx_worker_config.cc shrpx_worker_config.h \
|
||||||
shrpx_connect_blocker.cc shrpx_connect_blocker.h \
|
shrpx_connect_blocker.cc shrpx_connect_blocker.h \
|
||||||
shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h
|
shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \
|
||||||
|
shrpx_rate_limit.cc shrpx_rate_limit.h \
|
||||||
|
ringbuf.h memchunk.h
|
||||||
|
|
||||||
if HAVE_SPDYLAY
|
if HAVE_SPDYLAY
|
||||||
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
||||||
|
@ -141,7 +143,8 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
||||||
http2_test.cc http2_test.h \
|
http2_test.cc http2_test.h \
|
||||||
util_test.cc util_test.h \
|
util_test.cc util_test.h \
|
||||||
nghttp2_gzip_test.c nghttp2_gzip_test.h \
|
nghttp2_gzip_test.c nghttp2_gzip_test.h \
|
||||||
nghttp2_gzip.c nghttp2_gzip.h
|
nghttp2_gzip.c nghttp2_gzip.h \
|
||||||
|
ringbuf_test.cc ringbuf_test.h
|
||||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
|
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
|
||||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||||
nghttpx_unittest_LDFLAGS = ${AM_LDFLAGS} \
|
nghttpx_unittest_LDFLAGS = ${AM_LDFLAGS} \
|
||||||
|
|
589
src/h2load.cc
589
src/h2load.cc
|
@ -42,8 +42,6 @@
|
||||||
#include <spdylay/spdylay.h>
|
#include <spdylay/spdylay.h>
|
||||||
#endif // HAVE_SPDYLAY
|
#endif // HAVE_SPDYLAY
|
||||||
|
|
||||||
#include <event2/bufferevent_ssl.h>
|
|
||||||
|
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
|
|
||||||
|
@ -70,18 +68,6 @@ Config::~Config() { freeaddrinfo(addrs); }
|
||||||
|
|
||||||
Config config;
|
Config config;
|
||||||
|
|
||||||
namespace {
|
|
||||||
void eventcb(bufferevent *bev, short events, void *ptr);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void readcb(bufferevent *bev, void *ptr);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void writecb(bufferevent *bev, void *ptr);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void debug(const char *format, ...) {
|
void debug(const char *format, ...) {
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
|
@ -94,46 +80,113 @@ void debug(const char *format, ...) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void debug_nextproto_error() {
|
||||||
|
#ifdef HAVE_SPDYLAY
|
||||||
|
debug("no supported protocol was negotiated, expected: %s, "
|
||||||
|
"spdy/2, spdy/3, spdy/3.1\n",
|
||||||
|
NGHTTP2_PROTO_VERSION_ID);
|
||||||
|
#else // !HAVE_SPDYLAY
|
||||||
|
debug("no supported protocol was negotiated, expected: %s\n",
|
||||||
|
NGHTTP2_PROTO_VERSION_ID);
|
||||||
|
#endif // !HAVE_SPDYLAY
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Stream::Stream() : status_success(-1) {}
|
Stream::Stream() : status_success(-1) {}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto client = static_cast<Client *>(w->data);
|
||||||
|
if (client->do_read() != 0) {
|
||||||
|
client->fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto client = static_cast<Client *>(w->data);
|
||||||
|
auto rv = client->do_write();
|
||||||
|
if (rv == Client::ERR_CONNECT_FAIL) {
|
||||||
|
client->disconnect();
|
||||||
|
rv = client->connect();
|
||||||
|
if (rv != 0) {
|
||||||
|
client->fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rv != 0) {
|
||||||
|
client->fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Client::Client(Worker *worker, size_t req_todo)
|
Client::Client(Worker *worker, size_t req_todo)
|
||||||
: worker(worker), ssl(nullptr), bev(nullptr), next_addr(config.addrs),
|
: worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
|
||||||
reqidx(0), state(CLIENT_IDLE), req_todo(req_todo), req_started(0),
|
state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0),
|
||||||
req_done(0) {}
|
fd(-1) {
|
||||||
|
ev_io_init(&wev, writecb, 0, EV_WRITE);
|
||||||
|
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||||
|
|
||||||
|
wev.data = this;
|
||||||
|
rev.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
Client::~Client() { disconnect(); }
|
Client::~Client() { disconnect(); }
|
||||||
|
|
||||||
|
int Client::do_read() { return readfn(*this); }
|
||||||
|
int Client::do_write() { return writefn(*this); }
|
||||||
|
|
||||||
int Client::connect() {
|
int Client::connect() {
|
||||||
if (config.scheme == "https") {
|
|
||||||
ssl = SSL_new(worker->ssl_ctx);
|
|
||||||
|
|
||||||
auto config = worker->config;
|
|
||||||
|
|
||||||
if (!util::numeric_host(config->host.c_str())) {
|
|
||||||
SSL_set_tlsext_host_name(ssl, config->host.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
bev = bufferevent_openssl_socket_new(worker->evbase, -1, ssl,
|
|
||||||
BUFFEREVENT_SSL_CONNECTING,
|
|
||||||
BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
} else {
|
|
||||||
bev = bufferevent_socket_new(worker->evbase, -1, BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
}
|
|
||||||
|
|
||||||
int rv = -1;
|
|
||||||
while (next_addr) {
|
while (next_addr) {
|
||||||
rv = bufferevent_socket_connect(bev, next_addr->ai_addr,
|
auto addr = next_addr;
|
||||||
next_addr->ai_addrlen);
|
|
||||||
next_addr = next_addr->ai_next;
|
next_addr = next_addr->ai_next;
|
||||||
if (rv == 0) {
|
fd = util::create_nonblock_socket(addr->ai_family);
|
||||||
break;
|
if (fd == -1) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if (config.scheme == "https") {
|
||||||
|
ssl = SSL_new(worker->ssl_ctx);
|
||||||
|
|
||||||
|
auto config = worker->config;
|
||||||
|
|
||||||
|
if (!util::numeric_host(config->host.c_str())) {
|
||||||
|
SSL_set_tlsext_host_name(ssl, config->host.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_set_fd(ssl, fd);
|
||||||
|
SSL_set_connect_state(ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
|
||||||
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
|
if (ssl) {
|
||||||
|
SSL_free(ssl);
|
||||||
|
ssl = nullptr;
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (rv != 0) {
|
|
||||||
|
if (fd == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bufferevent_enable(bev, EV_READ);
|
|
||||||
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
|
writefn = &Client::connected;
|
||||||
|
|
||||||
|
on_readfn = &Client::on_read;
|
||||||
|
on_writefn = &Client::on_write;
|
||||||
|
|
||||||
|
ev_io_set(&rev, fd, EV_READ);
|
||||||
|
ev_io_set(&wev, fd, EV_WRITE);
|
||||||
|
|
||||||
|
ev_io_start(worker->loop, &wev);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,27 +197,21 @@ void Client::fail() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::disconnect() {
|
void Client::disconnect() {
|
||||||
int fd = -1;
|
|
||||||
streams.clear();
|
streams.clear();
|
||||||
session.reset();
|
session.reset();
|
||||||
state = CLIENT_IDLE;
|
state = CLIENT_IDLE;
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
ev_io_stop(worker->loop, &rev);
|
||||||
if (ssl) {
|
if (ssl) {
|
||||||
fd = SSL_get_fd(ssl);
|
|
||||||
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
|
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
|
||||||
SSL_shutdown(ssl);
|
SSL_shutdown(ssl);
|
||||||
}
|
|
||||||
if (bev) {
|
|
||||||
bufferevent_disable(bev, EV_READ | EV_WRITE);
|
|
||||||
bufferevent_free(bev);
|
|
||||||
bev = nullptr;
|
|
||||||
}
|
|
||||||
if (ssl) {
|
|
||||||
SSL_free(ssl);
|
SSL_free(ssl);
|
||||||
ssl = nullptr;
|
ssl = nullptr;
|
||||||
}
|
}
|
||||||
if (fd != -1) {
|
if (fd != -1) {
|
||||||
shutdown(fd, SHUT_WR);
|
shutdown(fd, SHUT_WR);
|
||||||
close(fd);
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +372,74 @@ void Client::on_stream_close(int32_t stream_id, bool success) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Client::noop() { return 0; }
|
||||||
|
|
||||||
int Client::on_connect() {
|
int Client::on_connect() {
|
||||||
|
if (ssl) {
|
||||||
|
report_tls_info();
|
||||||
|
|
||||||
|
const unsigned char *next_proto = nullptr;
|
||||||
|
unsigned int next_proto_len;
|
||||||
|
SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
if (next_proto) {
|
||||||
|
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||||
|
session = util::make_unique<Http2Session>(this);
|
||||||
|
} else {
|
||||||
|
#ifdef HAVE_SPDYLAY
|
||||||
|
auto spdy_version =
|
||||||
|
spdylay_npn_get_version(next_proto, next_proto_len);
|
||||||
|
if (spdy_version) {
|
||||||
|
session = util::make_unique<SpdySession>(this, spdy_version);
|
||||||
|
} else {
|
||||||
|
debug_nextproto_error();
|
||||||
|
fail();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#else // !HAVE_SPDYLAY
|
||||||
|
debug_nextproto_error();
|
||||||
|
fail();
|
||||||
|
return -1;
|
||||||
|
#endif // !HAVE_SPDYLAY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||||
|
SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
|
||||||
|
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||||
|
break;
|
||||||
|
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!next_proto) {
|
||||||
|
debug_nextproto_error();
|
||||||
|
fail();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (config.no_tls_proto) {
|
||||||
|
case Config::PROTO_HTTP2:
|
||||||
|
session = util::make_unique<Http2Session>(this);
|
||||||
|
break;
|
||||||
|
#ifdef HAVE_SPDYLAY
|
||||||
|
case Config::PROTO_SPDY2:
|
||||||
|
session = util::make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY2);
|
||||||
|
break;
|
||||||
|
case Config::PROTO_SPDY3:
|
||||||
|
session = util::make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3);
|
||||||
|
break;
|
||||||
|
case Config::PROTO_SPDY3_1:
|
||||||
|
session = util::make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3_1);
|
||||||
|
break;
|
||||||
|
#endif // HAVE_SPDYLAY
|
||||||
|
default:
|
||||||
|
// unreachable
|
||||||
|
assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state = CLIENT_CONNECTED;
|
||||||
|
|
||||||
session->on_connect();
|
session->on_connect();
|
||||||
|
|
||||||
auto nreq =
|
auto nreq =
|
||||||
|
@ -334,25 +448,235 @@ int Client::on_connect() {
|
||||||
for (; nreq > 0; --nreq) {
|
for (; nreq > 0; --nreq) {
|
||||||
submit_request();
|
submit_request();
|
||||||
}
|
}
|
||||||
return session->on_write();
|
|
||||||
|
signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::on_read() {
|
int Client::on_net_error() {
|
||||||
ssize_t rv = session->on_read();
|
if (state == CLIENT_IDLE) {
|
||||||
if (rv < 0) {
|
disconnect();
|
||||||
|
if (connect() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug("error/eof\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::on_read(const uint8_t *data, size_t len) {
|
||||||
|
auto rv = session->on_read(data, len);
|
||||||
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
worker->stats.bytes_total += rv;
|
worker->stats.bytes_total += len;
|
||||||
|
signal_write();
|
||||||
return on_write();
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Client::on_write() { return session->on_write(); }
|
int Client::on_write() {
|
||||||
|
if (session->on_write() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::read_clear() {
|
||||||
|
uint8_t buf[8192];
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
ssize_t nread;
|
||||||
|
while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
if (nread == -1) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return on_net_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (on_read(buf, nread) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::write_clear() {
|
||||||
|
for (;;) {
|
||||||
|
if (wb.rleft() > 0) {
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = wb.riovec(iov);
|
||||||
|
|
||||||
|
ssize_t nwrite;
|
||||||
|
while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
if (nwrite == -1) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
ev_io_start(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
wb.drain(nwrite);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (on_write() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (wb.rleft() == 0) {
|
||||||
|
wb.reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::connected() {
|
||||||
|
if (!util::check_socket_connected(fd)) {
|
||||||
|
return ERR_CONNECT_FAIL;
|
||||||
|
}
|
||||||
|
ev_io_start(worker->loop, &rev);
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
if (ssl) {
|
||||||
|
readfn = &Client::tls_handshake;
|
||||||
|
writefn = &Client::tls_handshake;
|
||||||
|
|
||||||
|
return do_write();
|
||||||
|
}
|
||||||
|
|
||||||
|
readfn = &Client::read_clear;
|
||||||
|
writefn = &Client::write_clear;
|
||||||
|
|
||||||
|
if (on_connect() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::tls_handshake() {
|
||||||
|
auto rv = SSL_do_handshake(ssl);
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
ev_io_start(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
readfn = &Client::read_tls;
|
||||||
|
writefn = &Client::write_tls;
|
||||||
|
|
||||||
|
if (on_connect() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::read_tls() {
|
||||||
|
uint8_t buf[8192];
|
||||||
|
for (;;) {
|
||||||
|
auto rv = SSL_read(ssl, buf, sizeof(buf));
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
ev_io_start(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (on_read(buf, rv) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Client::write_tls() {
|
||||||
|
for (;;) {
|
||||||
|
if (wb.rleft() > 0) {
|
||||||
|
const void *p;
|
||||||
|
size_t len;
|
||||||
|
std::tie(p, len) = wb.get();
|
||||||
|
|
||||||
|
auto rv = SSL_write(ssl, p, len);
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
ev_io_start(worker->loop, &wev);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wb.drain(rv);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (on_write() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (wb.rleft() == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_io_stop(worker->loop, &wev);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::signal_write() { ev_io_start(worker->loop, &wev); }
|
||||||
|
|
||||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||||
Config *config)
|
Config *config)
|
||||||
: stats{0}, evbase(event_base_new()), ssl_ctx(ssl_ctx), config(config),
|
: stats{0}, loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config), id(id),
|
||||||
id(id), tls_info_report_done(false) {
|
tls_info_report_done(false) {
|
||||||
stats.req_todo = req_todo;
|
stats.req_todo = req_todo;
|
||||||
progress_interval = std::max((size_t)1, req_todo / 10);
|
progress_interval = std::max((size_t)1, req_todo / 10);
|
||||||
|
|
||||||
|
@ -369,7 +693,12 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker::~Worker() { event_base_free(evbase); }
|
Worker::~Worker() {
|
||||||
|
// first clear clients so that io watchers are stopped before
|
||||||
|
// destructing ev_loop.
|
||||||
|
clients.clear();
|
||||||
|
ev_loop_destroy(loop);
|
||||||
|
}
|
||||||
|
|
||||||
void Worker::run() {
|
void Worker::run() {
|
||||||
for (auto &client : clients) {
|
for (auto &client : clients) {
|
||||||
|
@ -378,145 +707,9 @@ void Worker::run() {
|
||||||
client->fail();
|
client->fail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event_base_loop(evbase, 0);
|
ev_run(loop, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
void debug_nextproto_error() {
|
|
||||||
#ifdef HAVE_SPDYLAY
|
|
||||||
debug("no supported protocol was negotiated, expected: %s, "
|
|
||||||
"spdy/2, spdy/3, spdy/3.1\n",
|
|
||||||
NGHTTP2_PROTO_VERSION_ID);
|
|
||||||
#else // !HAVE_SPDYLAY
|
|
||||||
debug("no supported protocol was negotiated, expected: %s\n",
|
|
||||||
NGHTTP2_PROTO_VERSION_ID);
|
|
||||||
#endif // !HAVE_SPDYLAY
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void eventcb(bufferevent *bev, short events, void *ptr) {
|
|
||||||
int rv;
|
|
||||||
auto client = static_cast<Client *>(ptr);
|
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
|
||||||
if (client->ssl) {
|
|
||||||
client->report_tls_info();
|
|
||||||
|
|
||||||
const unsigned char *next_proto = nullptr;
|
|
||||||
unsigned int next_proto_len;
|
|
||||||
SSL_get0_next_proto_negotiated(client->ssl, &next_proto, &next_proto_len);
|
|
||||||
for (int i = 0; i < 2; ++i) {
|
|
||||||
if (next_proto) {
|
|
||||||
if (util::check_h2_is_selected(next_proto, next_proto_len)) {
|
|
||||||
client->session = util::make_unique<Http2Session>(client);
|
|
||||||
} else {
|
|
||||||
#ifdef HAVE_SPDYLAY
|
|
||||||
auto spdy_version =
|
|
||||||
spdylay_npn_get_version(next_proto, next_proto_len);
|
|
||||||
if (spdy_version) {
|
|
||||||
client->session =
|
|
||||||
util::make_unique<SpdySession>(client, spdy_version);
|
|
||||||
} else {
|
|
||||||
debug_nextproto_error();
|
|
||||||
client->fail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#else // !HAVE_SPDYLAY
|
|
||||||
debug_nextproto_error();
|
|
||||||
client->fail();
|
|
||||||
return;
|
|
||||||
#endif // !HAVE_SPDYLAY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
||||||
SSL_get0_alpn_selected(client->ssl, &next_proto, &next_proto_len);
|
|
||||||
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
|
||||||
break;
|
|
||||||
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!next_proto) {
|
|
||||||
debug_nextproto_error();
|
|
||||||
client->fail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (config.no_tls_proto) {
|
|
||||||
case Config::PROTO_HTTP2:
|
|
||||||
client->session = util::make_unique<Http2Session>(client);
|
|
||||||
break;
|
|
||||||
#ifdef HAVE_SPDYLAY
|
|
||||||
case Config::PROTO_SPDY2:
|
|
||||||
client->session =
|
|
||||||
util::make_unique<SpdySession>(client, SPDYLAY_PROTO_SPDY2);
|
|
||||||
break;
|
|
||||||
case Config::PROTO_SPDY3:
|
|
||||||
client->session =
|
|
||||||
util::make_unique<SpdySession>(client, SPDYLAY_PROTO_SPDY3);
|
|
||||||
break;
|
|
||||||
case Config::PROTO_SPDY3_1:
|
|
||||||
client->session =
|
|
||||||
util::make_unique<SpdySession>(client, SPDYLAY_PROTO_SPDY3_1);
|
|
||||||
break;
|
|
||||||
#endif // HAVE_SPDYLAY
|
|
||||||
default:
|
|
||||||
// unreachable
|
|
||||||
assert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int fd = bufferevent_getfd(bev);
|
|
||||||
int val = 1;
|
|
||||||
(void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
|
|
||||||
reinterpret_cast<char *>(&val), sizeof(val));
|
|
||||||
client->state = CLIENT_CONNECTED;
|
|
||||||
client->on_connect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (events & BEV_EVENT_EOF) {
|
|
||||||
client->fail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
|
||||||
if (client->state == CLIENT_IDLE) {
|
|
||||||
client->disconnect();
|
|
||||||
rv = client->connect();
|
|
||||||
if (rv == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug("error/eof\n");
|
|
||||||
client->fail();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void readcb(bufferevent *bev, void *ptr) {
|
|
||||||
int rv;
|
|
||||||
auto client = static_cast<Client *>(ptr);
|
|
||||||
rv = client->on_read();
|
|
||||||
if (rv != 0) {
|
|
||||||
client->fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void writecb(bufferevent *bev, void *ptr) {
|
|
||||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int rv;
|
|
||||||
auto client = static_cast<Client *>(ptr);
|
|
||||||
rv = client->on_write();
|
|
||||||
if (rv != 0) {
|
|
||||||
client->fail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void resolve_host() {
|
void resolve_host() {
|
||||||
int rv;
|
int rv;
|
||||||
|
|
38
src/h2load.h
38
src/h2load.h
|
@ -38,12 +38,14 @@
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/event.h>
|
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace h2load {
|
namespace h2load {
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ struct Client;
|
||||||
struct Worker {
|
struct Worker {
|
||||||
std::vector<std::unique_ptr<Client>> clients;
|
std::vector<std::unique_ptr<Client>> clients;
|
||||||
Stats stats;
|
Stats stats;
|
||||||
event_base *evbase;
|
struct ev_loop *loop;
|
||||||
SSL_CTX *ssl_ctx;
|
SSL_CTX *ssl_ctx;
|
||||||
Config *config;
|
Config *config;
|
||||||
size_t progress_interval;
|
size_t progress_interval;
|
||||||
|
@ -128,9 +130,13 @@ struct Stream {
|
||||||
struct Client {
|
struct Client {
|
||||||
std::unordered_map<int32_t, Stream> streams;
|
std::unordered_map<int32_t, Stream> streams;
|
||||||
std::unique_ptr<Session> session;
|
std::unique_ptr<Session> session;
|
||||||
|
ev_io wev;
|
||||||
|
ev_io rev;
|
||||||
|
std::function<int(Client &)> readfn, writefn;
|
||||||
|
std::function<int(Client &, const uint8_t *, size_t)> on_readfn;
|
||||||
|
std::function<int(Client &)> on_writefn;
|
||||||
Worker *worker;
|
Worker *worker;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
bufferevent *bev;
|
|
||||||
addrinfo *next_addr;
|
addrinfo *next_addr;
|
||||||
size_t reqidx;
|
size_t reqidx;
|
||||||
ClientState state;
|
ClientState state;
|
||||||
|
@ -140,6 +146,10 @@ struct Client {
|
||||||
size_t req_started;
|
size_t req_started;
|
||||||
// The number of requests this client has done so far.
|
// The number of requests this client has done so far.
|
||||||
size_t req_done;
|
size_t req_done;
|
||||||
|
int fd;
|
||||||
|
RingBuf<65536> wb;
|
||||||
|
|
||||||
|
enum { ERR_CONNECT_FAIL = -100 };
|
||||||
|
|
||||||
Client(Worker *worker, size_t req_todo);
|
Client(Worker *worker, size_t req_todo);
|
||||||
~Client();
|
~Client();
|
||||||
|
@ -151,13 +161,29 @@ struct Client {
|
||||||
void report_progress();
|
void report_progress();
|
||||||
void report_tls_info();
|
void report_tls_info();
|
||||||
void terminate_session();
|
void terminate_session();
|
||||||
int on_connect();
|
|
||||||
int on_read();
|
int do_read();
|
||||||
|
int do_write();
|
||||||
|
|
||||||
|
int connected();
|
||||||
|
int read_clear();
|
||||||
|
int write_clear();
|
||||||
|
int tls_handshake();
|
||||||
|
int read_tls();
|
||||||
|
int write_tls();
|
||||||
|
|
||||||
|
int on_read(const uint8_t *data, size_t len);
|
||||||
int on_write();
|
int on_write();
|
||||||
|
int on_connect();
|
||||||
|
int on_net_error();
|
||||||
|
int noop();
|
||||||
|
|
||||||
void on_request(int32_t stream_id);
|
void on_request(int32_t stream_id);
|
||||||
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen);
|
const uint8_t *value, size_t valuelen);
|
||||||
void on_stream_close(int32_t stream_id, bool success);
|
void on_stream_close(int32_t stream_id, bool success);
|
||||||
|
|
||||||
|
void signal_write();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace h2load
|
} // namespace h2load
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
#include "h2load.h"
|
#include "h2load.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
@ -86,6 +85,20 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
||||||
|
size_t length, int flags, void *user_data) {
|
||||||
|
auto client = static_cast<Client *>(user_data);
|
||||||
|
auto &wb = client->wb;
|
||||||
|
|
||||||
|
if (wb.wleft() == 0) {
|
||||||
|
return NGHTTP2_ERR_WOULDBLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wb.write(data, length);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void Http2Session::on_connect() {
|
void Http2Session::on_connect() {
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
|
@ -108,6 +121,8 @@ void Http2Session::on_connect() {
|
||||||
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
||||||
on_header_callback);
|
on_header_callback);
|
||||||
|
|
||||||
|
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
||||||
|
|
||||||
nghttp2_session_client_new(&session_, callbacks, client_);
|
nghttp2_session_client_new(&session_, callbacks, client_);
|
||||||
|
|
||||||
nghttp2_settings_entry iv[2];
|
nghttp2_settings_entry iv[2];
|
||||||
|
@ -129,8 +144,13 @@ void Http2Session::on_connect() {
|
||||||
extra_connection_window);
|
extra_connection_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_write(client_->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
|
auto &wb = client_->wb;
|
||||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
assert(wb.wleft() >= NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||||
|
|
||||||
|
wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
|
||||||
|
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||||
|
|
||||||
|
client_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Session::submit_request() {
|
void Http2Session::submit_request() {
|
||||||
|
@ -148,66 +168,35 @@ void Http2Session::submit_request() {
|
||||||
client_->on_request(stream_id);
|
client_->on_request(stream_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t Http2Session::on_read() {
|
int Http2Session::on_read(const uint8_t *data, size_t len) {
|
||||||
int rv;
|
auto rv = nghttp2_session_mem_recv(session_, data, len);
|
||||||
size_t nread = 0;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(static_cast<size_t>(rv) == len);
|
||||||
|
|
||||||
|
if (nghttp2_session_want_read(session_) == 0 &&
|
||||||
|
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Session::on_write() {
|
int Http2Session::on_write() {
|
||||||
int rv;
|
auto rv = nghttp2_session_send(session_);
|
||||||
uint8_t buf[16384];
|
|
||||||
auto output = bufferevent_get_output(client_->bev);
|
|
||||||
util::EvbufferBuffer evbbuf(output, buf, sizeof(buf));
|
|
||||||
for (;;) {
|
|
||||||
const uint8_t *data;
|
|
||||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
|
||||||
|
|
||||||
if (datalen < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (datalen == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rv = evbbuf.add(data, datalen);
|
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv = evbbuf.flush();
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nghttp2_session_want_read(session_) == 0 &&
|
if (nghttp2_session_want_read(session_) == 0 &&
|
||||||
nghttp2_session_want_write(session_) == 0 &&
|
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||||
evbuffer_get_length(output) == 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ public:
|
||||||
virtual ~Http2Session();
|
virtual ~Http2Session();
|
||||||
virtual void on_connect();
|
virtual void on_connect();
|
||||||
virtual void submit_request();
|
virtual void submit_request();
|
||||||
virtual ssize_t on_read();
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual void terminate();
|
virtual void terminate();
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "nghttp2_config.h"
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace h2load {
|
namespace h2load {
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ public:
|
||||||
virtual void submit_request() = 0;
|
virtual void submit_request() = 0;
|
||||||
// Called when incoming bytes are available. The subclass has to
|
// Called when incoming bytes are available. The subclass has to
|
||||||
// return the number of bytes read.
|
// return the number of bytes read.
|
||||||
virtual ssize_t on_read() = 0;
|
virtual int on_read(const uint8_t *data, size_t len) = 0;
|
||||||
// Called when write is available. Returns 0 on success, otherwise
|
// Called when write is available. Returns 0 on success, otherwise
|
||||||
// return -1.
|
// return -1.
|
||||||
virtual int on_write() = 0;
|
virtual int on_write() = 0;
|
||||||
|
|
|
@ -94,14 +94,13 @@ namespace {
|
||||||
ssize_t send_callback(spdylay_session *session, const uint8_t *data,
|
ssize_t send_callback(spdylay_session *session, const uint8_t *data,
|
||||||
size_t length, int flags, void *user_data) {
|
size_t length, int flags, void *user_data) {
|
||||||
auto client = static_cast<Client *>(user_data);
|
auto client = static_cast<Client *>(user_data);
|
||||||
auto spdy_session = static_cast<SpdySession *>(client->session.get());
|
auto &wb = client->wb;
|
||||||
int rv;
|
|
||||||
|
|
||||||
rv = spdy_session->sendbuf.add(data, length);
|
if (wb.wleft() == 0) {
|
||||||
if (rv != 0) {
|
return SPDYLAY_ERR_DEFERRED;
|
||||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
}
|
||||||
return length;
|
|
||||||
|
return wb.write(data, length);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -134,6 +133,8 @@ void SpdySession::on_connect() {
|
||||||
(1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE;
|
(1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE;
|
||||||
spdylay_submit_window_update(session_, 0, delta);
|
spdylay_submit_window_update(session_, 0, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpdySession::submit_request() {
|
void SpdySession::submit_request() {
|
||||||
|
@ -147,55 +148,32 @@ void SpdySession::submit_request() {
|
||||||
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t SpdySession::on_read() {
|
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
||||||
int rv;
|
auto rv = spdylay_session_mem_recv(session_, data, len);
|
||||||
size_t nread = 0;
|
if (rv < 0) {
|
||||||
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 = spdylay_session_mem_recv(session_, mem, inputlen);
|
|
||||||
|
|
||||||
if (rv < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nread += rv;
|
|
||||||
|
|
||||||
if (evbuffer_drain(input, rv) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int SpdySession::on_write() {
|
|
||||||
int rv;
|
|
||||||
uint8_t buf[16384];
|
|
||||||
|
|
||||||
sendbuf.reset(bufferevent_get_output(client_->bev), buf, sizeof(buf));
|
|
||||||
|
|
||||||
rv = spdylay_session_send(session_);
|
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = sendbuf.flush();
|
assert(static_cast<size_t>(rv) == len);
|
||||||
|
|
||||||
|
if (spdylay_session_want_read(session_) == 0 &&
|
||||||
|
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpdySession::on_write() {
|
||||||
|
auto rv = spdylay_session_send(session_);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spdylay_session_want_read(session_) == 0 &&
|
if (spdylay_session_want_read(session_) == 0 &&
|
||||||
spdylay_session_want_write(session_) == 0 &&
|
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||||
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
#include <spdylay/spdylay.h>
|
#include <spdylay/spdylay.h>
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
namespace h2load {
|
namespace h2load {
|
||||||
|
|
||||||
|
@ -42,13 +41,11 @@ public:
|
||||||
virtual ~SpdySession();
|
virtual ~SpdySession();
|
||||||
virtual void on_connect();
|
virtual void on_connect();
|
||||||
virtual void submit_request();
|
virtual void submit_request();
|
||||||
virtual ssize_t on_read();
|
virtual int on_read(const uint8_t *data, size_t len);
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual void terminate();
|
virtual void terminate();
|
||||||
void handle_window_update(int32_t stream_id, size_t recvlen);
|
void handle_window_update(int32_t stream_id, size_t recvlen);
|
||||||
|
|
||||||
nghttp2::util::EvbufferBuffer sendbuf;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Client *client_;
|
Client *client_;
|
||||||
spdylay_session *session_;
|
spdylay_session *session_;
|
||||||
|
|
501
src/http2.cc
501
src/http2.cc
|
@ -174,138 +174,6 @@ void copy_url_component(std::string &dest, const http_parser_url *u, int field,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_http2_allowed_header(const char *name) {
|
|
||||||
return check_http2_allowed_header(reinterpret_cast<const uint8_t *>(name),
|
|
||||||
strlen(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_http2_allowed_header(const uint8_t *name, size_t namelen) {
|
|
||||||
return !util::strieq("connection", name, namelen) &&
|
|
||||||
!util::strieq("host", name, namelen) &&
|
|
||||||
!util::strieq("keep-alive", name, namelen) &&
|
|
||||||
!util::strieq("proxy-connection", name, namelen) &&
|
|
||||||
!util::strieq("te", name, namelen) &&
|
|
||||||
!util::strieq("transfer-encoding", name, namelen) &&
|
|
||||||
!util::strieq("upgrade", name, namelen);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char *DISALLOWED_HD[] = {
|
|
||||||
"connection", "keep-alive", "proxy-connection",
|
|
||||||
"te", "transfer-encoding", "upgrade",
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto DISALLOWED_HDLEN = util::array_size(DISALLOWED_HD);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char *REQUEST_PSEUDO_HD[] = {
|
|
||||||
":authority", ":method", ":path", ":scheme",
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto REQUEST_PSEUDO_HDLEN = util::array_size(REQUEST_PSEUDO_HD);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char *RESPONSE_PSEUDO_HD[] = {
|
|
||||||
":status",
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto RESPONSE_PSEUDO_HDLEN = util::array_size(RESPONSE_PSEUDO_HD);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char *IGN_HD[] = {
|
|
||||||
"connection", "http2-settings", "keep-alive", "proxy-connection",
|
|
||||||
"server", "te", "transfer-encoding", "upgrade",
|
|
||||||
"via", "x-forwarded-for", "x-forwarded-proto",
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto IGN_HDLEN = util::array_size(IGN_HD);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
const char *HTTP1_IGN_HD[] = {
|
|
||||||
"connection", "cookie", "http2-settings", "keep-alive",
|
|
||||||
"proxy-connection", "server", "upgrade", "via",
|
|
||||||
"x-forwarded-for", "x-forwarded-proto",
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto HTTP1_IGN_HDLEN = util::array_size(HTTP1_IGN_HD);
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs) {
|
|
||||||
if (lhs.name.c_str()[0] == ':') {
|
|
||||||
if (rhs.name.c_str()[0] != ':') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (rhs.name.c_str()[0] == ':') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lhs.name < rhs.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_http2_headers(const Headers &nva) {
|
|
||||||
for (size_t i = 0; i < DISALLOWED_HDLEN; ++i) {
|
|
||||||
if (std::binary_search(std::begin(nva), std::end(nva),
|
|
||||||
Header(DISALLOWED_HD[i], ""), name_less)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_http2_request_headers(const Headers &nva) {
|
|
||||||
return check_http2_headers(nva);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_http2_response_headers(const Headers &nva) {
|
|
||||||
return check_http2_headers(nva);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
template <typename InputIterator>
|
|
||||||
bool check_pseudo_header(const uint8_t *name, size_t namelen,
|
|
||||||
InputIterator allowed_first,
|
|
||||||
InputIterator allowed_last) {
|
|
||||||
for (auto i = allowed_first; i != allowed_last; ++i) {
|
|
||||||
if (util::streq(*i, name, namelen)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen) {
|
|
||||||
return check_pseudo_header(name, namelen, REQUEST_PSEUDO_HD,
|
|
||||||
REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen) {
|
|
||||||
return check_pseudo_header(name, namelen, RESPONSE_PSEUDO_HD,
|
|
||||||
RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
void normalize_headers(Headers &nva) {
|
|
||||||
for (auto &kv : nva) {
|
|
||||||
util::inp_strlower(kv.name);
|
|
||||||
}
|
|
||||||
std::stable_sort(std::begin(nva), std::end(nva), name_less);
|
|
||||||
}
|
|
||||||
|
|
||||||
Headers::value_type to_header(const uint8_t *name, size_t namelen,
|
Headers::value_type to_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen,
|
const uint8_t *value, size_t valuelen,
|
||||||
bool no_index) {
|
bool no_index) {
|
||||||
|
@ -328,26 +196,14 @@ void add_header(Headers &nva, const uint8_t *name, size_t namelen,
|
||||||
nva.push_back(to_header(name, namelen, value, valuelen, no_index));
|
nva.push_back(to_header(name, namelen, value, valuelen, no_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Headers::value_type *get_unique_header(const Headers &nva,
|
const Headers::value_type *get_header(const Headers &nva, const char *name) {
|
||||||
const char *name) {
|
const Headers::value_type *res = nullptr;
|
||||||
auto nv = Headers::value_type(name, "");
|
for (auto &nv : nva) {
|
||||||
auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less);
|
if (nv.name == name) {
|
||||||
if (i != std::end(nva) && (*i).name == nv.name) {
|
res = &nv;
|
||||||
auto j = i + 1;
|
|
||||||
if (j == std::end(nva) || (*j).name != nv.name) {
|
|
||||||
return &(*i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return res;
|
||||||
}
|
|
||||||
|
|
||||||
const Headers::value_type *get_header(const Headers &nva, const char *name) {
|
|
||||||
auto nv = Headers::value_type(name, "");
|
|
||||||
auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less);
|
|
||||||
if (i != std::end(nva) && (*i).name == nv.name) {
|
|
||||||
return &(*i);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string value_to_str(const Headers::value_type *nv) {
|
std::string value_to_str(const Headers::value_type *nv) {
|
||||||
|
@ -371,65 +227,54 @@ nghttp2_nv make_nv(const std::string &name, const std::string &value,
|
||||||
value.size(), flags};
|
value.size(), flags};
|
||||||
}
|
}
|
||||||
|
|
||||||
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
|
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
|
||||||
const Headers &headers) {
|
for (auto &kv : headers) {
|
||||||
size_t i, j;
|
if (kv.name.empty() || kv.name[0] == ':') {
|
||||||
for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) {
|
continue;
|
||||||
auto &kv = headers[i];
|
|
||||||
int rv = strcmp(kv.name.c_str(), IGN_HD[j]);
|
|
||||||
if (rv < 0) {
|
|
||||||
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
|
|
||||||
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
} else if (rv > 0) {
|
|
||||||
++j;
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
}
|
switch (lookup_token(kv.name)) {
|
||||||
for (; i < headers.size(); ++i) {
|
case HD_COOKIE:
|
||||||
auto &kv = headers[i];
|
case HD_CONNECTION:
|
||||||
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
|
case HD_HTTP2_SETTINGS:
|
||||||
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
|
case HD_KEEP_ALIVE:
|
||||||
|
case HD_PROXY_CONNECTION:
|
||||||
|
case HD_SERVER:
|
||||||
|
case HD_TRANSFER_ENCODING:
|
||||||
|
case HD_UPGRADE:
|
||||||
|
case HD_VIA:
|
||||||
|
case HD_X_FORWARDED_FOR:
|
||||||
|
case HD_X_FORWARDED_PROTO:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void build_http1_headers_from_norm_headers(std::string &hdrs,
|
void build_http1_headers_from_headers(std::string &hdrs,
|
||||||
const Headers &headers) {
|
const Headers &headers) {
|
||||||
size_t i, j;
|
for (auto &kv : headers) {
|
||||||
for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) {
|
if (kv.name.empty() || kv.name[0] == ':') {
|
||||||
auto &kv = headers[i];
|
continue;
|
||||||
auto rv = strcmp(kv.name.c_str(), HTTP1_IGN_HD[j]);
|
|
||||||
|
|
||||||
if (rv < 0) {
|
|
||||||
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
|
|
||||||
hdrs += kv.name;
|
|
||||||
capitalize(hdrs, hdrs.size() - kv.name.size());
|
|
||||||
hdrs += ": ";
|
|
||||||
hdrs += kv.value;
|
|
||||||
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
|
|
||||||
hdrs += "\r\n";
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
} else if (rv > 0) {
|
|
||||||
++j;
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
}
|
switch (lookup_token(kv.name)) {
|
||||||
for (; i < headers.size(); ++i) {
|
case HD_CONNECTION:
|
||||||
auto &kv = headers[i];
|
case HD_COOKIE:
|
||||||
|
case HD_HTTP2_SETTINGS:
|
||||||
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
|
case HD_KEEP_ALIVE:
|
||||||
hdrs += kv.name;
|
case HD_PROXY_CONNECTION:
|
||||||
capitalize(hdrs, hdrs.size() - kv.name.size());
|
case HD_SERVER:
|
||||||
hdrs += ": ";
|
case HD_UPGRADE:
|
||||||
hdrs += kv.value;
|
case HD_VIA:
|
||||||
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
|
case HD_X_FORWARDED_FOR:
|
||||||
hdrs += "\r\n";
|
case HD_X_FORWARDED_PROTO:
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
hdrs += kv.name;
|
||||||
|
capitalize(hdrs, hdrs.size() - kv.name.size());
|
||||||
|
hdrs += ": ";
|
||||||
|
hdrs += kv.value;
|
||||||
|
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
|
||||||
|
hdrs += "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,6 +417,258 @@ int parse_http_status_code(const std::string &src) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lookup_token(const std::string &name) {
|
||||||
|
return lookup_token(reinterpret_cast<const uint8_t *>(name.c_str()),
|
||||||
|
name.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function was generated by genheaderfunc.py. Inspired by h2o
|
||||||
|
// header lookup. https://github.com/h2o/h2o
|
||||||
|
int lookup_token(const uint8_t *name, size_t namelen) {
|
||||||
|
switch (namelen) {
|
||||||
|
case 2:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'e':
|
||||||
|
if (util::streq("t", name, 1)) {
|
||||||
|
return HD_TE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'a':
|
||||||
|
if (util::streq("vi", name, 2)) {
|
||||||
|
return HD_VIA;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 't':
|
||||||
|
if (util::streq("hos", name, 3)) {
|
||||||
|
return HD_HOST;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'h':
|
||||||
|
if (util::streq(":pat", name, 4)) {
|
||||||
|
return HD__PATH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
if (util::streq(":hos", name, 4)) {
|
||||||
|
return HD__HOST;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'e':
|
||||||
|
if (util::streq("cooki", name, 5)) {
|
||||||
|
return HD_COOKIE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
if (util::streq("serve", name, 5)) {
|
||||||
|
return HD_SERVER;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
if (util::streq("expec", name, 5)) {
|
||||||
|
return HD_EXPECT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'c':
|
||||||
|
if (util::streq("alt-sv", name, 6)) {
|
||||||
|
return HD_ALT_SVC;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
if (util::streq(":metho", name, 6)) {
|
||||||
|
return HD__METHOD;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'e':
|
||||||
|
if (util::streq(":schem", name, 6)) {
|
||||||
|
return HD__SCHEME;
|
||||||
|
}
|
||||||
|
if (util::streq("upgrad", name, 6)) {
|
||||||
|
return HD_UPGRADE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (util::streq(":statu", name, 6)) {
|
||||||
|
return HD__STATUS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'n':
|
||||||
|
if (util::streq("locatio", name, 7)) {
|
||||||
|
return HD_LOCATION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'e':
|
||||||
|
if (util::streq("keep-aliv", name, 9)) {
|
||||||
|
return HD_KEEP_ALIVE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
if (util::streq("connectio", name, 9)) {
|
||||||
|
return HD_CONNECTION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'y':
|
||||||
|
if (util::streq(":authorit", name, 9)) {
|
||||||
|
return HD__AUTHORITY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 14:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'h':
|
||||||
|
if (util::streq("content-lengt", name, 13)) {
|
||||||
|
return HD_CONTENT_LENGTH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (util::streq("http2-setting", name, 13)) {
|
||||||
|
return HD_HTTP2_SETTINGS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'r':
|
||||||
|
if (util::streq("x-forwarded-fo", name, 14)) {
|
||||||
|
return HD_X_FORWARDED_FOR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'n':
|
||||||
|
if (util::streq("proxy-connectio", name, 15)) {
|
||||||
|
return HD_PROXY_CONNECTION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
switch (name[namelen - 1]) {
|
||||||
|
case 'e':
|
||||||
|
if (util::streq("if-modified-sinc", name, 16)) {
|
||||||
|
return HD_IF_MODIFIED_SINCE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
if (util::streq("transfer-encodin", name, 16)) {
|
||||||
|
return HD_TRANSFER_ENCODING;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
if (util::streq("x-forwarded-prot", name, 16)) {
|
||||||
|
return HD_X_FORWARDED_PROTO;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); }
|
||||||
|
|
||||||
|
void index_headers(int *hdidx, const Headers &headers) {
|
||||||
|
for (size_t i = 0; i < headers.size(); ++i) {
|
||||||
|
auto &kv = headers[i];
|
||||||
|
auto token = lookup_token(
|
||||||
|
reinterpret_cast<const uint8_t *>(kv.name.c_str()), kv.name.size());
|
||||||
|
if (token >= 0) {
|
||||||
|
http2::index_header(hdidx, token, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void index_header(int *hdidx, int token, size_t idx) {
|
||||||
|
if (token == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(token < HD_MAXIDX);
|
||||||
|
hdidx[token] = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_http2_request_pseudo_header(const int *hdidx, int token) {
|
||||||
|
switch (token) {
|
||||||
|
case HD__AUTHORITY:
|
||||||
|
case HD__METHOD:
|
||||||
|
case HD__PATH:
|
||||||
|
case HD__SCHEME:
|
||||||
|
return hdidx[token] == -1;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_http2_response_pseudo_header(const int *hdidx, int token) {
|
||||||
|
switch (token) {
|
||||||
|
case HD__STATUS:
|
||||||
|
return hdidx[token] == -1;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http2_header_allowed(int token) {
|
||||||
|
switch (token) {
|
||||||
|
case HD_CONNECTION:
|
||||||
|
case HD_KEEP_ALIVE:
|
||||||
|
case HD_PROXY_CONNECTION:
|
||||||
|
case HD_TRANSFER_ENCODING:
|
||||||
|
case HD_UPGRADE:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http2_mandatory_request_headers_presence(const int *hdidx) {
|
||||||
|
if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 ||
|
||||||
|
hdidx[HD__SCHEME] == -1 ||
|
||||||
|
(hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Headers::value_type *get_header(const int *hdidx, int token,
|
||||||
|
const Headers &nva) {
|
||||||
|
auto i = hdidx[token];
|
||||||
|
if (i == -1) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &nva[i];
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
113
src/http2.h
113
src/http2.h
|
@ -76,35 +76,6 @@ void sanitize_header_value(std::string &s, size_t offset);
|
||||||
void copy_url_component(std::string &dest, const http_parser_url *u, int field,
|
void copy_url_component(std::string &dest, const http_parser_url *u, int field,
|
||||||
const char *url);
|
const char *url);
|
||||||
|
|
||||||
// Returns true if the header field |name| with length |namelen| bytes
|
|
||||||
// is valid for HTTP/2.
|
|
||||||
bool check_http2_allowed_header(const uint8_t *name, size_t namelen);
|
|
||||||
|
|
||||||
// Calls check_http2_allowed_header with |name| and strlen(name),
|
|
||||||
// assuming |name| is null-terminated string.
|
|
||||||
bool check_http2_allowed_header(const char *name);
|
|
||||||
|
|
||||||
// Checks that headers |nva| do not contain disallowed header fields
|
|
||||||
// in HTTP/2 spec. This function returns true if |nva| does not
|
|
||||||
// contains such headers.
|
|
||||||
bool check_http2_headers(const Headers &nva);
|
|
||||||
|
|
||||||
// Calls check_http2_headers()
|
|
||||||
bool check_http2_request_headers(const Headers &nva);
|
|
||||||
|
|
||||||
// Calls check_http2_headers()
|
|
||||||
bool check_http2_response_headers(const Headers &nva);
|
|
||||||
|
|
||||||
// Returns true if |name| is allowed pusedo header for request.
|
|
||||||
bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen);
|
|
||||||
|
|
||||||
// Returns true if |name| is allowed pusedo header for response.
|
|
||||||
bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen);
|
|
||||||
|
|
||||||
bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs);
|
|
||||||
|
|
||||||
void normalize_headers(Headers &nva);
|
|
||||||
|
|
||||||
Headers::value_type to_header(const uint8_t *name, size_t namelen,
|
Headers::value_type to_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen,
|
const uint8_t *value, size_t valuelen,
|
||||||
bool no_index);
|
bool no_index);
|
||||||
|
@ -115,16 +86,9 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen,
|
||||||
void add_header(Headers &nva, const uint8_t *name, size_t namelen,
|
void add_header(Headers &nva, const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen, bool no_index);
|
const uint8_t *value, size_t valuelen, bool no_index);
|
||||||
|
|
||||||
// Returns the iterator to the entry in |nva| which has name |name|
|
// Returns pointer to the entry in |nva| which has name |name|. If
|
||||||
// and the |name| is uinque in the |nva|. If no such entry exist,
|
// more than one entries which have the name |name|, last occurrence
|
||||||
// returns nullptr.
|
// in |nva| is returned. If no such entry exist, returns nullptr.
|
||||||
const Headers::value_type *get_unique_header(const Headers &nva,
|
|
||||||
const char *name);
|
|
||||||
|
|
||||||
// Returns the iterator to the entry in |nva| which has name
|
|
||||||
// |name|. If more than one entries which have the name |name|, first
|
|
||||||
// occurrence in |nva| is returned. If no such entry exist, returns
|
|
||||||
// nullptr.
|
|
||||||
const Headers::value_type *get_header(const Headers &nva, const char *name);
|
const Headers::value_type *get_header(const Headers &nva, const char *name);
|
||||||
|
|
||||||
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
|
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
|
||||||
|
@ -165,14 +129,13 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
|
||||||
// Appends headers in |headers| to |nv|. Certain headers, including
|
// Appends headers in |headers| to |nv|. Certain headers, including
|
||||||
// disallowed headers in HTTP/2 spec and headers which require
|
// disallowed headers in HTTP/2 spec and headers which require
|
||||||
// special handling (i.e. via), are not copied.
|
// special handling (i.e. via), are not copied.
|
||||||
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
|
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
|
||||||
const Headers &headers);
|
|
||||||
|
|
||||||
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
|
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
|
||||||
// |headers|. Certain headers, which requires special handling
|
// |headers|. Certain headers, which requires special handling
|
||||||
// (i.e. via and cookie), are not appended.
|
// (i.e. via and cookie), are not appended.
|
||||||
void build_http1_headers_from_norm_headers(std::string &hdrs,
|
void build_http1_headers_from_headers(std::string &hdrs,
|
||||||
const Headers &headers);
|
const Headers &headers);
|
||||||
|
|
||||||
// Return positive window_size_increment if WINDOW_UPDATE should be
|
// Return positive window_size_increment if WINDOW_UPDATE should be
|
||||||
// sent for the stream |stream_id|. If |stream_id| == 0, this function
|
// sent for the stream |stream_id|. If |stream_id| == 0, this function
|
||||||
|
@ -218,6 +181,70 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
|
||||||
// Returns parsed HTTP status code. Returns -1 on failure.
|
// Returns parsed HTTP status code. Returns -1 on failure.
|
||||||
int parse_http_status_code(const std::string &src);
|
int parse_http_status_code(const std::string &src);
|
||||||
|
|
||||||
|
// Header fields to be indexed, except HD_MAXIDX which is convenient
|
||||||
|
// member to get maximum value.
|
||||||
|
enum {
|
||||||
|
HD__AUTHORITY,
|
||||||
|
HD__HOST,
|
||||||
|
HD__METHOD,
|
||||||
|
HD__PATH,
|
||||||
|
HD__SCHEME,
|
||||||
|
HD__STATUS,
|
||||||
|
HD_ALT_SVC,
|
||||||
|
HD_CONNECTION,
|
||||||
|
HD_CONTENT_LENGTH,
|
||||||
|
HD_COOKIE,
|
||||||
|
HD_EXPECT,
|
||||||
|
HD_HOST,
|
||||||
|
HD_HTTP2_SETTINGS,
|
||||||
|
HD_IF_MODIFIED_SINCE,
|
||||||
|
HD_KEEP_ALIVE,
|
||||||
|
HD_LOCATION,
|
||||||
|
HD_PROXY_CONNECTION,
|
||||||
|
HD_SERVER,
|
||||||
|
HD_TE,
|
||||||
|
HD_TRANSFER_ENCODING,
|
||||||
|
HD_UPGRADE,
|
||||||
|
HD_VIA,
|
||||||
|
HD_X_FORWARDED_FOR,
|
||||||
|
HD_X_FORWARDED_PROTO,
|
||||||
|
HD_MAXIDX,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Looks up header token for header name |name| of length |namelen|.
|
||||||
|
// Only headers we are interested in are tokenized. If header name
|
||||||
|
// cannot be tokenized, returns -1.
|
||||||
|
int lookup_token(const uint8_t *name, size_t namelen);
|
||||||
|
int lookup_token(const std::string &name);
|
||||||
|
|
||||||
|
// Initializes |hdidx|, header index. The |hdidx| must point to the
|
||||||
|
// array containing at least HD_MAXIDX elements.
|
||||||
|
void init_hdidx(int *hdidx);
|
||||||
|
// Indexes header |token| using index |idx|.
|
||||||
|
void index_header(int *hdidx, int token, size_t idx);
|
||||||
|
// Iterates |headers| and for each element, call index_header.
|
||||||
|
void index_headers(int *hdidx, const Headers &headers);
|
||||||
|
|
||||||
|
// Returns true if HTTP/2 request pseudo header |token| is not indexed
|
||||||
|
// yet and not -1.
|
||||||
|
bool check_http2_request_pseudo_header(const int *hdidx, int token);
|
||||||
|
|
||||||
|
// Returns true if HTTP/2 response pseudo header |token| is not
|
||||||
|
// indexed yet and not -1.
|
||||||
|
bool check_http2_response_pseudo_header(const int *hdidx, int token);
|
||||||
|
|
||||||
|
// Returns true if header field denoted by |token| is allowed for
|
||||||
|
// HTTP/2.
|
||||||
|
bool http2_header_allowed(int token);
|
||||||
|
|
||||||
|
// Returns true that |hdidx| contains mandatory HTTP/2 request
|
||||||
|
// headers.
|
||||||
|
bool http2_mandatory_request_headers_presence(const int *hdidx);
|
||||||
|
|
||||||
|
// Returns header denoted by |token| using index |hdidx|.
|
||||||
|
const Headers::value_type *get_header(const int *hdidx, int token,
|
||||||
|
const Headers &nva);
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -100,55 +100,14 @@ void test_http2_add_header(void) {
|
||||||
CU_ASSERT(Headers::value_type("a", "") == nva[0]);
|
CU_ASSERT(Headers::value_type("a", "") == nva[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_http2_check_http2_headers(void) {
|
|
||||||
auto nva1 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"upgrade", "http2"}};
|
|
||||||
CU_ASSERT(!http2::check_http2_headers(nva1));
|
|
||||||
|
|
||||||
auto nva2 = Headers{{"connection", "1"}, {"delta", "2"}, {"echo", "3"}};
|
|
||||||
CU_ASSERT(!http2::check_http2_headers(nva2));
|
|
||||||
|
|
||||||
auto nva3 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"te2", "3"}};
|
|
||||||
CU_ASSERT(http2::check_http2_headers(nva3));
|
|
||||||
|
|
||||||
auto n1 = ":authority";
|
|
||||||
auto n1u8 = reinterpret_cast<const uint8_t *>(n1);
|
|
||||||
|
|
||||||
CU_ASSERT(http2::check_http2_request_pseudo_header(n1u8, strlen(n1)));
|
|
||||||
CU_ASSERT(!http2::check_http2_response_pseudo_header(n1u8, strlen(n1)));
|
|
||||||
|
|
||||||
auto n2 = ":status";
|
|
||||||
auto n2u8 = reinterpret_cast<const uint8_t *>(n2);
|
|
||||||
|
|
||||||
CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2)));
|
|
||||||
CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_http2_get_unique_header(void) {
|
|
||||||
auto nva = Headers{{"alpha", "1"},
|
|
||||||
{"bravo", "2"},
|
|
||||||
{"bravo", "3"},
|
|
||||||
{"charlie", "4"},
|
|
||||||
{"delta", "5"},
|
|
||||||
{"echo", "6"}};
|
|
||||||
const Headers::value_type *rv;
|
|
||||||
rv = http2::get_unique_header(nva, "delta");
|
|
||||||
CU_ASSERT(rv != nullptr);
|
|
||||||
CU_ASSERT("delta" == rv->name);
|
|
||||||
|
|
||||||
rv = http2::get_unique_header(nva, "bravo");
|
|
||||||
CU_ASSERT(rv == nullptr);
|
|
||||||
|
|
||||||
rv = http2::get_unique_header(nva, "foxtrot");
|
|
||||||
CU_ASSERT(rv == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_http2_get_header(void) {
|
void test_http2_get_header(void) {
|
||||||
auto nva = Headers{{"alpha", "1"},
|
auto nva = Headers{{"alpha", "1"},
|
||||||
{"bravo", "2"},
|
{"bravo", "2"},
|
||||||
{"bravo", "3"},
|
{"bravo", "3"},
|
||||||
{"charlie", "4"},
|
{"charlie", "4"},
|
||||||
{"delta", "5"},
|
{"delta", "5"},
|
||||||
{"echo", "6"}};
|
{"echo", "6"},
|
||||||
|
{"content-length", "7"}};
|
||||||
const Headers::value_type *rv;
|
const Headers::value_type *rv;
|
||||||
rv = http2::get_header(nva, "delta");
|
rv = http2::get_header(nva, "delta");
|
||||||
CU_ASSERT(rv != nullptr);
|
CU_ASSERT(rv != nullptr);
|
||||||
|
@ -160,6 +119,12 @@ void test_http2_get_header(void) {
|
||||||
|
|
||||||
rv = http2::get_header(nva, "foxtrot");
|
rv = http2::get_header(nva, "foxtrot");
|
||||||
CU_ASSERT(rv == nullptr);
|
CU_ASSERT(rv == nullptr);
|
||||||
|
|
||||||
|
int hdidx[http2::HD_MAXIDX];
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
hdidx[http2::HD_CONTENT_LENGTH] = 6;
|
||||||
|
rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva);
|
||||||
|
CU_ASSERT("content-length" == rv->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true},
|
||||||
{"zulu", "12"}};
|
{"zulu", "12"}};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void test_http2_copy_norm_headers_to_nva(void) {
|
void test_http2_copy_headers_to_nva(void) {
|
||||||
std::vector<nghttp2_nv> nva;
|
std::vector<nghttp2_nv> nva;
|
||||||
http2::copy_norm_headers_to_nva(nva, headers);
|
http2::copy_headers_to_nva(nva, headers);
|
||||||
CU_ASSERT(7 == nva.size());
|
CU_ASSERT(9 == nva.size());
|
||||||
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
|
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
|
||||||
for (size_t i = 0; i < ans.size(); ++i) {
|
for (size_t i = 0; i < ans.size(); ++i) {
|
||||||
check_nv(headers[ans[i]], &nva[i]);
|
check_nv(headers[ans[i]], &nva[i]);
|
||||||
|
|
||||||
|
@ -194,9 +159,9 @@ void test_http2_copy_norm_headers_to_nva(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_http2_build_http1_headers_from_norm_headers(void) {
|
void test_http2_build_http1_headers_from_headers(void) {
|
||||||
std::string hdrs;
|
std::string hdrs;
|
||||||
http2::build_http1_headers_from_norm_headers(hdrs, headers);
|
http2::build_http1_headers_from_headers(hdrs, headers);
|
||||||
CU_ASSERT(hdrs == "Alpha: 0\r\n"
|
CU_ASSERT(hdrs == "Alpha: 0\r\n"
|
||||||
"Bravo: 1\r\n"
|
"Bravo: 1\r\n"
|
||||||
"Delta: 4\r\n"
|
"Delta: 4\r\n"
|
||||||
|
@ -206,15 +171,6 @@ void test_http2_build_http1_headers_from_norm_headers(void) {
|
||||||
"Te: 8\r\n"
|
"Te: 8\r\n"
|
||||||
"Te: 9\r\n"
|
"Te: 9\r\n"
|
||||||
"Zulu: 12\r\n");
|
"Zulu: 12\r\n");
|
||||||
|
|
||||||
hdrs.clear();
|
|
||||||
// Both nghttp2 and spdylay do not allow \r and \n in header value
|
|
||||||
// now.
|
|
||||||
|
|
||||||
// auto hd2 = std::vector<std::pair<std::string, std::string>>
|
|
||||||
// {{"alpha", "bravo\r\ncharlie\r\n"}};
|
|
||||||
// http2::build_http1_headers_from_norm_headers(hdrs, hd2);
|
|
||||||
// CU_ASSERT(hdrs == "Alpha: bravo charlie \r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_http2_lws(void) {
|
void test_http2_lws(void) {
|
||||||
|
@ -270,4 +226,68 @@ void test_http2_parse_http_status_code(void) {
|
||||||
CU_ASSERT(-1 == http2::parse_http_status_code(""));
|
CU_ASSERT(-1 == http2::parse_http_status_code(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_http2_index_header(void) {
|
||||||
|
int hdidx[http2::HD_MAXIDX];
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
|
||||||
|
http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
|
||||||
|
http2::index_header(hdidx, -1, 1);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_http2_lookup_token(void) {
|
||||||
|
CU_ASSERT(http2::HD__AUTHORITY == http2::lookup_token(":authority"));
|
||||||
|
CU_ASSERT(-1 == http2::lookup_token(":authorit"));
|
||||||
|
CU_ASSERT(-1 == http2::lookup_token(":Authority"));
|
||||||
|
CU_ASSERT(http2::HD_EXPECT == http2::lookup_token("expect"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_http2_check_http2_pseudo_header(void) {
|
||||||
|
int hdidx[http2::HD_MAXIDX];
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
|
||||||
|
CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
|
||||||
|
hdidx[http2::HD__PATH] = 0;
|
||||||
|
CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
|
||||||
|
hdidx[http2::HD__METHOD] = 1;
|
||||||
|
CU_ASSERT(
|
||||||
|
!http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
|
||||||
|
CU_ASSERT(!http2::check_http2_request_pseudo_header(hdidx, http2::HD_VIA));
|
||||||
|
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
|
||||||
|
CU_ASSERT(
|
||||||
|
http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
|
||||||
|
hdidx[http2::HD__STATUS] = 0;
|
||||||
|
CU_ASSERT(
|
||||||
|
!http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
|
||||||
|
CU_ASSERT(!http2::check_http2_response_pseudo_header(hdidx, http2::HD_VIA));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_http2_http2_header_allowed(void) {
|
||||||
|
CU_ASSERT(http2::http2_header_allowed(http2::HD__PATH));
|
||||||
|
CU_ASSERT(http2::http2_header_allowed(http2::HD_CONTENT_LENGTH));
|
||||||
|
CU_ASSERT(!http2::http2_header_allowed(http2::HD_CONNECTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_http2_mandatory_request_headers_presence(void) {
|
||||||
|
int hdidx[http2::HD_MAXIDX];
|
||||||
|
http2::init_hdidx(hdidx);
|
||||||
|
|
||||||
|
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
hdidx[http2::HD__AUTHORITY] = 0;
|
||||||
|
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
hdidx[http2::HD__METHOD] = 1;
|
||||||
|
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
hdidx[http2::HD__PATH] = 2;
|
||||||
|
CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
hdidx[http2::HD__SCHEME] = 3;
|
||||||
|
CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
|
||||||
|
hdidx[http2::HD__AUTHORITY] = -1;
|
||||||
|
hdidx[http2::HD_HOST] = 0;
|
||||||
|
CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -28,14 +28,17 @@
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
void test_http2_add_header(void);
|
void test_http2_add_header(void);
|
||||||
void test_http2_check_http2_headers(void);
|
|
||||||
void test_http2_get_unique_header(void);
|
|
||||||
void test_http2_get_header(void);
|
void test_http2_get_header(void);
|
||||||
void test_http2_copy_norm_headers_to_nva(void);
|
void test_http2_copy_headers_to_nva(void);
|
||||||
void test_http2_build_http1_headers_from_norm_headers(void);
|
void test_http2_build_http1_headers_from_headers(void);
|
||||||
void test_http2_lws(void);
|
void test_http2_lws(void);
|
||||||
void test_http2_rewrite_location_uri(void);
|
void test_http2_rewrite_location_uri(void);
|
||||||
void test_http2_parse_http_status_code(void);
|
void test_http2_parse_http_status_code(void);
|
||||||
|
void test_http2_index_header(void);
|
||||||
|
void test_http2_lookup_token(void);
|
||||||
|
void test_http2_check_http2_pseudo_header(void);
|
||||||
|
void test_http2_http2_header_allowed(void);
|
||||||
|
void test_http2_mandatory_request_headers_presence(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef MEMCHUNK_H
|
||||||
|
#define MEMCHUNK_H
|
||||||
|
|
||||||
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
template <size_t N> struct Memchunk {
|
||||||
|
Memchunk()
|
||||||
|
: kprev(nullptr), next(nullptr), pos(begin), last(begin), end(begin + N) {
|
||||||
|
}
|
||||||
|
size_t len() const { return last - pos; }
|
||||||
|
size_t left() const { return end - last; }
|
||||||
|
void reset() { pos = last = begin; }
|
||||||
|
std::unique_ptr<Memchunk> knext;
|
||||||
|
Memchunk *kprev;
|
||||||
|
Memchunk *next;
|
||||||
|
uint8_t *pos, *last;
|
||||||
|
uint8_t *end;
|
||||||
|
uint8_t begin[N];
|
||||||
|
static const size_t size = N;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> struct Pool {
|
||||||
|
Pool() : pool(nullptr), freelist(nullptr), poolsize(0) {}
|
||||||
|
T *get() {
|
||||||
|
if (freelist) {
|
||||||
|
auto m = freelist;
|
||||||
|
freelist = freelist->next;
|
||||||
|
m->next = nullptr;
|
||||||
|
m->reset();
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto m = util::make_unique<T>();
|
||||||
|
auto p = m.get();
|
||||||
|
if (pool) {
|
||||||
|
m->knext = std::move(pool);
|
||||||
|
m->knext->kprev = m.get();
|
||||||
|
}
|
||||||
|
pool = std::move(m);
|
||||||
|
poolsize += T::size;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
void recycle(T *m) {
|
||||||
|
if (freelist) {
|
||||||
|
m->next = freelist;
|
||||||
|
} else {
|
||||||
|
m->next = nullptr;
|
||||||
|
}
|
||||||
|
freelist = m;
|
||||||
|
}
|
||||||
|
void shrink(size_t max) {
|
||||||
|
auto m = freelist;
|
||||||
|
for (; m && poolsize > max;) {
|
||||||
|
auto next = m->next;
|
||||||
|
poolsize -= T::size;
|
||||||
|
auto p = m->kprev;
|
||||||
|
if (p) {
|
||||||
|
p->knext = std::move(m->knext);
|
||||||
|
if (p->knext) {
|
||||||
|
p->knext->kprev = p;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pool = std::move(m->knext);
|
||||||
|
if (pool) {
|
||||||
|
pool->kprev = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
freelist = m;
|
||||||
|
}
|
||||||
|
std::unique_ptr<T> pool;
|
||||||
|
T *freelist;
|
||||||
|
size_t poolsize;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void *cpymem(void *dest, const void *src, size_t count) {
|
||||||
|
memcpy(dest, src, count);
|
||||||
|
return reinterpret_cast<uint8_t *>(dest) + count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Memchunk> struct Memchunks {
|
||||||
|
Memchunks(Pool<Memchunk> *pool)
|
||||||
|
: pool(pool), head(nullptr), tail(nullptr), len(0) {}
|
||||||
|
~Memchunks() {
|
||||||
|
if (!pool) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (auto m = head; m;) {
|
||||||
|
auto next = m->next;
|
||||||
|
pool->recycle(m);
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t append(const void *data, size_t count) {
|
||||||
|
if (count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto p = reinterpret_cast<const uint8_t *>(data);
|
||||||
|
|
||||||
|
if (!tail) {
|
||||||
|
head = tail = pool->get();
|
||||||
|
}
|
||||||
|
auto all = count;
|
||||||
|
|
||||||
|
while (count > 0) {
|
||||||
|
auto n = std::min(count, tail->left());
|
||||||
|
tail->last = reinterpret_cast<uint8_t *>(cpymem(tail->last, p, n));
|
||||||
|
p += n;
|
||||||
|
count -= n;
|
||||||
|
len += n;
|
||||||
|
if (count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tail->next = pool->get();
|
||||||
|
|
||||||
|
assert(tail != tail->next);
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
template <size_t N> size_t append_cstr(const char (&s)[N]) {
|
||||||
|
return append(s, N - 1);
|
||||||
|
}
|
||||||
|
size_t remove(void *data, size_t count) {
|
||||||
|
if (!tail || count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto ndata = count;
|
||||||
|
auto m = head;
|
||||||
|
|
||||||
|
while (m) {
|
||||||
|
auto next = m->next;
|
||||||
|
auto n = std::min(count, m->len());
|
||||||
|
|
||||||
|
assert(m->len());
|
||||||
|
data = cpymem(data, m->pos, n);
|
||||||
|
m->pos += n;
|
||||||
|
count -= n;
|
||||||
|
len -= n;
|
||||||
|
if (m->len() > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pool->recycle(m);
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
head = m;
|
||||||
|
if (head == nullptr) {
|
||||||
|
tail = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ndata - count;
|
||||||
|
}
|
||||||
|
size_t drain(size_t count) {
|
||||||
|
auto ndata = count;
|
||||||
|
auto m = head;
|
||||||
|
while (m) {
|
||||||
|
auto next = m->next;
|
||||||
|
auto n = std::min(count, m->len());
|
||||||
|
m->pos += n;
|
||||||
|
count -= n;
|
||||||
|
len -= n;
|
||||||
|
if (m->len() > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool->recycle(m);
|
||||||
|
m = next;
|
||||||
|
}
|
||||||
|
head = m;
|
||||||
|
if (head == nullptr) {
|
||||||
|
tail = nullptr;
|
||||||
|
}
|
||||||
|
return ndata - count;
|
||||||
|
}
|
||||||
|
int riovec(struct iovec *iov, int iovcnt) {
|
||||||
|
if (!head) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto m = head;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < iovcnt && m; ++i, m = m->next) {
|
||||||
|
iov[i].iov_base = m->pos;
|
||||||
|
iov[i].iov_len = m->len();
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
size_t rleft() const { return len; }
|
||||||
|
|
||||||
|
Pool<Memchunk> *pool;
|
||||||
|
Memchunk *head, *tail;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Memchunk<4096> Memchunk4K;
|
||||||
|
typedef Pool<Memchunk4K> MemchunkPool4K;
|
||||||
|
typedef Memchunks<Memchunk4K> Memchunks4K;
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
||||||
|
|
||||||
|
#endif // MEMCHUNK_H
|
1047
src/nghttp.cc
1047
src/nghttp.cc
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef RINGBUF_H
|
||||||
|
#define RINGBUF_H
|
||||||
|
|
||||||
|
#include "nghttp2_config.h"
|
||||||
|
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
template <size_t N> struct RingBuf {
|
||||||
|
RingBuf() : pos(0), len(0) {}
|
||||||
|
// Returns the number of bytes to read.
|
||||||
|
size_t rleft() const { return len; }
|
||||||
|
// Returns the number of bytes this buffer can store.
|
||||||
|
size_t wleft() const { return N - len; }
|
||||||
|
// Writes up to min(wleft(), |count|) bytes from buffer pointed by
|
||||||
|
// |buf|. Returns number of bytes written.
|
||||||
|
size_t write(const void *buf, size_t count) {
|
||||||
|
count = std::min(count, wleft());
|
||||||
|
auto last = (pos + len) % N;
|
||||||
|
if (count > N - last) {
|
||||||
|
auto c = N - last;
|
||||||
|
memcpy(begin + last, buf, c);
|
||||||
|
memcpy(begin, reinterpret_cast<const uint8_t *>(buf) + c, count - c);
|
||||||
|
} else {
|
||||||
|
memcpy(begin + last, buf, count);
|
||||||
|
}
|
||||||
|
len += count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
size_t write(size_t count) {
|
||||||
|
count = std::min(count, wleft());
|
||||||
|
len += count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
// Drains min(rleft(), |count|) bytes from start of the buffer.
|
||||||
|
size_t drain(size_t count) {
|
||||||
|
count = std::min(count, rleft());
|
||||||
|
pos = (pos + count) % N;
|
||||||
|
len -= count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
// Returns pointer to the next contiguous readable buffer and its
|
||||||
|
// length.
|
||||||
|
std::pair<const void *, size_t> get() const {
|
||||||
|
if (pos + len > N) {
|
||||||
|
return {begin + pos, N - pos};
|
||||||
|
}
|
||||||
|
return {begin + pos, len};
|
||||||
|
}
|
||||||
|
void reset() { pos = len = 0; }
|
||||||
|
// Fills |iov| for reading. |iov| must contain at least 2 elements.
|
||||||
|
// Returns the number of filled elements.
|
||||||
|
int riovec(struct iovec *iov) {
|
||||||
|
if (len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pos + len > N) {
|
||||||
|
auto c = N - pos;
|
||||||
|
iov[0].iov_base = begin + pos;
|
||||||
|
iov[0].iov_len = c;
|
||||||
|
iov[1].iov_base = begin;
|
||||||
|
iov[1].iov_len = len - c;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
iov[0].iov_base = begin + pos;
|
||||||
|
iov[0].iov_len = len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Fills |iov| for writing. |iov| must contain at least 2 elements.
|
||||||
|
// Returns the number of filled elements.
|
||||||
|
int wiovec(struct iovec *iov) {
|
||||||
|
if (len == N) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pos == 0) {
|
||||||
|
iov[0].iov_base = begin + pos + len;
|
||||||
|
iov[0].iov_len = N - pos - len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (pos + len < N) {
|
||||||
|
auto c = N - pos - len;
|
||||||
|
iov[0].iov_base = begin + pos + len;
|
||||||
|
iov[0].iov_len = c;
|
||||||
|
iov[1].iov_base = begin;
|
||||||
|
iov[1].iov_len = N - len - c;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
auto last = (pos + len) % N;
|
||||||
|
iov[0].iov_base = begin + last;
|
||||||
|
iov[0].iov_len = N - len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
size_t pos;
|
||||||
|
size_t len;
|
||||||
|
uint8_t begin[N];
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) {
|
||||||
|
if (max == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < iovcnt; ++i) {
|
||||||
|
auto d = std::min(max, iov[i].iov_len);
|
||||||
|
iov[i].iov_len = d;
|
||||||
|
max -= d;
|
||||||
|
if (max == 0) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iovcnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
||||||
|
|
||||||
|
#endif // RINGBUF_H
|
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "ringbuf_test.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <CUnit/CUnit.h>
|
||||||
|
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
void test_ringbuf_write(void) {
|
||||||
|
RingBuf<16> b;
|
||||||
|
CU_ASSERT(0 == b.rleft());
|
||||||
|
CU_ASSERT(16 == b.wleft());
|
||||||
|
|
||||||
|
b.write("012", 3);
|
||||||
|
|
||||||
|
CU_ASSERT(3 == b.rleft());
|
||||||
|
CU_ASSERT(13 == b.wleft());
|
||||||
|
CU_ASSERT(0 == b.pos);
|
||||||
|
CU_ASSERT(3 == b.len);
|
||||||
|
|
||||||
|
b.drain(3);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == b.rleft());
|
||||||
|
CU_ASSERT(16 == b.wleft());
|
||||||
|
CU_ASSERT(3 == b.pos);
|
||||||
|
CU_ASSERT(0 == b.len);
|
||||||
|
|
||||||
|
b.write("0123456789ABCDEF", 16);
|
||||||
|
|
||||||
|
CU_ASSERT(16 == b.rleft());
|
||||||
|
CU_ASSERT(0 == b.wleft());
|
||||||
|
CU_ASSERT(3 == b.pos);
|
||||||
|
CU_ASSERT(16 == b.len);
|
||||||
|
CU_ASSERT(0 == memcmp(b.begin, "DEF0123456789ABC", 16));
|
||||||
|
|
||||||
|
const void *p;
|
||||||
|
size_t len;
|
||||||
|
std::tie(p, len) = b.get();
|
||||||
|
CU_ASSERT(13 == len);
|
||||||
|
CU_ASSERT(0 == memcmp(p, "0123456789ABC", len));
|
||||||
|
|
||||||
|
b.drain(14);
|
||||||
|
|
||||||
|
CU_ASSERT(2 == b.rleft());
|
||||||
|
CU_ASSERT(14 == b.wleft());
|
||||||
|
CU_ASSERT(1 == b.pos);
|
||||||
|
CU_ASSERT(2 == b.len);
|
||||||
|
|
||||||
|
std::tie(p, len) = b.get();
|
||||||
|
CU_ASSERT(2 == len);
|
||||||
|
CU_ASSERT(0 == memcmp(p, "EF", len));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_ringbuf_iovec(void) {
|
||||||
|
RingBuf<16> b;
|
||||||
|
struct iovec iov[2];
|
||||||
|
|
||||||
|
auto rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin == iov[0].iov_base);
|
||||||
|
CU_ASSERT(16 == iov[0].iov_len);
|
||||||
|
|
||||||
|
// set pos to somewhere middle of the buffer, this will require 2
|
||||||
|
// iovec for writing.
|
||||||
|
b.pos = 6;
|
||||||
|
|
||||||
|
rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == rv);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(2 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.pos == iov[0].iov_base);
|
||||||
|
CU_ASSERT(10 == iov[0].iov_len);
|
||||||
|
CU_ASSERT(b.begin == iov[1].iov_base);
|
||||||
|
CU_ASSERT(6 == iov[1].iov_len);
|
||||||
|
|
||||||
|
// occupy first region of buffer
|
||||||
|
b.pos = 0;
|
||||||
|
b.len = 10;
|
||||||
|
|
||||||
|
rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin == iov[0].iov_base);
|
||||||
|
CU_ASSERT(10 == iov[0].iov_len);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.len == iov[0].iov_base);
|
||||||
|
CU_ASSERT(6 == iov[0].iov_len);
|
||||||
|
|
||||||
|
// occupy last region of buffer
|
||||||
|
b.pos = 6;
|
||||||
|
b.len = 10;
|
||||||
|
|
||||||
|
rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.pos == iov[0].iov_base);
|
||||||
|
CU_ASSERT(10 == iov[0].iov_len);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin == iov[0].iov_base);
|
||||||
|
CU_ASSERT(6 == iov[0].iov_len);
|
||||||
|
|
||||||
|
// occupy middle of buffer
|
||||||
|
b.pos = 3;
|
||||||
|
b.len = 10;
|
||||||
|
|
||||||
|
rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.pos == iov[0].iov_base);
|
||||||
|
CU_ASSERT(10 == iov[0].iov_len);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(2 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.pos + b.len == iov[0].iov_base);
|
||||||
|
CU_ASSERT(3 == iov[0].iov_len);
|
||||||
|
CU_ASSERT(b.begin == iov[1].iov_base);
|
||||||
|
CU_ASSERT(3 == iov[1].iov_len);
|
||||||
|
|
||||||
|
// crossover
|
||||||
|
b.pos = 13;
|
||||||
|
b.len = 10;
|
||||||
|
|
||||||
|
rv = b.riovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(2 == rv);
|
||||||
|
CU_ASSERT(b.begin + b.pos == iov[0].iov_base);
|
||||||
|
CU_ASSERT(3 == iov[0].iov_len);
|
||||||
|
CU_ASSERT(b.begin == iov[1].iov_base);
|
||||||
|
CU_ASSERT(7 == iov[1].iov_len);
|
||||||
|
|
||||||
|
rv = b.wiovec(iov);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == rv);
|
||||||
|
CU_ASSERT(b.begin + 7 == iov[0].iov_base);
|
||||||
|
CU_ASSERT(6 == iov[0].iov_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef RINGBUF_TEST_H
|
||||||
|
#define RINGBUF_TEST_H
|
||||||
|
|
||||||
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
void test_ringbuf_write(void);
|
||||||
|
void test_ringbuf_iovec(void);
|
||||||
|
|
||||||
|
} // namespace nghttp2
|
||||||
|
|
||||||
|
#endif // RINGBUF_TEST_H
|
|
@ -38,6 +38,8 @@
|
||||||
#include "http2_test.h"
|
#include "http2_test.h"
|
||||||
#include "util_test.h"
|
#include "util_test.h"
|
||||||
#include "nghttp2_gzip_test.h"
|
#include "nghttp2_gzip_test.h"
|
||||||
|
#include "ringbuf_test.h"
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
|
||||||
static int init_suite1(void) { return 0; }
|
static int init_suite1(void) { return 0; }
|
||||||
|
|
||||||
|
@ -51,6 +53,8 @@ int main(int argc, char *argv[]) {
|
||||||
SSL_load_error_strings();
|
SSL_load_error_strings();
|
||||||
SSL_library_init();
|
SSL_library_init();
|
||||||
|
|
||||||
|
shrpx::create_config();
|
||||||
|
|
||||||
// initialize the CUnit test registry
|
// initialize the CUnit test registry
|
||||||
if (CUE_SUCCESS != CU_initialize_registry())
|
if (CUE_SUCCESS != CU_initialize_registry())
|
||||||
return CU_get_error();
|
return CU_get_error();
|
||||||
|
@ -68,35 +72,40 @@ int main(int argc, char *argv[]) {
|
||||||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
|
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
|
||||||
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
|
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
|
||||||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
|
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
|
||||||
!CU_add_test(pSuite, "http2_check_http2_headers",
|
|
||||||
shrpx::test_http2_check_http2_headers) ||
|
|
||||||
!CU_add_test(pSuite, "http2_get_unique_header",
|
|
||||||
shrpx::test_http2_get_unique_header) ||
|
|
||||||
!CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
|
!CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
|
||||||
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
|
!CU_add_test(pSuite, "http2_copy_headers_to_nva",
|
||||||
shrpx::test_http2_copy_norm_headers_to_nva) ||
|
shrpx::test_http2_copy_headers_to_nva) ||
|
||||||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
|
!CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
|
||||||
shrpx::test_http2_build_http1_headers_from_norm_headers) ||
|
shrpx::test_http2_build_http1_headers_from_headers) ||
|
||||||
!CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
|
!CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
|
||||||
!CU_add_test(pSuite, "http2_rewrite_location_uri",
|
!CU_add_test(pSuite, "http2_rewrite_location_uri",
|
||||||
shrpx::test_http2_rewrite_location_uri) ||
|
shrpx::test_http2_rewrite_location_uri) ||
|
||||||
!CU_add_test(pSuite, "http2_parse_http_status_code",
|
!CU_add_test(pSuite, "http2_parse_http_status_code",
|
||||||
shrpx::test_http2_parse_http_status_code) ||
|
shrpx::test_http2_parse_http_status_code) ||
|
||||||
!CU_add_test(pSuite, "downstream_normalize_request_headers",
|
!CU_add_test(pSuite, "http2_index_header",
|
||||||
shrpx::test_downstream_normalize_request_headers) ||
|
shrpx::test_http2_index_header) ||
|
||||||
!CU_add_test(pSuite, "downstream_normalize_response_headers",
|
!CU_add_test(pSuite, "http2_lookup_token",
|
||||||
shrpx::test_downstream_normalize_response_headers) ||
|
shrpx::test_http2_lookup_token) ||
|
||||||
!CU_add_test(pSuite, "downstream_get_norm_request_header",
|
!CU_add_test(pSuite, "http2_check_http2_pseudo_header",
|
||||||
shrpx::test_downstream_get_norm_request_header) ||
|
shrpx::test_http2_check_http2_pseudo_header) ||
|
||||||
!CU_add_test(pSuite, "downstream_get_norm_response_header",
|
!CU_add_test(pSuite, "http2_http2_header_allowed",
|
||||||
shrpx::test_downstream_get_norm_response_header) ||
|
shrpx::test_http2_http2_header_allowed) ||
|
||||||
|
!CU_add_test(pSuite, "http2_mandatory_request_headers_presence",
|
||||||
|
shrpx::test_http2_mandatory_request_headers_presence) ||
|
||||||
|
!CU_add_test(pSuite, "downstream_index_request_headers",
|
||||||
|
shrpx::test_downstream_index_request_headers) ||
|
||||||
|
!CU_add_test(pSuite, "downstream_index_response_headers",
|
||||||
|
shrpx::test_downstream_index_response_headers) ||
|
||||||
|
!CU_add_test(pSuite, "downstream_get_request_header",
|
||||||
|
shrpx::test_downstream_get_request_header) ||
|
||||||
|
!CU_add_test(pSuite, "downstream_get_response_header",
|
||||||
|
shrpx::test_downstream_get_response_header) ||
|
||||||
!CU_add_test(pSuite, "downstream_crumble_request_cookie",
|
!CU_add_test(pSuite, "downstream_crumble_request_cookie",
|
||||||
shrpx::test_downstream_crumble_request_cookie) ||
|
shrpx::test_downstream_crumble_request_cookie) ||
|
||||||
!CU_add_test(pSuite, "downstream_assemble_request_cookie",
|
!CU_add_test(pSuite, "downstream_assemble_request_cookie",
|
||||||
shrpx::test_downstream_assemble_request_cookie) ||
|
shrpx::test_downstream_assemble_request_cookie) ||
|
||||||
!CU_add_test(
|
!CU_add_test(pSuite, "downstream_rewrite_location_response_header",
|
||||||
pSuite, "downstream_rewrite_norm_location_response_header",
|
shrpx::test_downstream_rewrite_location_response_header) ||
|
||||||
shrpx::test_downstream_rewrite_norm_location_response_header) ||
|
|
||||||
!CU_add_test(pSuite, "config_parse_config_str_list",
|
!CU_add_test(pSuite, "config_parse_config_str_list",
|
||||||
shrpx::test_shrpx_config_parse_config_str_list) ||
|
shrpx::test_shrpx_config_parse_config_str_list) ||
|
||||||
!CU_add_test(pSuite, "config_parse_header",
|
!CU_add_test(pSuite, "config_parse_header",
|
||||||
|
@ -115,7 +124,9 @@ int main(int argc, char *argv[]) {
|
||||||
!CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) ||
|
!CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) ||
|
||||||
!CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) ||
|
!CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) ||
|
||||||
!CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
|
!CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
|
||||||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate)) {
|
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||||
|
!CU_add_test(pSuite, "ringbuf_write", nghttp2::test_ringbuf_write) ||
|
||||||
|
!CU_add_test(pSuite, "ringbuf_iovec", nghttp2::test_ringbuf_iovec)) {
|
||||||
CU_cleanup_registry();
|
CU_cleanup_registry();
|
||||||
return CU_get_error();
|
return CU_get_error();
|
||||||
}
|
}
|
||||||
|
|
331
src/shrpx.cc
331
src/shrpx.cc
|
@ -48,7 +48,7 @@
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
|
|
||||||
#include <event2/listener.h>
|
#include <ev.h>
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_worker_config.h"
|
#include "shrpx_worker_config.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
|
#include "shrpx_accept_handler.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "app_helper.h"
|
#include "app_helper.h"
|
||||||
#include "ssl.h"
|
#include "ssl.h"
|
||||||
|
@ -82,13 +83,14 @@ const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
|
||||||
// binary is listening to.
|
// binary is listening to.
|
||||||
#define ENV_PORT "NGHTTPX_PORT"
|
#define ENV_PORT "NGHTTPX_PORT"
|
||||||
|
|
||||||
namespace {
|
// namespace {
|
||||||
void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int addrlen,
|
// void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int
|
||||||
void *arg) {
|
// addrlen,
|
||||||
auto handler = static_cast<ListenHandler *>(arg);
|
// void *arg) {
|
||||||
handler->accept_connection(fd, addr, addrlen);
|
// auto handler = static_cast<ListenHandler *>(arg);
|
||||||
}
|
// handler->accept_connection(fd, addr, addrlen);
|
||||||
} // namespace
|
// }
|
||||||
|
// } // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
bool is_ipv6_numeric_addr(const char *host) {
|
bool is_ipv6_numeric_addr(const char *host) {
|
||||||
|
@ -145,28 +147,8 @@ int resolve_hostname(sockaddr_union *addr, size_t *addrlen,
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void evlistener_errorcb(evconnlistener *listener, void *ptr) {
|
std::unique_ptr<AcceptHandler> create_acceptor(ListenHandler *handler,
|
||||||
LOG(ERROR) << "Accepting incoming connection failed";
|
int family) {
|
||||||
|
|
||||||
auto listener_handler = static_cast<ListenHandler *>(ptr);
|
|
||||||
|
|
||||||
listener_handler->disable_evlistener_temporary(
|
|
||||||
&get_config()->listener_disable_timeout);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
evconnlistener *new_evlistener(ListenHandler *handler, int fd) {
|
|
||||||
auto evlistener = evconnlistener_new(
|
|
||||||
handler->get_evbase(), ssl_acceptcb, handler,
|
|
||||||
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, get_config()->backlog, fd);
|
|
||||||
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
|
||||||
return evlistener;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|
||||||
{
|
{
|
||||||
auto envfd =
|
auto envfd =
|
||||||
getenv(family == AF_INET ? ENV_LISTENER4_FD : ENV_LISTENER6_FD);
|
getenv(family == AF_INET ? ENV_LISTENER4_FD : ENV_LISTENER6_FD);
|
||||||
|
@ -182,7 +164,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||||
if (port == get_config()->port) {
|
if (port == get_config()->port) {
|
||||||
LOG(NOTICE) << "Listening on port " << get_config()->port;
|
LOG(NOTICE) << "Listening on port " << get_config()->port;
|
||||||
|
|
||||||
return new_evlistener(handler, fd);
|
return util::make_unique<AcceptHandler>(fd, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG(WARN) << "Port was changed between old binary (" << port
|
LOG(WARN) << "Port was changed between old binary (" << port
|
||||||
|
@ -219,7 +201,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
for (rp = res; rp; rp = rp->ai_next) {
|
for (rp = res; rp; rp = rp->ai_next) {
|
||||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
fd =
|
||||||
|
socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -229,7 +212,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||||
close(fd);
|
close(fd);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
evutil_make_socket_nonblocking(fd);
|
|
||||||
#ifdef IPV6_V6ONLY
|
#ifdef IPV6_V6ONLY
|
||||||
if (family == AF_INET6) {
|
if (family == AF_INET6) {
|
||||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
||||||
|
@ -239,7 +222,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // IPV6_V6ONLY
|
#endif // IPV6_V6ONLY
|
||||||
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 &&
|
||||||
|
listen(fd, get_config()->backlog) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
@ -270,7 +254,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||||
|
|
||||||
LOG(NOTICE) << "Listening on " << host << ", port " << get_config()->port;
|
LOG(NOTICE) << "Listening on " << host << ", port " << get_config()->port;
|
||||||
|
|
||||||
return new_evlistener(handler, fd);
|
return util::make_unique<AcceptHandler>(fd, handler);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -317,8 +301,8 @@ void save_pid() {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config << ")";
|
LOG(INFO) << "Reopening log files: worker_info(" << worker_config << ")";
|
||||||
|
@ -333,8 +317,8 @@ void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
void exec_binary_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||||
|
|
||||||
LOG(NOTICE) << "Executing new binary";
|
LOG(NOTICE) << "Executing new binary";
|
||||||
|
|
||||||
|
@ -373,17 +357,17 @@ void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||||
auto envp = util::make_unique<char *[]>(envlen + 3 + 1);
|
auto envp = util::make_unique<char *[]>(envlen + 3 + 1);
|
||||||
size_t envidx = 0;
|
size_t envidx = 0;
|
||||||
|
|
||||||
auto evlistener4 = listener_handler->get_evlistener4();
|
auto acceptor4 = listener_handler->get_acceptor4();
|
||||||
if (evlistener4) {
|
if (acceptor4) {
|
||||||
std::string fd4 = ENV_LISTENER4_FD "=";
|
std::string fd4 = ENV_LISTENER4_FD "=";
|
||||||
fd4 += util::utos(evconnlistener_get_fd(evlistener4));
|
fd4 += util::utos(acceptor4->get_fd());
|
||||||
envp[envidx++] = strdup(fd4.c_str());
|
envp[envidx++] = strdup(fd4.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evlistener6 = listener_handler->get_evlistener6();
|
auto acceptor6 = listener_handler->get_acceptor6();
|
||||||
if (evlistener6) {
|
if (acceptor6) {
|
||||||
std::string fd6 = ENV_LISTENER6_FD "=";
|
std::string fd6 = ENV_LISTENER6_FD "=";
|
||||||
fd6 += util::utos(evconnlistener_get_fd(evlistener6));
|
fd6 += util::utos(acceptor6->get_fd());
|
||||||
envp[envidx++] = strdup(fd6.c_str());
|
envp[envidx++] = strdup(fd6.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,14 +407,15 @@ void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
|
||||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
int revents) {
|
||||||
|
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||||
|
|
||||||
LOG(NOTICE) << "Graceful shutdown signal received";
|
LOG(NOTICE) << "Graceful shutdown signal received";
|
||||||
|
|
||||||
worker_config->graceful_shutdown = true;
|
worker_config->graceful_shutdown = true;
|
||||||
|
|
||||||
listener_handler->disable_evlistener();
|
listener_handler->disable_acceptor();
|
||||||
|
|
||||||
// After disabling accepting new connection, disptach incoming
|
// After disabling accepting new connection, disptach incoming
|
||||||
// connection in backlog.
|
// connection in backlog.
|
||||||
|
@ -438,6 +423,10 @@ void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||||
listener_handler->accept_pending_connection();
|
listener_handler->accept_pending_connection();
|
||||||
|
|
||||||
listener_handler->graceful_shutdown_worker();
|
listener_handler->graceful_shutdown_worker();
|
||||||
|
|
||||||
|
// We have accepted all pending connections. Shutdown main event
|
||||||
|
// loop.
|
||||||
|
ev_break(loop);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -449,8 +438,8 @@ std::unique_ptr<std::string> generate_time() {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void refresh_cb(evutil_socket_t sig, short events, void *arg) {
|
void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||||
auto worker_stat = listener_handler->get_worker_stat();
|
auto worker_stat = listener_handler->get_worker_stat();
|
||||||
|
|
||||||
mod_config()->cached_time = generate_time();
|
mod_config()->cached_time = generate_time();
|
||||||
|
@ -458,20 +447,14 @@ void refresh_cb(evutil_socket_t sig, short events, void *arg) {
|
||||||
// wait for event notification to workers to finish.
|
// wait for event notification to workers to finish.
|
||||||
if (get_config()->num_worker == 1 && worker_config->graceful_shutdown &&
|
if (get_config()->num_worker == 1 && worker_config->graceful_shutdown &&
|
||||||
(!worker_stat || worker_stat->num_connections == 0)) {
|
(!worker_stat || worker_stat->num_connections == 0)) {
|
||||||
event_base_loopbreak(listener_handler->get_evbase());
|
ev_break(loop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int event_loop() {
|
int event_loop() {
|
||||||
int rv;
|
auto loop = EV_DEFAULT;
|
||||||
|
|
||||||
auto evbase = event_base_new();
|
|
||||||
if (!evbase) {
|
|
||||||
LOG(FATAL) << "event_base_new() failed";
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
SSL_CTX *sv_ssl_ctx, *cl_ssl_ctx;
|
SSL_CTX *sv_ssl_ctx, *cl_ssl_ctx;
|
||||||
|
|
||||||
if (get_config()->client_mode) {
|
if (get_config()->client_mode) {
|
||||||
|
@ -487,7 +470,8 @@ int event_loop() {
|
||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto listener_handler = new ListenHandler(evbase, sv_ssl_ctx, cl_ssl_ctx);
|
auto listener_handler =
|
||||||
|
util::make_unique<ListenHandler>(loop, sv_ssl_ctx, cl_ssl_ctx);
|
||||||
if (get_config()->daemon) {
|
if (get_config()->daemon) {
|
||||||
if (daemon(0, 0) == -1) {
|
if (daemon(0, 0) == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
|
@ -503,22 +487,23 @@ int event_loop() {
|
||||||
save_pid();
|
save_pid();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evlistener6 = create_evlistener(listener_handler, AF_INET6);
|
auto acceptor6 = create_acceptor(listener_handler.get(), AF_INET6);
|
||||||
auto evlistener4 = create_evlistener(listener_handler, AF_INET);
|
auto acceptor4 = create_acceptor(listener_handler.get(), AF_INET);
|
||||||
if (!evlistener6 && !evlistener4) {
|
if (!acceptor6 && !acceptor4) {
|
||||||
LOG(FATAL) << "Failed to listen on address " << get_config()->host.get()
|
LOG(FATAL) << "Failed to listen on address " << get_config()->host.get()
|
||||||
<< ", port " << get_config()->port;
|
<< ", port " << get_config()->port;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
listener_handler->set_evlistener4(evlistener4);
|
listener_handler->set_acceptor4(std::move(acceptor4));
|
||||||
listener_handler->set_evlistener6(evlistener6);
|
listener_handler->set_acceptor6(std::move(acceptor6));
|
||||||
|
|
||||||
// ListenHandler loads private key, and we listen on a priveleged port.
|
// ListenHandler loads private key, and we listen on a priveleged port.
|
||||||
// After that, we drop the root privileges if needed.
|
// After that, we drop the root privileges if needed.
|
||||||
drop_privileges();
|
drop_privileges();
|
||||||
|
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
|
int rv;
|
||||||
sigset_t signals;
|
sigset_t signals;
|
||||||
sigemptyset(&signals);
|
sigemptyset(&signals);
|
||||||
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
||||||
|
@ -545,83 +530,35 @@ int event_loop() {
|
||||||
}
|
}
|
||||||
#endif // !NOTHREADS
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
auto reopen_log_signal_event = evsignal_new(
|
ev_signal reopen_log_sig;
|
||||||
evbase, REOPEN_LOG_SIGNAL, reopen_log_signal_cb, listener_handler);
|
ev_signal_init(&reopen_log_sig, reopen_log_signal_cb, REOPEN_LOG_SIGNAL);
|
||||||
|
reopen_log_sig.data = listener_handler.get();
|
||||||
|
ev_signal_start(loop, &reopen_log_sig);
|
||||||
|
|
||||||
if (!reopen_log_signal_event) {
|
ev_signal exec_bin_sig;
|
||||||
LOG(ERROR) << "evsignal_new failed";
|
ev_signal_init(&exec_bin_sig, exec_binary_signal_cb, EXEC_BINARY_SIGNAL);
|
||||||
} else {
|
exec_bin_sig.data = listener_handler.get();
|
||||||
rv = event_add(reopen_log_signal_event, nullptr);
|
ev_signal_start(loop, &exec_bin_sig);
|
||||||
if (rv < 0) {
|
|
||||||
LOG(ERROR) << "event_add for reopen_log_signal_event failed";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto exec_binary_signal_event = evsignal_new(
|
ev_signal graceful_shutdown_sig;
|
||||||
evbase, EXEC_BINARY_SIGNAL, exec_binary_signal_cb, listener_handler);
|
ev_signal_init(&graceful_shutdown_sig, graceful_shutdown_signal_cb,
|
||||||
rv = event_add(exec_binary_signal_event, nullptr);
|
GRACEFUL_SHUTDOWN_SIGNAL);
|
||||||
|
graceful_shutdown_sig.data = listener_handler.get();
|
||||||
|
ev_signal_start(loop, &graceful_shutdown_sig);
|
||||||
|
|
||||||
if (rv == -1) {
|
ev_timer refresh_timer;
|
||||||
LOG(FATAL) << "event_add for exec_binary_signal_event failed";
|
ev_timer_init(&refresh_timer, refresh_cb, 0., 1.);
|
||||||
|
refresh_timer.data = listener_handler.get();
|
||||||
exit(EXIT_FAILURE);
|
ev_timer_again(loop, &refresh_timer);
|
||||||
}
|
|
||||||
|
|
||||||
auto graceful_shutdown_signal_event =
|
|
||||||
evsignal_new(evbase, GRACEFUL_SHUTDOWN_SIGNAL,
|
|
||||||
graceful_shutdown_signal_cb, listener_handler);
|
|
||||||
|
|
||||||
rv = event_add(graceful_shutdown_signal_event, nullptr);
|
|
||||||
|
|
||||||
if (rv == -1) {
|
|
||||||
LOG(FATAL) << "event_add for graceful_shutdown_signal_event failed";
|
|
||||||
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto refresh_event =
|
|
||||||
event_new(evbase, -1, EV_PERSIST, refresh_cb, listener_handler);
|
|
||||||
|
|
||||||
if (!refresh_event) {
|
|
||||||
LOG(ERROR) << "event_new failed";
|
|
||||||
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeval refresh_timeout = {1, 0};
|
|
||||||
rv = event_add(refresh_event, &refresh_timeout);
|
|
||||||
|
|
||||||
if (rv == -1) {
|
|
||||||
LOG(ERROR) << "Adding refresh_event failed";
|
|
||||||
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Entering event loop";
|
LOG(INFO) << "Entering event loop";
|
||||||
}
|
}
|
||||||
event_base_loop(evbase, 0);
|
|
||||||
|
ev_run(loop, 0);
|
||||||
|
|
||||||
listener_handler->join_worker();
|
listener_handler->join_worker();
|
||||||
|
|
||||||
if (refresh_event) {
|
|
||||||
event_free(refresh_event);
|
|
||||||
}
|
|
||||||
if (graceful_shutdown_signal_event) {
|
|
||||||
event_free(graceful_shutdown_signal_event);
|
|
||||||
}
|
|
||||||
if (exec_binary_signal_event) {
|
|
||||||
event_free(exec_binary_signal_event);
|
|
||||||
}
|
|
||||||
if (reopen_log_signal_event) {
|
|
||||||
event_free(reopen_log_signal_event);
|
|
||||||
}
|
|
||||||
if (evlistener4) {
|
|
||||||
evconnlistener_free(evlistener4);
|
|
||||||
}
|
|
||||||
if (evlistener6) {
|
|
||||||
evconnlistener_free(evlistener6);
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -673,26 +610,26 @@ void fill_default_config() {
|
||||||
mod_config()->cert_file = nullptr;
|
mod_config()->cert_file = nullptr;
|
||||||
|
|
||||||
// Read timeout for HTTP2 upstream connection
|
// Read timeout for HTTP2 upstream connection
|
||||||
mod_config()->http2_upstream_read_timeout = {180, 0};
|
mod_config()->http2_upstream_read_timeout = 180.;
|
||||||
|
|
||||||
// Read timeout for non-HTTP2 upstream connection
|
// Read timeout for non-HTTP2 upstream connection
|
||||||
mod_config()->upstream_read_timeout = {180, 0};
|
mod_config()->upstream_read_timeout = 180.;
|
||||||
|
|
||||||
// Write timeout for HTTP2/non-HTTP2 upstream connection
|
// Write timeout for HTTP2/non-HTTP2 upstream connection
|
||||||
mod_config()->upstream_write_timeout = {30, 0};
|
mod_config()->upstream_write_timeout = 30.;
|
||||||
|
|
||||||
// Read/Write timeouts for downstream connection
|
// Read/Write timeouts for downstream connection
|
||||||
mod_config()->downstream_read_timeout = {180, 0};
|
mod_config()->downstream_read_timeout = 180.;
|
||||||
mod_config()->downstream_write_timeout = {30, 0};
|
mod_config()->downstream_write_timeout = 30.;
|
||||||
|
|
||||||
// Read timeout for HTTP/2 stream
|
// Read timeout for HTTP/2 stream
|
||||||
mod_config()->stream_read_timeout = {0, 0};
|
mod_config()->stream_read_timeout = 0.;
|
||||||
|
|
||||||
// Write timeout for HTTP/2 stream
|
// Write timeout for HTTP/2 stream
|
||||||
mod_config()->stream_write_timeout = {0, 0};
|
mod_config()->stream_write_timeout = 0.;
|
||||||
|
|
||||||
// Timeout for pooled (idle) connections
|
// Timeout for pooled (idle) connections
|
||||||
mod_config()->downstream_idle_read_timeout = {600, 0};
|
mod_config()->downstream_idle_read_timeout = 600.;
|
||||||
|
|
||||||
// window bits for HTTP/2 and SPDY upstream/downstream connection
|
// window bits for HTTP/2 and SPDY upstream/downstream connection
|
||||||
// per stream. 2**16-1 = 64KiB-1, which is HTTP/2 default. Please
|
// per stream. 2**16-1 = 64KiB-1, which is HTTP/2 default. Please
|
||||||
|
@ -726,7 +663,7 @@ void fill_default_config() {
|
||||||
mod_config()->conf_path = strcopy("/etc/nghttpx/nghttpx.conf");
|
mod_config()->conf_path = strcopy("/etc/nghttpx/nghttpx.conf");
|
||||||
mod_config()->syslog_facility = LOG_DAEMON;
|
mod_config()->syslog_facility = LOG_DAEMON;
|
||||||
// Default accept() backlog
|
// Default accept() backlog
|
||||||
mod_config()->backlog = -1;
|
mod_config()->backlog = SOMAXCONN;
|
||||||
mod_config()->ciphers = nullptr;
|
mod_config()->ciphers = nullptr;
|
||||||
mod_config()->http2_proxy = false;
|
mod_config()->http2_proxy = false;
|
||||||
mod_config()->http2_bridge = false;
|
mod_config()->http2_bridge = false;
|
||||||
|
@ -746,16 +683,14 @@ void fill_default_config() {
|
||||||
mod_config()->downstream_http_proxy_host = nullptr;
|
mod_config()->downstream_http_proxy_host = nullptr;
|
||||||
mod_config()->downstream_http_proxy_port = 0;
|
mod_config()->downstream_http_proxy_port = 0;
|
||||||
mod_config()->downstream_http_proxy_addrlen = 0;
|
mod_config()->downstream_http_proxy_addrlen = 0;
|
||||||
mod_config()->rate_limit_cfg = nullptr;
|
|
||||||
mod_config()->read_rate = 0;
|
mod_config()->read_rate = 0;
|
||||||
mod_config()->read_burst = 1 << 30;
|
mod_config()->read_burst = 0;
|
||||||
mod_config()->write_rate = 0;
|
mod_config()->write_rate = 0;
|
||||||
mod_config()->write_burst = 0;
|
mod_config()->write_burst = 0;
|
||||||
mod_config()->worker_read_rate = 0;
|
mod_config()->worker_read_rate = 0;
|
||||||
mod_config()->worker_read_burst = 0;
|
mod_config()->worker_read_burst = 0;
|
||||||
mod_config()->worker_write_rate = 0;
|
mod_config()->worker_write_rate = 0;
|
||||||
mod_config()->worker_write_burst = 0;
|
mod_config()->worker_write_burst = 0;
|
||||||
mod_config()->worker_rate_limit_cfg = nullptr;
|
|
||||||
mod_config()->verify_client = false;
|
mod_config()->verify_client = false;
|
||||||
mod_config()->verify_client_cacert = nullptr;
|
mod_config()->verify_client_cacert = nullptr;
|
||||||
mod_config()->client_private_key_file = nullptr;
|
mod_config()->client_private_key_file = nullptr;
|
||||||
|
@ -777,19 +712,20 @@ void fill_default_config() {
|
||||||
mod_config()->argc = 0;
|
mod_config()->argc = 0;
|
||||||
mod_config()->argv = nullptr;
|
mod_config()->argv = nullptr;
|
||||||
mod_config()->downstream_connections_per_host = 8;
|
mod_config()->downstream_connections_per_host = 8;
|
||||||
mod_config()->listener_disable_timeout = {0, 0};
|
mod_config()->downstream_connections_per_frontend = 0;
|
||||||
|
mod_config()->listener_disable_timeout = 0.;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
// namespace {
|
||||||
size_t get_rate_limit(size_t rate_limit) {
|
// size_t get_rate_limit(size_t rate_limit) {
|
||||||
if (rate_limit == 0) {
|
// if (rate_limit == 0) {
|
||||||
return EV_RATE_LIMIT_MAX;
|
// return EV_RATE_LIMIT_MAX;
|
||||||
} else {
|
// } else {
|
||||||
return rate_limit;
|
// return rate_limit;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} // namespace
|
// } // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void print_version(std::ostream &out) {
|
void print_version(std::ostream &out) {
|
||||||
|
@ -864,10 +800,8 @@ Performance:
|
||||||
Default: )" << get_config()->read_rate << R"(
|
Default: )" << get_config()->read_rate << R"(
|
||||||
--read-burst=<SIZE>
|
--read-burst=<SIZE>
|
||||||
Set maximum read burst size on frontend
|
Set maximum read burst size on frontend
|
||||||
connection. Setting 0 does not work, but it is
|
connection. Setting 0 to this option means read
|
||||||
not a problem because --read-rate=0 will give
|
burst size is unlimited.
|
||||||
unlimited read rate regardless of this option
|
|
||||||
value.
|
|
||||||
Default: )" << get_config()->read_burst << R"(
|
Default: )" << get_config()->read_burst << R"(
|
||||||
--write-rate=<RATE>
|
--write-rate=<RATE>
|
||||||
Set maximum average write rate on frontend
|
Set maximum average write rate on frontend
|
||||||
|
@ -882,22 +816,26 @@ Performance:
|
||||||
--worker-read-rate=<RATE>
|
--worker-read-rate=<RATE>
|
||||||
Set maximum average read rate on frontend
|
Set maximum average read rate on frontend
|
||||||
connection per worker. Setting 0 to this option
|
connection per worker. Setting 0 to this option
|
||||||
means read rate is unlimited.
|
means read rate is unlimited. Not implemented
|
||||||
|
yet.
|
||||||
Default: )" << get_config()->worker_read_rate << R"(
|
Default: )" << get_config()->worker_read_rate << R"(
|
||||||
--worker-read-burst=<SIZE>
|
--worker-read-burst=<SIZE>
|
||||||
Set maximum read burst size on frontend
|
Set maximum read burst size on frontend
|
||||||
connection per worker. Setting 0 to this option
|
connection per worker. Setting 0 to this option
|
||||||
means read burst size is unlimited.
|
means read burst size is unlimited. Not
|
||||||
|
implemented yet.
|
||||||
Default: )" << get_config()->worker_read_burst << R"(
|
Default: )" << get_config()->worker_read_burst << R"(
|
||||||
--worker-write-rate=<RATE>
|
--worker-write-rate=<RATE>
|
||||||
Set maximum average write rate on frontend
|
Set maximum average write rate on frontend
|
||||||
connection per worker. Setting 0 to this option
|
connection per worker. Setting 0 to this option
|
||||||
means write rate is unlimited.
|
means write rate is unlimited. Not implemented
|
||||||
|
yet.
|
||||||
Default: )" << get_config()->worker_write_rate << R"(
|
Default: )" << get_config()->worker_write_rate << R"(
|
||||||
--worker-write-burst=<SIZE>
|
--worker-write-burst=<SIZE>
|
||||||
Set maximum write burst size on frontend
|
Set maximum write burst size on frontend
|
||||||
connection per worker. Setting 0 to this option
|
connection per worker. Setting 0 to this option
|
||||||
means write burst size is unlimited.
|
means write burst size is unlimited. Not
|
||||||
|
implemented yet.
|
||||||
Default: )" << get_config()->worker_write_burst << R"(
|
Default: )" << get_config()->worker_write_burst << R"(
|
||||||
--worker-frontend-connections=<NUM>
|
--worker-frontend-connections=<NUM>
|
||||||
Set maximum number of simultaneous connections
|
Set maximum number of simultaneous connections
|
||||||
|
@ -906,55 +844,61 @@ Performance:
|
||||||
--backend-http1-connections-per-host=<NUM>
|
--backend-http1-connections-per-host=<NUM>
|
||||||
Set maximum number of backend concurrent HTTP/1
|
Set maximum number of backend concurrent HTTP/1
|
||||||
connections per host. This option is meaningful
|
connections per host. This option is meaningful
|
||||||
when -s option is used.
|
when -s option is used. To limit the number of
|
||||||
|
connections per frontend for default mode, use
|
||||||
|
--backend-http1-connections-per-frontend.
|
||||||
Default: )"
|
Default: )"
|
||||||
<< get_config()->downstream_connections_per_host << R"(
|
<< get_config()->downstream_connections_per_host << R"(
|
||||||
|
--backend-http1-connections-per-frontend=<NUM>
|
||||||
|
Set maximum number of backend concurrent HTTP/1
|
||||||
|
connections per frontend. This option is only
|
||||||
|
used for default mode. 0 means unlimited. To
|
||||||
|
limit the number of connections per host for
|
||||||
|
HTTP/2 or SPDY proxy mode (-s option), use
|
||||||
|
--backend-http1-connections-per-host.
|
||||||
|
Default: )"
|
||||||
|
<< get_config()->downstream_connections_per_frontend << R"(
|
||||||
|
|
||||||
Timeout:
|
Timeout:
|
||||||
--frontend-http2-read-timeout=<SEC>
|
--frontend-http2-read-timeout=<SEC>
|
||||||
Specify read timeout for HTTP/2 and SPDY frontend
|
Specify read timeout for HTTP/2 and SPDY frontend
|
||||||
connection.
|
connection.
|
||||||
Default: )"
|
Default: )" << get_config()->http2_upstream_read_timeout
|
||||||
<< get_config()->http2_upstream_read_timeout.tv_sec << R"(
|
<< R"(
|
||||||
--frontend-read-timeout=<SEC>
|
--frontend-read-timeout=<SEC>
|
||||||
Specify read timeout for HTTP/1.1 frontend
|
Specify read timeout for HTTP/1.1 frontend
|
||||||
connection.
|
connection.
|
||||||
Default: )" << get_config()->upstream_read_timeout.tv_sec
|
Default: )" << get_config()->upstream_read_timeout << R"(
|
||||||
<< R"(
|
|
||||||
--frontend-write-timeout=<SEC>
|
--frontend-write-timeout=<SEC>
|
||||||
Specify write timeout for all frontend
|
Specify write timeout for all frontend
|
||||||
connections.
|
connections.
|
||||||
Default: )" << get_config()->upstream_write_timeout.tv_sec
|
Default: )" << get_config()->upstream_write_timeout << R"(
|
||||||
<< R"(
|
|
||||||
--stream-read-timeout=<SEC>
|
--stream-read-timeout=<SEC>
|
||||||
Specify read timeout for HTTP/2 and SPDY streams.
|
Specify read timeout for HTTP/2 and SPDY streams.
|
||||||
0 means no timeout.
|
0 means no timeout.
|
||||||
Default: )" << get_config()->stream_read_timeout.tv_sec
|
Default: )" << get_config()->stream_read_timeout << R"(
|
||||||
<< R"(
|
|
||||||
--stream-write-timeout=<SEC>
|
--stream-write-timeout=<SEC>
|
||||||
Specify write timeout for HTTP/2 and SPDY
|
Specify write timeout for HTTP/2 and SPDY
|
||||||
streams. 0 means no timeout.
|
streams. 0 means no timeout.
|
||||||
Default: )" << get_config()->stream_write_timeout.tv_sec
|
Default: )" << get_config()->stream_write_timeout << R"(
|
||||||
<< R"(
|
|
||||||
--backend-read-timeout=<SEC>
|
--backend-read-timeout=<SEC>
|
||||||
Specify read timeout for backend connection.
|
Specify read timeout for backend connection.
|
||||||
Default: )" << get_config()->downstream_read_timeout.tv_sec
|
Default: )" << get_config()->downstream_read_timeout << R"(
|
||||||
<< R"(
|
|
||||||
--backend-write-timeout=<SEC>
|
--backend-write-timeout=<SEC>
|
||||||
Specify write timeout for backend connection.
|
Specify write timeout for backend connection.
|
||||||
Default: )"
|
Default: )" << get_config()->downstream_write_timeout
|
||||||
<< get_config()->downstream_write_timeout.tv_sec << R"(
|
<< R"(
|
||||||
--backend-keep-alive-timeout=<SEC>
|
--backend-keep-alive-timeout=<SEC>
|
||||||
Specify keep-alive timeout for backend
|
Specify keep-alive timeout for backend
|
||||||
connection.
|
connection.
|
||||||
Default: )"
|
Default: )" << get_config()->downstream_idle_read_timeout
|
||||||
<< get_config()->downstream_idle_read_timeout.tv_sec << R"(
|
<< R"(
|
||||||
--listener-disable-timeout=<SEC>
|
--listener-disable-timeout=<SEC>
|
||||||
After accepting connection failed, connection
|
After accepting connection failed, connection
|
||||||
listener is disabled for a given time in seconds.
|
listener is disabled for a given time in seconds.
|
||||||
Specifying 0 disables this feature.
|
Specifying 0 disables this feature.
|
||||||
Default: )"
|
Default: )" << get_config()->listener_disable_timeout
|
||||||
<< get_config()->listener_disable_timeout.tv_sec << R"(
|
<< R"(
|
||||||
|
|
||||||
SSL/TLS:
|
SSL/TLS:
|
||||||
--ciphers=<SUITE> Set allowed cipher list. The format of the
|
--ciphers=<SUITE> Set allowed cipher list. The format of the
|
||||||
|
@ -1283,6 +1227,8 @@ int main(int argc, char **argv) {
|
||||||
{"listener-disable-timeout", required_argument, &flag, 64},
|
{"listener-disable-timeout", required_argument, &flag, 64},
|
||||||
{"strip-incoming-x-forwarded-for", no_argument, &flag, 65},
|
{"strip-incoming-x-forwarded-for", no_argument, &flag, 65},
|
||||||
{"accesslog-format", required_argument, &flag, 66},
|
{"accesslog-format", required_argument, &flag, 66},
|
||||||
|
{"backend-http1-connections-per-frontend", required_argument, &flag,
|
||||||
|
67},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -1587,6 +1533,11 @@ int main(int argc, char **argv) {
|
||||||
// --accesslog-format
|
// --accesslog-format
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, optarg);
|
cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, optarg);
|
||||||
break;
|
break;
|
||||||
|
case 67:
|
||||||
|
// --backend-http1-connections-per-frontend
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
|
||||||
|
optarg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1829,17 +1780,17 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new(
|
// mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new(
|
||||||
get_rate_limit(get_config()->read_rate),
|
// get_rate_limit(get_config()->read_rate),
|
||||||
get_rate_limit(get_config()->read_burst),
|
// get_rate_limit(get_config()->read_burst),
|
||||||
get_rate_limit(get_config()->write_rate),
|
// get_rate_limit(get_config()->write_rate),
|
||||||
get_rate_limit(get_config()->write_burst), nullptr);
|
// get_rate_limit(get_config()->write_burst), nullptr);
|
||||||
|
|
||||||
mod_config()->worker_rate_limit_cfg = ev_token_bucket_cfg_new(
|
// mod_config()->worker_rate_limit_cfg = ev_token_bucket_cfg_new(
|
||||||
get_rate_limit(get_config()->worker_read_rate),
|
// get_rate_limit(get_config()->worker_read_rate),
|
||||||
get_rate_limit(get_config()->worker_read_burst),
|
// get_rate_limit(get_config()->worker_read_burst),
|
||||||
get_rate_limit(get_config()->worker_write_rate),
|
// get_rate_limit(get_config()->worker_write_rate),
|
||||||
get_rate_limit(get_config()->worker_write_burst), nullptr);
|
// get_rate_limit(get_config()->worker_write_burst), nullptr);
|
||||||
|
|
||||||
if (get_config()->upstream_frame_debug) {
|
if (get_config()->upstream_frame_debug) {
|
||||||
// To make it sync to logging
|
// To make it sync to logging
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_accept_handler.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "shrpx_listen_handler.h"
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
|
||||||
|
auto h = static_cast<AcceptHandler *>(w->data);
|
||||||
|
h->accept_connection();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
AcceptHandler::AcceptHandler(int fd, ListenHandler *h) : conn_hnr_(h), fd_(fd) {
|
||||||
|
ev_io_init(&wev_, acceptcb, fd_, EV_READ);
|
||||||
|
wev_.data = this;
|
||||||
|
ev_io_start(conn_hnr_->get_loop(), &wev_);
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptHandler::~AcceptHandler() {
|
||||||
|
ev_io_stop(conn_hnr_->get_loop(), &wev_);
|
||||||
|
close(fd_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptHandler::accept_connection() {
|
||||||
|
for (;;) {
|
||||||
|
sockaddr_union sockaddr;
|
||||||
|
socklen_t addrlen = sizeof(sockaddr);
|
||||||
|
|
||||||
|
#ifdef HAVE_ACCEPT4
|
||||||
|
auto cfd =
|
||||||
|
accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||||
|
#else // !HAVE_ACCEPT4
|
||||||
|
auto cfd = accept(fd_, &sockaddr.sa, &addrlen);
|
||||||
|
#endif // !HAVE_ACCEPT4
|
||||||
|
|
||||||
|
if (cfd == -1) {
|
||||||
|
switch (errno) {
|
||||||
|
case EINTR:
|
||||||
|
case ENETDOWN:
|
||||||
|
case EPROTO:
|
||||||
|
case ENOPROTOOPT:
|
||||||
|
case EHOSTDOWN:
|
||||||
|
#ifdef ENONET
|
||||||
|
case ENONET:
|
||||||
|
#endif // ENONET
|
||||||
|
case EHOSTUNREACH:
|
||||||
|
case EOPNOTSUPP:
|
||||||
|
case ENETUNREACH:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef HAVE_ACCEPT4
|
||||||
|
util::make_socket_nonblocking(cfd);
|
||||||
|
util::make_socket_closeonexec(cfd);
|
||||||
|
#endif // !HAVE_ACCEPT4
|
||||||
|
|
||||||
|
util::make_socket_nodelay(cfd);
|
||||||
|
|
||||||
|
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); }
|
||||||
|
|
||||||
|
void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); }
|
||||||
|
|
||||||
|
int AcceptHandler::get_fd() const { return fd_; }
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_ACCEPT_HANDLER_H
|
||||||
|
#define SHRPX_ACCEPT_HANDLER_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
class ListenHandler;
|
||||||
|
|
||||||
|
class AcceptHandler {
|
||||||
|
public:
|
||||||
|
AcceptHandler(int fd, ListenHandler *h);
|
||||||
|
~AcceptHandler();
|
||||||
|
void accept_connection();
|
||||||
|
void enable();
|
||||||
|
void disable();
|
||||||
|
int get_fd() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ev_io wev_;
|
||||||
|
ListenHandler *conn_hnr_;
|
||||||
|
int fd_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_ACCEPT_HANDLER_H
|
|
@ -42,161 +42,446 @@
|
||||||
#include "shrpx_spdy_upstream.h"
|
#include "shrpx_spdy_upstream.h"
|
||||||
#endif // HAVE_SPDYLAY
|
#endif // HAVE_SPDYLAY
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void upstream_readcb(bufferevent *bev, void *arg) {
|
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
auto handler = static_cast<ClientHandler *>(w->data);
|
||||||
auto upstream = handler->get_upstream();
|
|
||||||
if (upstream) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
upstream->reset_timeouts();
|
CLOG(INFO, handler) << "Time out";
|
||||||
}
|
}
|
||||||
int rv = handler->on_read();
|
|
||||||
if (rv != 0) {
|
delete handler;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void shutdowncb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto handler = static_cast<ClientHandler *>(w->data);
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
|
||||||
|
}
|
||||||
|
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto handler = static_cast<ClientHandler *>(w->data);
|
||||||
|
|
||||||
|
if (handler->do_read() != 0) {
|
||||||
delete handler;
|
delete handler;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void upstream_writecb(bufferevent *bev, void *arg) {
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
auto handler = static_cast<ClientHandler *>(w->data);
|
||||||
auto upstream = handler->get_upstream();
|
|
||||||
if (upstream) {
|
|
||||||
upstream->reset_timeouts();
|
|
||||||
}
|
|
||||||
|
|
||||||
handler->update_last_write_time();
|
if (handler->do_write() != 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.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (handler->get_should_close_after_write()) {
|
|
||||||
delete handler;
|
delete handler;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!upstream) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int rv = upstream->on_write();
|
|
||||||
if (rv != 0) {
|
|
||||||
delete handler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
int ClientHandler::read_clear() {
|
||||||
void upstream_eventcb(bufferevent *bev, short events, void *arg) {
|
ev_timer_again(loop_, &rt_);
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
|
||||||
bool finish = false;
|
for (;;) {
|
||||||
if (events & BEV_EVENT_EOF) {
|
if (rb_.rleft() && on_read() != 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
return -1;
|
||||||
CLOG(INFO, handler) << "EOF";
|
|
||||||
}
|
}
|
||||||
finish = true;
|
rb_.reset();
|
||||||
}
|
struct iovec iov[2];
|
||||||
if (events & BEV_EVENT_ERROR) {
|
auto iovcnt = rb_.wiovec(iov);
|
||||||
if (LOG_ENABLED(INFO)) {
|
iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail());
|
||||||
CLOG(INFO, handler) << "Network error: " << evutil_socket_error_to_string(
|
if (iovcnt == 0) {
|
||||||
EVUTIL_SOCKET_ERROR());
|
break;
|
||||||
}
|
}
|
||||||
finish = true;
|
|
||||||
}
|
ssize_t nread;
|
||||||
if (events & BEV_EVENT_TIMEOUT) {
|
while ((nread = readv(fd_, iov, iovcnt)) == -1 && errno == EINTR)
|
||||||
if (LOG_ENABLED(INFO)) {
|
;
|
||||||
CLOG(INFO, handler) << "Time out";
|
if (nread == -1) {
|
||||||
}
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
finish = true;
|
break;
|
||||||
}
|
|
||||||
if (finish) {
|
|
||||||
delete handler;
|
|
||||||
} else {
|
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
|
||||||
handler->set_tls_handshake(true);
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
CLOG(INFO, handler) << "SSL/TLS handshake completed";
|
|
||||||
}
|
}
|
||||||
if (handler->validate_next_proto() != 0) {
|
return -1;
|
||||||
delete handler;
|
}
|
||||||
return;
|
|
||||||
|
if (nread == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_.write(nread);
|
||||||
|
rlimit_.drain(nread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::write_clear() {
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (wb_.rleft() > 0) {
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = wb_.riovec(iov);
|
||||||
|
iovcnt = limit_iovec(iov, iovcnt, wlimit_.avail());
|
||||||
|
if (iovcnt == 0) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
if (SSL_session_reused(handler->get_ssl())) {
|
ssize_t nwrite;
|
||||||
CLOG(INFO, handler) << "SSL/TLS session reused";
|
while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
if (nwrite == -1) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
wlimit_.startw();
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
wb_.drain(nwrite);
|
||||||
|
wlimit_.drain(nwrite);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wb_.reset();
|
||||||
|
if (on_write() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (wb_.rleft() == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wlimit_.stopw();
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::tls_handshake() {
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
|
auto rv = SSL_do_handshake(ssl_);
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl_, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
wlimit_.stopw();
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
wlimit_.startw();
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wlimit_.stopw();
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
|
||||||
|
set_tls_handshake(true);
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, this) << "SSL/TLS handshake completed";
|
||||||
|
}
|
||||||
|
if (validate_next_proto() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
if (SSL_session_reused(ssl_)) {
|
||||||
|
CLOG(INFO, this) << "SSL/TLS session reused";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read_ = &ClientHandler::read_tls;
|
||||||
|
write_ = &ClientHandler::write_tls;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::read_tls() {
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (rb_.rleft() && on_read() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_.reset();
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = rb_.wiovec(iov);
|
||||||
|
iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail());
|
||||||
|
if (iovcnt == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rv = SSL_read(ssl_, iov[0].iov_base, iov[0].iov_len);
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl_, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
goto fin;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
wlimit_.startw();
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
goto fin;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rb_.write(rv);
|
||||||
|
rlimit_.drain(rv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fin:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::write_tls() {
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (wb_.rleft() > 0) {
|
||||||
|
const void *p;
|
||||||
|
size_t len;
|
||||||
|
std::tie(p, len) = wb_.get();
|
||||||
|
|
||||||
|
len = std::min(len, wlimit_.avail());
|
||||||
|
if (len == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto limit = get_write_limit();
|
||||||
|
if (limit != -1) {
|
||||||
|
len = std::min(len, static_cast<size_t>(limit));
|
||||||
|
}
|
||||||
|
auto rv = SSL_write(ssl_, p, len);
|
||||||
|
|
||||||
|
if (rv == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
auto err = SSL_get_error(ssl_, rv);
|
||||||
|
switch (err) {
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
wlimit_.stopw();
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
wlimit_.startw();
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wb_.drain(rv);
|
||||||
|
wlimit_.drain(rv);
|
||||||
|
|
||||||
|
update_warmup_writelen(rv);
|
||||||
|
update_last_write_time();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
wb_.reset();
|
||||||
|
if (on_write() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (wb_.rleft() == 0) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
wlimit_.stopw();
|
||||||
void upstream_http2_connhd_readcb(bufferevent *bev, void *arg) {
|
ev_timer_stop(loop_, &wt_);
|
||||||
// This callback assumes upstream is Http2Upstream.
|
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
return 0;
|
||||||
if (handler->on_http2_connhd_read() != 0) {
|
}
|
||||||
delete handler;
|
|
||||||
|
int ClientHandler::upstream_noop() { return 0; }
|
||||||
|
|
||||||
|
int ClientHandler::upstream_read() {
|
||||||
|
assert(upstream_);
|
||||||
|
if (upstream_->on_read() != 0) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
int ClientHandler::upstream_write() {
|
||||||
void upstream_http1_connhd_readcb(bufferevent *bev, void *arg) {
|
assert(upstream_);
|
||||||
// This callback assumes upstream is HttpsUpstream.
|
if (upstream_->on_write() != 0) {
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
return -1;
|
||||||
if (handler->on_http1_connhd_read() != 0) {
|
|
||||||
delete handler;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
ClientHandler::ClientHandler(bufferevent *bev,
|
if (get_should_close_after_write() && wb_.rleft() == 0) {
|
||||||
bufferevent_rate_limit_group *rate_limit_group,
|
return -1;
|
||||||
int fd, SSL *ssl, const char *ipaddr,
|
}
|
||||||
const char *port, WorkerStat *worker_stat,
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::upstream_http2_connhd_read() {
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = rb_.riovec(iov);
|
||||||
|
for (int i = 0; i < iovcnt; ++i) {
|
||||||
|
auto nread =
|
||||||
|
std::min(left_connhd_len_, static_cast<size_t>(iov[i].iov_len));
|
||||||
|
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
|
||||||
|
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
|
||||||
|
iov[i].iov_base, nread) != 0) {
|
||||||
|
// There is no downgrade path here. Just drop the connection.
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, this) << "invalid client connection header";
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
left_connhd_len_ -= nread;
|
||||||
|
rb_.drain(nread);
|
||||||
|
|
||||||
|
if (left_connhd_len_ == 0) {
|
||||||
|
on_read_ = &ClientHandler::upstream_read;
|
||||||
|
// Run on_read to process data left in buffer since they are not
|
||||||
|
// notified further
|
||||||
|
if (on_read() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClientHandler::upstream_http1_connhd_read() {
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = rb_.riovec(iov);
|
||||||
|
for (int i = 0; i < iovcnt; ++i) {
|
||||||
|
auto nread =
|
||||||
|
std::min(left_connhd_len_, static_cast<size_t>(iov[i].iov_len));
|
||||||
|
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
|
||||||
|
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
|
||||||
|
iov[i].iov_base, nread) != 0) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, this) << "This is HTTP/1.1 connection, "
|
||||||
|
<< "but may be upgraded to HTTP/2 later.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset header length for later HTTP/2 upgrade
|
||||||
|
left_connhd_len_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
|
||||||
|
on_read_ = &ClientHandler::upstream_read;
|
||||||
|
on_write_ = &ClientHandler::upstream_write;
|
||||||
|
|
||||||
|
if (on_read() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
left_connhd_len_ -= nread;
|
||||||
|
rb_.drain(nread);
|
||||||
|
|
||||||
|
if (left_connhd_len_ == 0) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
CLOG(INFO, this) << "direct HTTP/2 connection";
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_http2_upgrade();
|
||||||
|
on_read_ = &ClientHandler::upstream_read;
|
||||||
|
on_write_ = &ClientHandler::upstream_write;
|
||||||
|
|
||||||
|
// Run on_read to process data left in buffer since they are not
|
||||||
|
// notified further
|
||||||
|
if (on_read() != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientHandler::ClientHandler(struct ev_loop *loop, int fd, SSL *ssl,
|
||||||
|
const char *ipaddr, const char *port,
|
||||||
|
WorkerStat *worker_stat,
|
||||||
DownstreamConnectionPool *dconn_pool)
|
DownstreamConnectionPool *dconn_pool)
|
||||||
: ipaddr_(ipaddr), port_(port), dconn_pool_(dconn_pool), bev_(bev),
|
: ipaddr_(ipaddr), port_(port),
|
||||||
http2session_(nullptr), ssl_(ssl), reneg_shutdown_timerev_(nullptr),
|
wlimit_(loop, &wev_, get_config()->write_rate, get_config()->write_burst),
|
||||||
|
rlimit_(loop, &rev_, get_config()->read_rate, get_config()->read_burst),
|
||||||
|
loop_(loop), dconn_pool_(dconn_pool), http2session_(nullptr), ssl_(ssl),
|
||||||
worker_stat_(worker_stat), last_write_time_(0), warmup_writelen_(0),
|
worker_stat_(worker_stat), last_write_time_(0), warmup_writelen_(0),
|
||||||
left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN), fd_(fd),
|
left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN), fd_(fd),
|
||||||
should_close_after_write_(false), tls_handshake_(false),
|
should_close_after_write_(false), tls_handshake_(false),
|
||||||
tls_renegotiation_(false) {
|
tls_renegotiation_(false) {
|
||||||
int rv;
|
|
||||||
|
|
||||||
++worker_stat->num_connections;
|
++worker_stat->num_connections;
|
||||||
|
|
||||||
rv = bufferevent_set_rate_limit(bev_, get_config()->rate_limit_cfg);
|
ev_io_init(&wev_, writecb, fd_, EV_WRITE);
|
||||||
if (rv == -1) {
|
ev_io_init(&rev_, readcb, fd_, EV_READ);
|
||||||
CLOG(FATAL, this) << "bufferevent_set_rate_limit() failed";
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = bufferevent_add_to_rate_limit_group(bev_, rate_limit_group);
|
wev_.data = this;
|
||||||
if (rv == -1) {
|
rev_.data = this;
|
||||||
CLOG(FATAL, this) << "bufferevent_add_to_rate_limit_group() failed";
|
|
||||||
}
|
ev_timer_init(&wt_, timeoutcb, 0., get_config()->upstream_write_timeout);
|
||||||
|
ev_timer_init(&rt_, timeoutcb, 0., get_config()->upstream_read_timeout);
|
||||||
|
|
||||||
|
wt_.data = this;
|
||||||
|
rt_.data = this;
|
||||||
|
|
||||||
|
ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.);
|
||||||
|
|
||||||
|
reneg_shutdown_timer_.data = this;
|
||||||
|
|
||||||
|
rlimit_.startw();
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
util::bev_enable_unless(bev_, EV_READ | EV_WRITE);
|
|
||||||
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
|
|
||||||
set_upstream_timeouts(&get_config()->upstream_read_timeout,
|
|
||||||
&get_config()->upstream_write_timeout);
|
|
||||||
if (ssl_) {
|
if (ssl_) {
|
||||||
SSL_set_app_data(ssl_, reinterpret_cast<char *>(this));
|
SSL_set_app_data(ssl_, reinterpret_cast<char *>(this));
|
||||||
set_bev_cb(nullptr, upstream_writecb, upstream_eventcb);
|
read_ = write_ = &ClientHandler::tls_handshake;
|
||||||
|
on_read_ = &ClientHandler::upstream_noop;
|
||||||
|
on_write_ = &ClientHandler::upstream_write;
|
||||||
} else {
|
} else {
|
||||||
// For non-TLS version, first create HttpsUpstream. It may be
|
// For non-TLS version, first create HttpsUpstream. It may be
|
||||||
// upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
|
// upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
|
||||||
// connection.
|
// connection.
|
||||||
upstream_ = util::make_unique<HttpsUpstream>(this);
|
upstream_ = util::make_unique<HttpsUpstream>(this);
|
||||||
alpn_ = "http/1.1";
|
alpn_ = "http/1.1";
|
||||||
set_bev_cb(upstream_http1_connhd_readcb, nullptr, upstream_eventcb);
|
read_ = &ClientHandler::read_clear;
|
||||||
|
write_ = &ClientHandler::write_clear;
|
||||||
|
on_read_ = &ClientHandler::upstream_http1_connhd_read;
|
||||||
|
on_write_ = &ClientHandler::upstream_noop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,14 +496,18 @@ ClientHandler::~ClientHandler() {
|
||||||
|
|
||||||
--worker_stat_->num_connections;
|
--worker_stat_->num_connections;
|
||||||
|
|
||||||
|
ev_timer_stop(loop_, &reneg_shutdown_timer_);
|
||||||
|
|
||||||
|
ev_timer_stop(loop_, &rt_);
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
|
||||||
|
ev_io_stop(loop_, &rev_);
|
||||||
|
ev_io_stop(loop_, &wev_);
|
||||||
|
|
||||||
// TODO If backend is http/2, and it is in CONNECTED state, signal
|
// TODO If backend is http/2, and it is in CONNECTED state, signal
|
||||||
// it and make it loopbreak when output is zero.
|
// it and make it loopbreak when output is zero.
|
||||||
if (worker_config->graceful_shutdown && worker_stat_->num_connections == 0) {
|
if (worker_config->graceful_shutdown && worker_stat_->num_connections == 0) {
|
||||||
event_base_loopbreak(get_evbase());
|
ev_break(loop_);
|
||||||
}
|
|
||||||
|
|
||||||
if (reneg_shutdown_timerev_) {
|
|
||||||
event_free(reneg_shutdown_timerev_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ssl_) {
|
if (ssl_) {
|
||||||
|
@ -227,11 +516,6 @@ ClientHandler::~ClientHandler() {
|
||||||
SSL_shutdown(ssl_);
|
SSL_shutdown(ssl_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_remove_from_rate_limit_group(bev_);
|
|
||||||
|
|
||||||
util::bev_disable_unless(bev_, EV_READ | EV_WRITE);
|
|
||||||
bufferevent_free(bev_);
|
|
||||||
|
|
||||||
if (ssl_) {
|
if (ssl_) {
|
||||||
SSL_free(ssl_);
|
SSL_free(ssl_);
|
||||||
}
|
}
|
||||||
|
@ -245,21 +529,22 @@ ClientHandler::~ClientHandler() {
|
||||||
|
|
||||||
Upstream *ClientHandler::get_upstream() { return upstream_.get(); }
|
Upstream *ClientHandler::get_upstream() { return upstream_.get(); }
|
||||||
|
|
||||||
bufferevent *ClientHandler::get_bev() const { return bev_; }
|
struct ev_loop *ClientHandler::get_loop() const {
|
||||||
|
return loop_;
|
||||||
event_base *ClientHandler::get_evbase() const {
|
|
||||||
return bufferevent_get_base(bev_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientHandler::set_bev_cb(bufferevent_data_cb readcb,
|
void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) {
|
||||||
bufferevent_data_cb writecb,
|
ev_timer_set(&rt_, 0., t);
|
||||||
bufferevent_event_cb eventcb) {
|
if (ev_is_active(&rt_)) {
|
||||||
bufferevent_setcb(bev_, readcb, writecb, eventcb, this);
|
ev_timer_again(loop_, &rt_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientHandler::set_upstream_timeouts(const timeval *read_timeout,
|
void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
|
||||||
const timeval *write_timeout) {
|
ev_timer_set(&wt_, 0., t);
|
||||||
bufferevent_set_timeouts(bev_, read_timeout, write_timeout);
|
if (ev_is_active(&wt_)) {
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ClientHandler::validate_next_proto() {
|
int ClientHandler::validate_next_proto() {
|
||||||
|
@ -268,7 +553,8 @@ int ClientHandler::validate_next_proto() {
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
// First set callback for catch all cases
|
// First set callback for catch all cases
|
||||||
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
|
on_read_ = &ClientHandler::upstream_read;
|
||||||
|
|
||||||
SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len);
|
SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len);
|
||||||
for (int i = 0; i < 2; ++i) {
|
for (int i = 0; i < 2; ++i) {
|
||||||
if (next_proto) {
|
if (next_proto) {
|
||||||
|
@ -284,8 +570,7 @@ int ClientHandler::validate_next_proto() {
|
||||||
(next_proto_len == sizeof("h2-16") - 1 &&
|
(next_proto_len == sizeof("h2-16") - 1 &&
|
||||||
memcmp("h2-16", next_proto, next_proto_len) == 0)) {
|
memcmp("h2-16", next_proto, next_proto_len) == 0)) {
|
||||||
|
|
||||||
set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb,
|
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||||
upstream_eventcb);
|
|
||||||
|
|
||||||
auto http2_upstream = util::make_unique<Http2Upstream>(this);
|
auto http2_upstream = util::make_unique<Http2Upstream>(this);
|
||||||
|
|
||||||
|
@ -303,7 +588,7 @@ int ClientHandler::validate_next_proto() {
|
||||||
// At this point, input buffer is already filled with some
|
// At this point, input buffer is already filled with some
|
||||||
// bytes. The read callback is not called until new data
|
// bytes. The read callback is not called until new data
|
||||||
// come. So consume input buffer here.
|
// come. So consume input buffer here.
|
||||||
if (on_http2_connhd_read() != 0) {
|
if (on_read() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +616,7 @@ int ClientHandler::validate_next_proto() {
|
||||||
// At this point, input buffer is already filled with some
|
// At this point, input buffer is already filled with some
|
||||||
// bytes. The read callback is not called until new data
|
// bytes. The read callback is not called until new data
|
||||||
// come. So consume input buffer here.
|
// come. So consume input buffer here.
|
||||||
if (upstream_->on_read() != 0) {
|
if (on_read() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +630,7 @@ int ClientHandler::validate_next_proto() {
|
||||||
// At this point, input buffer is already filled with some
|
// At this point, input buffer is already filled with some
|
||||||
// bytes. The read callback is not called until new data
|
// bytes. The read callback is not called until new data
|
||||||
// come. So consume input buffer here.
|
// come. So consume input buffer here.
|
||||||
if (upstream_->on_read() != 0) {
|
if (on_read() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +655,7 @@ int ClientHandler::validate_next_proto() {
|
||||||
// At this point, input buffer is already filled with some bytes.
|
// At this point, input buffer is already filled with some bytes.
|
||||||
// The read callback is not called until new data come. So consume
|
// The read callback is not called until new data come. So consume
|
||||||
// input buffer here.
|
// input buffer here.
|
||||||
if (upstream_->on_read() != 0) {
|
if (on_read() != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,101 +667,11 @@ int ClientHandler::validate_next_proto() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ClientHandler::on_read() { return upstream_->on_read(); }
|
int ClientHandler::do_read() { return read_(*this); }
|
||||||
|
int ClientHandler::do_write() { return write_(*this); }
|
||||||
|
|
||||||
int ClientHandler::on_event() { return upstream_->on_event(); }
|
int ClientHandler::on_read() { return on_read_(*this); }
|
||||||
|
int ClientHandler::on_write() { return on_write_(*this); }
|
||||||
int ClientHandler::on_http2_connhd_read() {
|
|
||||||
// This callback assumes upstream is Http2Upstream.
|
|
||||||
uint8_t data[NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN];
|
|
||||||
auto input = bufferevent_get_input(bev_);
|
|
||||||
auto readlen = evbuffer_remove(input, data, left_connhd_len_);
|
|
||||||
|
|
||||||
if (readlen == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
|
|
||||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
|
|
||||||
data, readlen) != 0) {
|
|
||||||
// There is no downgrade path here. Just drop the connection.
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
CLOG(INFO, this) << "invalid client connection header";
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
left_connhd_len_ -= readlen;
|
|
||||||
|
|
||||||
if (left_connhd_len_ > 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
|
|
||||||
|
|
||||||
// Run on_read to process data left in buffer since they are not
|
|
||||||
// notified further
|
|
||||||
if (on_read() != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ClientHandler::on_http1_connhd_read() {
|
|
||||||
uint8_t data[NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN];
|
|
||||||
auto input = bufferevent_get_input(bev_);
|
|
||||||
auto readlen = evbuffer_copyout(input, data, left_connhd_len_);
|
|
||||||
|
|
||||||
if (readlen == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
|
|
||||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
|
|
||||||
data, readlen) != 0) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
CLOG(INFO, this) << "This is HTTP/1.1 connection, "
|
|
||||||
<< "but may be upgraded to HTTP/2 later.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset header length for later HTTP/2 upgrade
|
|
||||||
left_connhd_len_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
|
|
||||||
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
|
|
||||||
|
|
||||||
if (on_read() != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evbuffer_drain(input, readlen) == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
left_connhd_len_ -= readlen;
|
|
||||||
|
|
||||||
if (left_connhd_len_ > 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
CLOG(INFO, this) << "direct HTTP/2 connection";
|
|
||||||
}
|
|
||||||
|
|
||||||
direct_http2_upgrade();
|
|
||||||
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
|
|
||||||
|
|
||||||
// Run on_read to process data left in buffer since they are not
|
|
||||||
// notified further
|
|
||||||
if (on_read() != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; }
|
const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; }
|
||||||
|
|
||||||
|
@ -519,7 +714,7 @@ ClientHandler::get_downstream_connection() {
|
||||||
dconn = util::make_unique<Http2DownstreamConnection>(dconn_pool_,
|
dconn = util::make_unique<Http2DownstreamConnection>(dconn_pool_,
|
||||||
http2session_);
|
http2session_);
|
||||||
} else {
|
} else {
|
||||||
dconn = util::make_unique<HttpDownstreamConnection>(dconn_pool_);
|
dconn = util::make_unique<HttpDownstreamConnection>(dconn_pool_, loop_);
|
||||||
}
|
}
|
||||||
dconn->set_client_handler(this);
|
dconn->set_client_handler(this);
|
||||||
return dconn;
|
return dconn;
|
||||||
|
@ -535,10 +730,6 @@ ClientHandler::get_downstream_connection() {
|
||||||
return dconn;
|
return dconn;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ClientHandler::get_outbuf_length() {
|
|
||||||
return evbuffer_get_length(bufferevent_get_output(bev_));
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL *ClientHandler::get_ssl() const { return ssl_; }
|
SSL *ClientHandler::get_ssl() const { return ssl_; }
|
||||||
|
|
||||||
void ClientHandler::set_http2_session(Http2Session *http2session) {
|
void ClientHandler::set_http2_session(Http2Session *http2session) {
|
||||||
|
@ -561,11 +752,10 @@ void ClientHandler::direct_http2_upgrade() {
|
||||||
// TODO We don't know exact h2 draft version in direct upgrade. We
|
// TODO We don't know exact h2 draft version in direct upgrade. We
|
||||||
// just use library default for now.
|
// just use library default for now.
|
||||||
alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
|
alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
|
||||||
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
|
on_read_ = &ClientHandler::upstream_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
||||||
int rv;
|
|
||||||
auto upstream = util::make_unique<Http2Upstream>(this);
|
auto upstream = util::make_unique<Http2Upstream>(this);
|
||||||
if (upstream->upgrade_upstream(http) != 0) {
|
if (upstream->upgrade_upstream(http) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -576,16 +766,13 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
||||||
// TODO We might get other version id in HTTP2-settings, if we
|
// TODO We might get other version id in HTTP2-settings, if we
|
||||||
// support aliasing for h2, but we just use library default for now.
|
// support aliasing for h2, but we just use library default for now.
|
||||||
alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
|
alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
|
||||||
set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb, upstream_eventcb);
|
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||||
|
|
||||||
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
|
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
"Connection: Upgrade\r\n"
|
"Connection: Upgrade\r\n"
|
||||||
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
||||||
"\r\n";
|
"\r\n";
|
||||||
rv = bufferevent_write(bev_, res, sizeof(res) - 1);
|
wb_.write(res, sizeof(res) - 1);
|
||||||
if (rv != 0) {
|
|
||||||
CLOG(FATAL, this) << "bufferevent_write() faild";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,18 +790,6 @@ void ClientHandler::set_tls_handshake(bool f) { tls_handshake_ = f; }
|
||||||
|
|
||||||
bool ClientHandler::get_tls_handshake() const { return tls_handshake_; }
|
bool ClientHandler::get_tls_handshake() const { return tls_handshake_; }
|
||||||
|
|
||||||
namespace {
|
|
||||||
void shutdown_cb(evutil_socket_t fd, short what, void *arg) {
|
|
||||||
auto handler = static_cast<ClientHandler *>(arg);
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
|
|
||||||
}
|
|
||||||
|
|
||||||
delete handler;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void ClientHandler::set_tls_renegotiation(bool f) {
|
void ClientHandler::set_tls_renegotiation(bool f) {
|
||||||
if (tls_renegotiation_ == false) {
|
if (tls_renegotiation_ == false) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -622,13 +797,7 @@ void ClientHandler::set_tls_renegotiation(bool f) {
|
||||||
<< "Start shutdown timer now.";
|
<< "Start shutdown timer now.";
|
||||||
}
|
}
|
||||||
|
|
||||||
reneg_shutdown_timerev_ = evtimer_new(get_evbase(), shutdown_cb, this);
|
ev_timer_start(loop_, &reneg_shutdown_timer_);
|
||||||
event_priority_set(reneg_shutdown_timerev_, 0);
|
|
||||||
|
|
||||||
timeval timeout = {0, 0};
|
|
||||||
|
|
||||||
// TODO What to do if this failed?
|
|
||||||
evtimer_add(reneg_shutdown_timerev_, &timeout);
|
|
||||||
}
|
}
|
||||||
tls_renegotiation_ = f;
|
tls_renegotiation_ = f;
|
||||||
}
|
}
|
||||||
|
@ -645,14 +814,12 @@ ssize_t ClientHandler::get_write_limit() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeval tv;
|
auto t = ev_now(loop_);
|
||||||
if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) {
|
|
||||||
auto now = util::to_time64(tv);
|
if (t - last_write_time_ > 1.0) {
|
||||||
if (now - last_write_time_ > 1000000) {
|
// Time out, use small record size
|
||||||
// Time out, use small record size
|
warmup_writelen_ = 0;
|
||||||
warmup_writelen_ = 0;
|
return SHRPX_SMALL_WRITE_LIMIT;
|
||||||
return SHRPX_SMALL_WRITE_LIMIT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If event_base_gettimeofday_cached() failed, we just skip timer
|
// If event_base_gettimeofday_cached() failed, we just skip timer
|
||||||
|
@ -672,10 +839,7 @@ void ClientHandler::update_warmup_writelen(size_t n) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientHandler::update_last_write_time() {
|
void ClientHandler::update_last_write_time() {
|
||||||
timeval tv;
|
last_write_time_ = ev_now(loop_);
|
||||||
if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) {
|
|
||||||
last_write_time_ = util::to_time64(tv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientHandler::write_accesslog(Downstream *downstream) {
|
void ClientHandler::write_accesslog(Downstream *downstream) {
|
||||||
|
@ -721,4 +885,13 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
|
||||||
|
|
||||||
WorkerStat *ClientHandler::get_worker_stat() const { return worker_stat_; }
|
WorkerStat *ClientHandler::get_worker_stat() const { return worker_stat_; }
|
||||||
|
|
||||||
|
UpstreamBuf *ClientHandler::get_wb() { return &wb_; }
|
||||||
|
|
||||||
|
UpstreamBuf *ClientHandler::get_rb() { return &rb_; }
|
||||||
|
|
||||||
|
void ClientHandler::signal_write() { wlimit_.startw(); }
|
||||||
|
|
||||||
|
RateLimit *ClientHandler::get_rlimit() { return &rlimit_; }
|
||||||
|
RateLimit *ClientHandler::get_wlimit() { return &wlimit_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -29,11 +29,15 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#include "shrpx_rate_limit.h"
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
class Upstream;
|
class Upstream;
|
||||||
|
@ -44,21 +48,41 @@ class ConnectBlocker;
|
||||||
class DownstreamConnectionPool;
|
class DownstreamConnectionPool;
|
||||||
struct WorkerStat;
|
struct WorkerStat;
|
||||||
|
|
||||||
|
typedef RingBuf<65536> UpstreamBuf;
|
||||||
|
|
||||||
class ClientHandler {
|
class ClientHandler {
|
||||||
public:
|
public:
|
||||||
ClientHandler(bufferevent *bev,
|
ClientHandler(struct ev_loop *loop, int fd, SSL *ssl, const char *ipaddr,
|
||||||
bufferevent_rate_limit_group *rate_limit_group, int fd,
|
const char *port, WorkerStat *worker_stat,
|
||||||
SSL *ssl, const char *ipaddr, const char *port,
|
DownstreamConnectionPool *dconn_pool);
|
||||||
WorkerStat *worker_stat, DownstreamConnectionPool *dconn_pool);
|
|
||||||
~ClientHandler();
|
~ClientHandler();
|
||||||
|
|
||||||
|
// Performs clear text I/O
|
||||||
|
int read_clear();
|
||||||
|
int write_clear();
|
||||||
|
// Performs TLS handshake
|
||||||
|
int tls_handshake();
|
||||||
|
// Performs TLS I/O
|
||||||
|
int read_tls();
|
||||||
|
int write_tls();
|
||||||
|
|
||||||
|
int upstream_noop();
|
||||||
|
int upstream_read();
|
||||||
|
int upstream_http2_connhd_read();
|
||||||
|
int upstream_http1_connhd_read();
|
||||||
|
int upstream_write();
|
||||||
|
|
||||||
|
// Performs I/O operation. Internally calls on_read()/on_write().
|
||||||
|
int do_read();
|
||||||
|
int do_write();
|
||||||
|
|
||||||
|
// Processes buffers. No underlying I/O operation will be done.
|
||||||
int on_read();
|
int on_read();
|
||||||
int on_event();
|
int on_write();
|
||||||
bufferevent *get_bev() const;
|
|
||||||
event_base *get_evbase() const;
|
struct ev_loop *get_loop() const;
|
||||||
void set_bev_cb(bufferevent_data_cb readcb, bufferevent_data_cb writecb,
|
void reset_upstream_read_timeout(ev_tstamp t);
|
||||||
bufferevent_event_cb eventcb);
|
void reset_upstream_write_timeout(ev_tstamp t);
|
||||||
void set_upstream_timeouts(const timeval *read_timeout,
|
|
||||||
const timeval *write_timeout);
|
|
||||||
int validate_next_proto();
|
int validate_next_proto();
|
||||||
const std::string &get_ipaddr() const;
|
const std::string &get_ipaddr() const;
|
||||||
const std::string &get_port() const;
|
const std::string &get_port() const;
|
||||||
|
@ -69,7 +93,6 @@ public:
|
||||||
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
||||||
void remove_downstream_connection(DownstreamConnection *dconn);
|
void remove_downstream_connection(DownstreamConnection *dconn);
|
||||||
std::unique_ptr<DownstreamConnection> get_downstream_connection();
|
std::unique_ptr<DownstreamConnection> get_downstream_connection();
|
||||||
size_t get_outbuf_length();
|
|
||||||
SSL *get_ssl() const;
|
SSL *get_ssl() const;
|
||||||
void set_http2_session(Http2Session *http2session);
|
void set_http2_session(Http2Session *http2session);
|
||||||
Http2Session *get_http2_session() const;
|
Http2Session *get_http2_session() const;
|
||||||
|
@ -89,8 +112,6 @@ public:
|
||||||
bool get_tls_handshake() const;
|
bool get_tls_handshake() const;
|
||||||
void set_tls_renegotiation(bool f);
|
void set_tls_renegotiation(bool f);
|
||||||
bool get_tls_renegotiation() const;
|
bool get_tls_renegotiation() const;
|
||||||
int on_http2_connhd_read();
|
|
||||||
int on_http1_connhd_read();
|
|
||||||
// Returns maximum chunk size for one evbuffer_add(). The intention
|
// Returns maximum chunk size for one evbuffer_add(). The intention
|
||||||
// of this chunk size is control the TLS record size. The actual
|
// of this chunk size is control the TLS record size. The actual
|
||||||
// SSL_write() call is done under libevent control. In
|
// SSL_write() call is done under libevent control. In
|
||||||
|
@ -116,20 +137,36 @@ public:
|
||||||
int64_t body_bytes_sent);
|
int64_t body_bytes_sent);
|
||||||
WorkerStat *get_worker_stat() const;
|
WorkerStat *get_worker_stat() const;
|
||||||
|
|
||||||
|
UpstreamBuf *get_wb();
|
||||||
|
UpstreamBuf *get_rb();
|
||||||
|
|
||||||
|
RateLimit *get_rlimit();
|
||||||
|
RateLimit *get_wlimit();
|
||||||
|
|
||||||
|
void signal_write();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ev_io wev_;
|
||||||
|
ev_io rev_;
|
||||||
|
ev_timer wt_;
|
||||||
|
ev_timer rt_;
|
||||||
|
ev_timer reneg_shutdown_timer_;
|
||||||
std::unique_ptr<Upstream> upstream_;
|
std::unique_ptr<Upstream> upstream_;
|
||||||
std::string ipaddr_;
|
std::string ipaddr_;
|
||||||
std::string port_;
|
std::string port_;
|
||||||
// The ALPN identifier negotiated for this connection.
|
// The ALPN identifier negotiated for this connection.
|
||||||
std::string alpn_;
|
std::string alpn_;
|
||||||
|
std::function<int(ClientHandler &)> read_, write_;
|
||||||
|
std::function<int(ClientHandler &)> on_read_, on_write_;
|
||||||
|
RateLimit wlimit_;
|
||||||
|
RateLimit rlimit_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
DownstreamConnectionPool *dconn_pool_;
|
DownstreamConnectionPool *dconn_pool_;
|
||||||
bufferevent *bev_;
|
|
||||||
// Shared HTTP2 session for each thread. NULL if backend is not
|
// Shared HTTP2 session for each thread. NULL if backend is not
|
||||||
// HTTP2. Not deleted by this object.
|
// HTTP2. Not deleted by this object.
|
||||||
Http2Session *http2session_;
|
Http2Session *http2session_;
|
||||||
ConnectBlocker *http1_connect_blocker_;
|
ConnectBlocker *http1_connect_blocker_;
|
||||||
SSL *ssl_;
|
SSL *ssl_;
|
||||||
event *reneg_shutdown_timerev_;
|
|
||||||
WorkerStat *worker_stat_;
|
WorkerStat *worker_stat_;
|
||||||
int64_t last_write_time_;
|
int64_t last_write_time_;
|
||||||
size_t warmup_writelen_;
|
size_t warmup_writelen_;
|
||||||
|
@ -139,6 +176,8 @@ private:
|
||||||
bool should_close_after_write_;
|
bool should_close_after_write_;
|
||||||
bool tls_handshake_;
|
bool tls_handshake_;
|
||||||
bool tls_renegotiation_;
|
bool tls_renegotiation_;
|
||||||
|
UpstreamBuf wb_;
|
||||||
|
UpstreamBuf rb_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -134,6 +134,8 @@ const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[] =
|
||||||
const char SHRPX_OPT_NO_LOCATION_REWRITE[] = "no-location-rewrite";
|
const char SHRPX_OPT_NO_LOCATION_REWRITE[] = "no-location-rewrite";
|
||||||
const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] =
|
const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] =
|
||||||
"backend-http1-connections-per-host";
|
"backend-http1-connections-per-host";
|
||||||
|
const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[] =
|
||||||
|
"backend-http1-connections-per-frontend";
|
||||||
const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout";
|
const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -198,7 +200,7 @@ FILE *open_file_for_write(const char *filename) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
evutil_make_socket_closeonexec(fileno(f));
|
util::make_socket_closeonexec(fileno(f));
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
@ -421,15 +423,14 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int parse_timeval(timeval *dest, const char *opt, const char *optarg) {
|
int parse_timeval(ev_tstamp *dest, const char *opt, const char *optarg) {
|
||||||
time_t sec;
|
time_t sec;
|
||||||
|
|
||||||
if (parse_uint(&sec, opt, optarg) != 0) {
|
if (parse_uint(&sec, opt, optarg) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->tv_sec = sec;
|
*dest = sec;
|
||||||
dest->tv_usec = 0;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -856,18 +857,22 @@ int parse_config(const char *opt, const char *optarg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::strieq(opt, SHRPX_OPT_WORKER_READ_RATE)) {
|
if (util::strieq(opt, SHRPX_OPT_WORKER_READ_RATE)) {
|
||||||
|
LOG(WARN) << opt << ": not implemented yet";
|
||||||
return parse_uint(&mod_config()->worker_read_rate, opt, optarg);
|
return parse_uint(&mod_config()->worker_read_rate, opt, optarg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::strieq(opt, SHRPX_OPT_WORKER_READ_BURST)) {
|
if (util::strieq(opt, SHRPX_OPT_WORKER_READ_BURST)) {
|
||||||
|
LOG(WARN) << opt << ": not implemented yet";
|
||||||
return parse_uint(&mod_config()->worker_read_burst, opt, optarg);
|
return parse_uint(&mod_config()->worker_read_burst, opt, optarg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_RATE)) {
|
if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_RATE)) {
|
||||||
|
LOG(WARN) << opt << ": not implemented yet";
|
||||||
return parse_uint(&mod_config()->worker_write_rate, opt, optarg);
|
return parse_uint(&mod_config()->worker_write_rate, opt, optarg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_BURST)) {
|
if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_BURST)) {
|
||||||
|
LOG(WARN) << opt << ": not implemented yet";
|
||||||
return parse_uint(&mod_config()->worker_write_burst, opt, optarg);
|
return parse_uint(&mod_config()->worker_write_burst, opt, optarg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,6 +1032,24 @@ int parse_config(const char *opt, const char *optarg) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND)) {
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (parse_uint(&n, opt, optarg) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_config()->downstream_connections_per_frontend = n;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (util::strieq(opt, SHRPX_OPT_LISTENER_DISABLE_TIMEOUT)) {
|
if (util::strieq(opt, SHRPX_OPT_LISTENER_DISABLE_TIMEOUT)) {
|
||||||
return parse_timeval(&mod_config()->listener_disable_timeout, opt, optarg);
|
return parse_timeval(&mod_config()->listener_disable_timeout, opt, optarg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,10 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
#include <event.h>
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
@ -124,6 +125,7 @@ extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[];
|
||||||
extern const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[];
|
extern const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[];
|
||||||
extern const char SHRPX_OPT_NO_LOCATION_REWRITE[];
|
extern const char SHRPX_OPT_NO_LOCATION_REWRITE[];
|
||||||
extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[];
|
extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[];
|
||||||
|
extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[];
|
||||||
extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[];
|
extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[];
|
||||||
|
|
||||||
union sockaddr_union {
|
union sockaddr_union {
|
||||||
|
@ -171,15 +173,15 @@ struct Config {
|
||||||
std::vector<DownstreamAddr> downstream_addrs;
|
std::vector<DownstreamAddr> downstream_addrs;
|
||||||
// binary form of http proxy host and port
|
// binary form of http proxy host and port
|
||||||
sockaddr_union downstream_http_proxy_addr;
|
sockaddr_union downstream_http_proxy_addr;
|
||||||
timeval http2_upstream_read_timeout;
|
ev_tstamp http2_upstream_read_timeout;
|
||||||
timeval upstream_read_timeout;
|
ev_tstamp upstream_read_timeout;
|
||||||
timeval upstream_write_timeout;
|
ev_tstamp upstream_write_timeout;
|
||||||
timeval downstream_read_timeout;
|
ev_tstamp downstream_read_timeout;
|
||||||
timeval downstream_write_timeout;
|
ev_tstamp downstream_write_timeout;
|
||||||
timeval stream_read_timeout;
|
ev_tstamp stream_read_timeout;
|
||||||
timeval stream_write_timeout;
|
ev_tstamp stream_write_timeout;
|
||||||
timeval downstream_idle_read_timeout;
|
ev_tstamp downstream_idle_read_timeout;
|
||||||
timeval listener_disable_timeout;
|
ev_tstamp listener_disable_timeout;
|
||||||
std::unique_ptr<char[]> host;
|
std::unique_ptr<char[]> host;
|
||||||
std::unique_ptr<char[]> private_key_file;
|
std::unique_ptr<char[]> private_key_file;
|
||||||
std::unique_ptr<char[]> private_key_passwd;
|
std::unique_ptr<char[]> private_key_passwd;
|
||||||
|
@ -199,10 +201,10 @@ struct Config {
|
||||||
std::unique_ptr<char[]> downstream_http_proxy_host;
|
std::unique_ptr<char[]> downstream_http_proxy_host;
|
||||||
std::unique_ptr<char[]> http2_upstream_dump_request_header_file;
|
std::unique_ptr<char[]> http2_upstream_dump_request_header_file;
|
||||||
std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
|
std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
|
||||||
// Rate limit configuration per connection
|
// // Rate limit configuration per connection
|
||||||
ev_token_bucket_cfg *rate_limit_cfg;
|
// ev_token_bucket_cfg *rate_limit_cfg;
|
||||||
// Rate limit configuration per worker (thread)
|
// // Rate limit configuration per worker (thread)
|
||||||
ev_token_bucket_cfg *worker_rate_limit_cfg;
|
// ev_token_bucket_cfg *worker_rate_limit_cfg;
|
||||||
// list of supported NPN/ALPN protocol strings in the order of
|
// list of supported NPN/ALPN protocol strings in the order of
|
||||||
// preference. The each element of this list is a NULL-terminated
|
// preference. The each element of this list is a NULL-terminated
|
||||||
// string.
|
// string.
|
||||||
|
@ -229,6 +231,7 @@ struct Config {
|
||||||
size_t http2_upstream_connection_window_bits;
|
size_t http2_upstream_connection_window_bits;
|
||||||
size_t http2_downstream_connection_window_bits;
|
size_t http2_downstream_connection_window_bits;
|
||||||
size_t downstream_connections_per_host;
|
size_t downstream_connections_per_host;
|
||||||
|
size_t downstream_connections_per_frontend;
|
||||||
// actual size of downstream_http_proxy_addr
|
// actual size of downstream_http_proxy_addr
|
||||||
size_t downstream_http_proxy_addrlen;
|
size_t downstream_http_proxy_addrlen;
|
||||||
size_t read_rate;
|
size_t read_rate;
|
||||||
|
|
|
@ -27,55 +27,35 @@
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const int INITIAL_SLEEP = 2;
|
const ev_tstamp INITIAL_SLEEP = 2.;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ConnectBlocker::ConnectBlocker() : timerev_(nullptr), sleep_(INITIAL_SLEEP) {}
|
|
||||||
|
|
||||||
ConnectBlocker::~ConnectBlocker() {
|
|
||||||
if (timerev_) {
|
|
||||||
event_free(timerev_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void connect_blocker_cb(evutil_socket_t sig, short events, void *arg) {
|
void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "unblock downstream connection";
|
LOG(INFO) << "unblock downstream connection";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int ConnectBlocker::init(event_base *evbase) {
|
ConnectBlocker::ConnectBlocker(struct ev_loop *loop)
|
||||||
timerev_ = evtimer_new(evbase, connect_blocker_cb, this);
|
: loop_(loop), sleep_(INITIAL_SLEEP) {
|
||||||
|
ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
|
||||||
if (timerev_ == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConnectBlocker::blocked() const {
|
ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
|
||||||
return evtimer_pending(timerev_, nullptr);
|
|
||||||
}
|
bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
|
||||||
|
|
||||||
void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
|
void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
|
||||||
|
|
||||||
void ConnectBlocker::on_failure() {
|
void ConnectBlocker::on_failure() {
|
||||||
int rv;
|
sleep_ = std::min(128., sleep_ * 2);
|
||||||
|
|
||||||
sleep_ = std::min(128, sleep_ * 2);
|
|
||||||
|
|
||||||
LOG(WARN) << "connect failure, start sleeping " << sleep_;
|
LOG(WARN) << "connect failure, start sleeping " << sleep_;
|
||||||
|
|
||||||
timeval t = {sleep_, 0};
|
ev_timer_set(&timer_, sleep_, 0.);
|
||||||
|
ev_timer_start(loop_, &timer_);
|
||||||
rv = evtimer_add(timerev_, &t);
|
|
||||||
|
|
||||||
if (rv == -1) {
|
|
||||||
LOG(ERROR) << "evtimer_add for ConnectBlocker timerev_ failed";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -27,16 +27,15 @@
|
||||||
|
|
||||||
#include "shrpx.h"
|
#include "shrpx.h"
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <ev.h>
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
class ConnectBlocker {
|
class ConnectBlocker {
|
||||||
public:
|
public:
|
||||||
ConnectBlocker();
|
ConnectBlocker(struct ev_loop *loop);
|
||||||
~ConnectBlocker();
|
~ConnectBlocker();
|
||||||
|
|
||||||
int init(event_base *evbase);
|
|
||||||
// Returns true if making connection is not allowed.
|
// Returns true if making connection is not allowed.
|
||||||
bool blocked() const;
|
bool blocked() const;
|
||||||
// Call this function if connect operation succeeded. This will
|
// Call this function if connect operation succeeded. This will
|
||||||
|
@ -47,8 +46,9 @@ public:
|
||||||
void on_failure();
|
void on_failure();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
event *timerev_;
|
ev_timer timer_;
|
||||||
int sleep_;
|
struct ev_loop *loop_;
|
||||||
|
ev_tstamp sleep_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -38,45 +38,123 @@
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto downstream = static_cast<Downstream *>(w->data);
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
|
||||||
|
auto which = revents == EV_READ ? "read" : "write";
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DLOG(INFO, downstream) << "upstream timeout stream_id="
|
||||||
|
<< downstream->get_stream_id() << " event=" << which;
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->disable_upstream_rtimer();
|
||||||
|
downstream->disable_upstream_wtimer();
|
||||||
|
|
||||||
|
upstream->on_timeout(downstream);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void upstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
upstream_timeoutcb(loop, w, EV_READ);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void upstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
upstream_timeoutcb(loop, w, EV_WRITE);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void downstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto downstream = static_cast<Downstream *>(w->data);
|
||||||
|
|
||||||
|
auto which = revents == EV_READ ? "read" : "write";
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DLOG(INFO, downstream) << "downstream timeout stream_id="
|
||||||
|
<< downstream->get_downstream_stream_id()
|
||||||
|
<< " event=" << which;
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->disable_downstream_rtimer();
|
||||||
|
downstream->disable_downstream_wtimer();
|
||||||
|
|
||||||
|
auto dconn = downstream->get_downstream_connection();
|
||||||
|
|
||||||
|
if (dconn) {
|
||||||
|
dconn->on_timeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void downstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
downstream_timeoutcb(loop, w, EV_READ);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
downstream_timeoutcb(loop, w, EV_WRITE);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// upstream could be nullptr for unittests
|
||||||
Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
|
Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
|
||||||
: request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
|
: request_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||||
upstream_(upstream), response_body_buf_(nullptr),
|
response_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||||
upstream_rtimerev_(nullptr), upstream_wtimerev_(nullptr),
|
request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
|
||||||
downstream_rtimerev_(nullptr), downstream_wtimerev_(nullptr),
|
upstream_(upstream), request_headers_sum_(0), response_headers_sum_(0),
|
||||||
request_headers_sum_(0), response_headers_sum_(0), request_datalen_(0),
|
request_datalen_(0), response_datalen_(0), stream_id_(stream_id),
|
||||||
response_datalen_(0), stream_id_(stream_id), priority_(priority),
|
priority_(priority), downstream_stream_id_(-1),
|
||||||
downstream_stream_id_(-1),
|
|
||||||
response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
|
response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
|
||||||
request_state_(INITIAL), request_major_(1), request_minor_(1),
|
request_state_(INITIAL), request_major_(1), request_minor_(1),
|
||||||
response_state_(INITIAL), response_http_status_(0), response_major_(1),
|
response_state_(INITIAL), response_http_status_(0), response_major_(1),
|
||||||
response_minor_(1), upgrade_request_(false), upgraded_(false),
|
response_minor_(1), upgrade_request_(false), upgraded_(false),
|
||||||
http2_upgrade_seen_(false), http2_settings_seen_(false),
|
http2_upgrade_seen_(false), chunked_request_(false),
|
||||||
chunked_request_(false), request_connection_close_(false),
|
request_connection_close_(false), request_header_key_prev_(false),
|
||||||
request_header_key_prev_(false), request_http2_expect_body_(false),
|
request_http2_expect_body_(false), chunked_response_(false),
|
||||||
chunked_response_(false), response_connection_close_(false),
|
response_connection_close_(false), response_header_key_prev_(false),
|
||||||
response_header_key_prev_(false), expect_final_response_(false),
|
expect_final_response_(false) {
|
||||||
request_headers_normalized_(false) {}
|
|
||||||
|
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
|
||||||
|
get_config()->stream_read_timeout);
|
||||||
|
ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0.,
|
||||||
|
get_config()->stream_write_timeout);
|
||||||
|
ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0.,
|
||||||
|
get_config()->stream_read_timeout);
|
||||||
|
ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
|
||||||
|
get_config()->stream_write_timeout);
|
||||||
|
|
||||||
|
upstream_rtimer_.data = this;
|
||||||
|
upstream_wtimer_.data = this;
|
||||||
|
downstream_rtimer_.data = this;
|
||||||
|
downstream_wtimer_.data = this;
|
||||||
|
|
||||||
|
http2::init_hdidx(request_hdidx_);
|
||||||
|
http2::init_hdidx(response_hdidx_);
|
||||||
|
}
|
||||||
|
|
||||||
Downstream::~Downstream() {
|
Downstream::~Downstream() {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DLOG(INFO, this) << "Deleting";
|
DLOG(INFO, this) << "Deleting";
|
||||||
}
|
}
|
||||||
if (response_body_buf_) {
|
|
||||||
// Passing NULL to evbuffer_free() causes segmentation fault.
|
// check nullptr for unittest
|
||||||
evbuffer_free(response_body_buf_);
|
if (upstream_) {
|
||||||
}
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
if (upstream_rtimerev_) {
|
|
||||||
event_free(upstream_rtimerev_);
|
ev_timer_stop(loop, &upstream_rtimer_);
|
||||||
}
|
ev_timer_stop(loop, &upstream_wtimer_);
|
||||||
if (upstream_wtimerev_) {
|
ev_timer_stop(loop, &downstream_rtimer_);
|
||||||
event_free(upstream_wtimerev_);
|
ev_timer_stop(loop, &downstream_wtimer_);
|
||||||
}
|
|
||||||
if (downstream_rtimerev_) {
|
|
||||||
event_free(downstream_rtimerev_);
|
|
||||||
}
|
|
||||||
if (downstream_wtimerev_) {
|
|
||||||
event_free(downstream_wtimerev_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DLOG(INFO, this) << "Deleted";
|
DLOG(INFO, this) << "Deleted";
|
||||||
}
|
}
|
||||||
|
@ -137,35 +215,15 @@ void Downstream::force_resume_read() {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Headers::const_iterator get_norm_header(const Headers &headers,
|
const Headers::value_type *get_header_linear(const Headers &headers,
|
||||||
const std::string &name) {
|
const std::string &name) {
|
||||||
auto i = std::lower_bound(std::begin(headers), std::end(headers),
|
const Headers::value_type *res = nullptr;
|
||||||
Header(name, ""), http2::name_less);
|
for (auto &kv : headers) {
|
||||||
if (i != std::end(headers) && (*i).name == name) {
|
if (kv.name == name) {
|
||||||
return i;
|
res = &kv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return std::end(headers);
|
return res;
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Headers::iterator get_norm_header(Headers &headers, const std::string &name) {
|
|
||||||
auto i = std::lower_bound(std::begin(headers), std::end(headers),
|
|
||||||
Header(name, ""), http2::name_less);
|
|
||||||
if (i != std::end(headers) && (*i).name == name) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
return std::end(headers);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
Headers::const_iterator get_header_linear(const Headers &headers,
|
|
||||||
const std::string &name) {
|
|
||||||
auto i = std::find_if(
|
|
||||||
std::begin(headers), std::end(headers),
|
|
||||||
[&name](const Header &header) { return header.name == name; });
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -177,90 +235,70 @@ void Downstream::assemble_request_cookie() {
|
||||||
std::string &cookie = assembled_request_cookie_;
|
std::string &cookie = assembled_request_cookie_;
|
||||||
cookie = "";
|
cookie = "";
|
||||||
for (auto &kv : request_headers_) {
|
for (auto &kv : request_headers_) {
|
||||||
if (util::strieq("cookie", kv.name.c_str())) {
|
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
|
||||||
auto end = kv.value.find_last_not_of(" ;");
|
!util::streq("cooki", kv.name.c_str(), 5)) {
|
||||||
if (end == std::string::npos) {
|
continue;
|
||||||
cookie += kv.value;
|
|
||||||
} else {
|
|
||||||
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
|
|
||||||
}
|
|
||||||
cookie += "; ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto end = kv.value.find_last_not_of(" ;");
|
||||||
|
if (end == std::string::npos) {
|
||||||
|
cookie += kv.value;
|
||||||
|
} else {
|
||||||
|
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
|
||||||
|
}
|
||||||
|
cookie += "; ";
|
||||||
}
|
}
|
||||||
if (cookie.size() >= 2) {
|
if (cookie.size() >= 2) {
|
||||||
cookie.erase(cookie.size() - 2);
|
cookie.erase(cookie.size() - 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::crumble_request_cookie() {
|
Headers Downstream::crumble_request_cookie() {
|
||||||
Headers cookie_hdrs;
|
Headers cookie_hdrs;
|
||||||
for (auto &kv : request_headers_) {
|
for (auto &kv : request_headers_) {
|
||||||
if (util::strieq("cookie", kv.name.c_str())) {
|
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
|
||||||
size_t last = kv.value.size();
|
!util::streq("cooki", kv.name.c_str(), 5)) {
|
||||||
size_t num = 0;
|
continue;
|
||||||
std::string rep_cookie;
|
}
|
||||||
|
size_t last = kv.value.size();
|
||||||
|
|
||||||
for (size_t j = 0; j < last;) {
|
for (size_t j = 0; j < last;) {
|
||||||
j = kv.value.find_first_not_of("\t ;", j);
|
j = kv.value.find_first_not_of("\t ;", j);
|
||||||
if (j == std::string::npos) {
|
if (j == std::string::npos) {
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
auto first = j;
|
|
||||||
|
|
||||||
j = kv.value.find(';', j);
|
|
||||||
if (j == std::string::npos) {
|
|
||||||
j = last;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (num == 0) {
|
|
||||||
if (first == 0 && j == last) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rep_cookie = kv.value.substr(first, j - first);
|
|
||||||
} else {
|
|
||||||
cookie_hdrs.push_back(
|
|
||||||
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
|
|
||||||
}
|
|
||||||
++num;
|
|
||||||
}
|
}
|
||||||
if (num > 0) {
|
auto first = j;
|
||||||
kv.value = std::move(rep_cookie);
|
|
||||||
|
j = kv.value.find(';', j);
|
||||||
|
if (j == std::string::npos) {
|
||||||
|
j = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookie_hdrs.push_back(
|
||||||
|
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request_headers_.insert(std::end(request_headers_),
|
return cookie_hdrs;
|
||||||
std::make_move_iterator(std::begin(cookie_hdrs)),
|
|
||||||
std::make_move_iterator(std::end(cookie_hdrs)));
|
|
||||||
if (request_headers_normalized_) {
|
|
||||||
normalize_request_headers();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &Downstream::get_assembled_request_cookie() const {
|
const std::string &Downstream::get_assembled_request_cookie() const {
|
||||||
return assembled_request_cookie_;
|
return assembled_request_cookie_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::normalize_request_headers() {
|
void Downstream::index_request_headers() {
|
||||||
http2::normalize_headers(request_headers_);
|
for (auto &kv : request_headers_) {
|
||||||
request_headers_normalized_ = true;
|
util::inp_strlower(kv.name);
|
||||||
}
|
|
||||||
|
|
||||||
Headers::const_iterator
|
|
||||||
Downstream::get_norm_request_header(const std::string &name) const {
|
|
||||||
return get_norm_header(request_headers_, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Headers::const_iterator
|
|
||||||
Downstream::get_request_header(const std::string &name) const {
|
|
||||||
if (request_headers_normalized_) {
|
|
||||||
return get_norm_request_header(name);
|
|
||||||
}
|
}
|
||||||
|
http2::index_headers(request_hdidx_, request_headers_);
|
||||||
return get_header_linear(request_headers_, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Downstream::get_request_headers_normalized() const {
|
const Headers::value_type *Downstream::get_request_header(int token) const {
|
||||||
return request_headers_normalized_;
|
return http2::get_header(request_hdidx_, token, request_headers_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Headers::value_type *
|
||||||
|
Downstream::get_request_header(const std::string &name) const {
|
||||||
|
return get_header_linear(request_headers_, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::add_request_header(std::string name, std::string value) {
|
void Downstream::add_request_header(std::string name, std::string value) {
|
||||||
|
@ -276,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) {
|
||||||
item.value = std::move(value);
|
item.value = std::move(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::split_add_request_header(const uint8_t *name, size_t namelen,
|
void Downstream::add_request_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen,
|
const uint8_t *value, size_t valuelen,
|
||||||
bool no_index) {
|
bool no_index, int token) {
|
||||||
|
http2::index_header(request_hdidx_, token, request_headers_.size());
|
||||||
request_headers_sum_ += namelen + valuelen;
|
request_headers_sum_ += namelen + valuelen;
|
||||||
http2::add_header(request_headers_, name, namelen, value, valuelen, no_index);
|
http2::add_header(request_headers_, name, namelen, value, valuelen, no_index);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +341,10 @@ void Downstream::append_last_request_header_value(const char *data,
|
||||||
item.value.append(data, len);
|
item.value.append(data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::clear_request_headers() { Headers().swap(request_headers_); }
|
void Downstream::clear_request_headers() {
|
||||||
|
Headers().swap(request_headers_);
|
||||||
|
http2::init_hdidx(request_hdidx_);
|
||||||
|
}
|
||||||
|
|
||||||
size_t Downstream::get_request_headers_sum() const {
|
size_t Downstream::get_request_headers_sum() const {
|
||||||
return request_headers_sum_;
|
return request_headers_sum_;
|
||||||
|
@ -399,14 +441,16 @@ void Downstream::set_request_http2_expect_body(bool f) {
|
||||||
request_http2_expect_body_ = f;
|
request_http2_expect_body_ = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Downstream::get_output_buffer_full() {
|
bool Downstream::request_buf_full() {
|
||||||
if (dconn_) {
|
if (dconn_) {
|
||||||
return dconn_->get_output_buffer_full();
|
return request_buf_.rleft() >= 16384;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Memchunks4K *Downstream::get_request_buf() { return &request_buf_; }
|
||||||
|
|
||||||
// Call this function after this object is attached to
|
// Call this function after this object is attached to
|
||||||
// Downstream. Otherwise, the program will crash.
|
// Downstream. Otherwise, the program will crash.
|
||||||
int Downstream::push_request_headers() {
|
int Downstream::push_request_headers() {
|
||||||
|
@ -446,19 +490,23 @@ const Headers &Downstream::get_response_headers() const {
|
||||||
return response_headers_;
|
return response_headers_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::normalize_response_headers() {
|
void Downstream::index_response_headers() {
|
||||||
http2::normalize_headers(response_headers_);
|
for (auto &kv : response_headers_) {
|
||||||
|
util::inp_strlower(kv.name);
|
||||||
|
}
|
||||||
|
http2::index_headers(response_hdidx_, response_headers_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Headers::const_iterator
|
const Headers::value_type *Downstream::get_response_header(int token) const {
|
||||||
Downstream::get_norm_response_header(const std::string &name) const {
|
return http2::get_header(response_hdidx_, token, response_headers_);
|
||||||
return get_norm_header(response_headers_, name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::rewrite_norm_location_response_header(
|
void
|
||||||
const std::string &upstream_scheme, uint16_t upstream_port) {
|
Downstream::rewrite_location_response_header(const std::string &upstream_scheme,
|
||||||
auto hd = get_norm_header(response_headers_, "location");
|
uint16_t upstream_port) {
|
||||||
if (hd == std::end(response_headers_)) {
|
auto hd =
|
||||||
|
http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_);
|
||||||
|
if (!hd) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
http_parser_url u;
|
http_parser_url u;
|
||||||
|
@ -475,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header(
|
||||||
upstream_scheme, upstream_port);
|
upstream_scheme, upstream_port);
|
||||||
}
|
}
|
||||||
if (new_uri.empty()) {
|
if (new_uri.empty()) {
|
||||||
auto host = get_norm_request_header("host");
|
auto host = get_request_header(http2::HD_HOST);
|
||||||
if (host == std::end(request_headers_)) {
|
if (!host) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
|
new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
|
||||||
upstream_scheme, upstream_port);
|
upstream_scheme, upstream_port);
|
||||||
}
|
}
|
||||||
if (!new_uri.empty()) {
|
if (!new_uri.empty()) {
|
||||||
(*hd).value = std::move(new_uri);
|
auto idx = response_hdidx_[http2::HD_LOCATION];
|
||||||
|
response_headers_[idx].value = std::move(new_uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,9 +549,10 @@ void Downstream::set_last_response_header_value(std::string value) {
|
||||||
item.value = std::move(value);
|
item.value = std::move(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::split_add_response_header(const uint8_t *name, size_t namelen,
|
void Downstream::add_response_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value,
|
const uint8_t *value, size_t valuelen,
|
||||||
size_t valuelen, bool no_index) {
|
bool no_index, int token) {
|
||||||
|
http2::index_header(response_hdidx_, token, response_headers_.size());
|
||||||
response_headers_sum_ += namelen + valuelen;
|
response_headers_sum_ += namelen + valuelen;
|
||||||
http2::add_header(response_headers_, name, namelen, value, valuelen,
|
http2::add_header(response_headers_, name, namelen, value, valuelen,
|
||||||
no_index);
|
no_index);
|
||||||
|
@ -527,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data,
|
||||||
item.value.append(data, len);
|
item.value.append(data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::clear_response_headers() { Headers().swap(response_headers_); }
|
void Downstream::clear_response_headers() {
|
||||||
|
Headers().swap(response_headers_);
|
||||||
|
http2::init_hdidx(response_hdidx_);
|
||||||
|
}
|
||||||
|
|
||||||
size_t Downstream::get_response_headers_sum() const {
|
size_t Downstream::get_response_headers_sum() const {
|
||||||
return response_headers_sum_;
|
return response_headers_sum_;
|
||||||
|
@ -585,17 +638,15 @@ void Downstream::set_response_state(int state) { response_state_ = state; }
|
||||||
|
|
||||||
int Downstream::get_response_state() const { return response_state_; }
|
int Downstream::get_response_state() const { return response_state_; }
|
||||||
|
|
||||||
int Downstream::init_response_body_buf() {
|
Memchunks4K *Downstream::get_response_buf() { return &response_buf_; }
|
||||||
if (!response_body_buf_) {
|
|
||||||
response_body_buf_ = evbuffer_new();
|
|
||||||
if (response_body_buf_ == nullptr) {
|
|
||||||
DIE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
evbuffer *Downstream::get_response_body_buf() { return response_body_buf_; }
|
bool Downstream::response_buf_full() {
|
||||||
|
if (dconn_) {
|
||||||
|
return response_buf_.rleft() >= 64 * 1024;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Downstream::add_response_bodylen(size_t amount) {
|
void Downstream::add_response_bodylen(size_t amount) {
|
||||||
response_bodylen_ += amount;
|
response_bodylen_ += amount;
|
||||||
|
@ -641,37 +692,31 @@ void Downstream::inspect_http1_request() {
|
||||||
upgrade_request_ = true;
|
upgrade_request_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &hd : request_headers_) {
|
if (!upgrade_request_) {
|
||||||
if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) {
|
auto idx = request_hdidx_[http2::HD_UPGRADE];
|
||||||
// TODO Perform more strict checking for upgrade headers
|
if (idx != -1) {
|
||||||
upgrade_request_ = true;
|
upgrade_request_ = true;
|
||||||
|
|
||||||
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(),
|
auto &val = request_headers_[idx].value;
|
||||||
hd.value.size())) {
|
// TODO Perform more strict checking for upgrade headers
|
||||||
|
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(),
|
||||||
|
val.size())) {
|
||||||
http2_upgrade_seen_ = true;
|
http2_upgrade_seen_ = true;
|
||||||
}
|
}
|
||||||
} else if (!http2_settings_seen_ &&
|
|
||||||
util::strieq(hd.name.c_str(), "http2-settings")) {
|
|
||||||
|
|
||||||
http2_settings_seen_ = true;
|
|
||||||
http2_settings_ = hd.value;
|
|
||||||
} else if (!chunked_request_ &&
|
|
||||||
util::strieq(hd.name.c_str(), "transfer-encoding")) {
|
|
||||||
if (util::strifind(hd.value.c_str(), "chunked")) {
|
|
||||||
chunked_request_ = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING];
|
||||||
|
if (idx != -1 &&
|
||||||
|
util::strifind(request_headers_[idx].value.c_str(), "chunked")) {
|
||||||
|
chunked_request_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::inspect_http1_response() {
|
void Downstream::inspect_http1_response() {
|
||||||
for (auto &hd : response_headers_) {
|
auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
|
||||||
if (!chunked_response_ &&
|
if (idx != -1 &&
|
||||||
util::strieq(hd.name.c_str(), "transfer-encoding")) {
|
util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
|
||||||
if (util::strifind(hd.value.c_str(), "chunked")) {
|
chunked_response_ = true;
|
||||||
chunked_response_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -690,11 +735,20 @@ bool Downstream::get_upgraded() const { return upgraded_; }
|
||||||
bool Downstream::get_upgrade_request() const { return upgrade_request_; }
|
bool Downstream::get_upgrade_request() const { return upgrade_request_; }
|
||||||
|
|
||||||
bool Downstream::get_http2_upgrade_request() const {
|
bool Downstream::get_http2_upgrade_request() const {
|
||||||
return request_bodylen_ == 0 && http2_upgrade_seen_ && http2_settings_seen_;
|
return request_bodylen_ == 0 && http2_upgrade_seen_ &&
|
||||||
|
request_hdidx_[http2::HD_HTTP2_SETTINGS] != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const std::string EMPTY;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
const std::string &Downstream::get_http2_settings() const {
|
const std::string &Downstream::get_http2_settings() const {
|
||||||
return http2_settings_;
|
auto idx = request_hdidx_[http2::HD_HTTP2_SETTINGS];
|
||||||
|
if (idx == -1) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return request_headers_[idx].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::set_downstream_stream_id(int32_t stream_id) {
|
void Downstream::set_downstream_stream_id(int32_t stream_id) {
|
||||||
|
@ -756,207 +810,130 @@ bool pseudo_header_allowed(const Headers &headers) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool Downstream::request_pseudo_header_allowed() const {
|
bool Downstream::request_pseudo_header_allowed(int token) const {
|
||||||
return pseudo_header_allowed(request_headers_);
|
if (!pseudo_header_allowed(request_headers_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return http2::check_http2_request_pseudo_header(request_hdidx_, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Downstream::response_pseudo_header_allowed() const {
|
bool Downstream::response_pseudo_header_allowed(int token) const {
|
||||||
return pseudo_header_allowed(response_headers_);
|
if (!pseudo_header_allowed(response_headers_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return http2::check_http2_response_pseudo_header(response_hdidx_, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void upstream_timeoutcb(evutil_socket_t fd, short event, void *arg) {
|
void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); }
|
||||||
auto downstream = static_cast<Downstream *>(arg);
|
|
||||||
auto upstream = downstream->get_upstream();
|
|
||||||
|
|
||||||
auto which = event == EV_READ ? "read" : "write";
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream) << "upstream timeout stream_id="
|
|
||||||
<< downstream->get_stream_id() << " event=" << which;
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->disable_upstream_rtimer();
|
|
||||||
downstream->disable_upstream_wtimer();
|
|
||||||
|
|
||||||
upstream->on_timeout(downstream);
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void upstream_rtimeoutcb(evutil_socket_t fd, short event, void *arg) {
|
void try_reset_timer(struct ev_loop *loop, ev_timer *w) {
|
||||||
upstream_timeoutcb(fd, EV_READ, arg);
|
if (!ev_is_active(w)) {
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void upstream_wtimeoutcb(evutil_socket_t fd, short event, void *arg) {
|
|
||||||
upstream_timeoutcb(fd, EV_WRITE, arg);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
event *init_timer(event_base *evbase, event_callback_fn cb, void *arg) {
|
|
||||||
auto timerev = evtimer_new(evbase, cb, arg);
|
|
||||||
|
|
||||||
if (timerev == nullptr) {
|
|
||||||
LOG(WARN) << "timer initialization failed";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return timerev;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void Downstream::init_upstream_timer() {
|
|
||||||
auto evbase = upstream_->get_client_handler()->get_evbase();
|
|
||||||
|
|
||||||
if (get_config()->stream_read_timeout.tv_sec > 0) {
|
|
||||||
upstream_rtimerev_ = init_timer(evbase, upstream_rtimeoutcb, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_config()->stream_write_timeout.tv_sec > 0) {
|
|
||||||
upstream_wtimerev_ = init_timer(evbase, upstream_wtimeoutcb, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void reset_timer(event *timer, const timeval *timeout) {
|
|
||||||
if (!timer) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ev_timer_again(loop, w);
|
||||||
event_add(timer, timeout);
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void try_reset_timer(event *timer, const timeval *timeout) {
|
void ensure_timer(struct ev_loop *loop, ev_timer *w) {
|
||||||
if (!timer) {
|
if (ev_is_active(w)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ev_timer_again(loop, w);
|
||||||
if (!evtimer_pending(timer, nullptr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(timer, timeout);
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void ensure_timer(event *timer, const timeval *timeout) {
|
void disable_timer(struct ev_loop *loop, ev_timer *w) {
|
||||||
if (!timer) {
|
ev_timer_stop(loop, w);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evtimer_pending(timer, nullptr)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_add(timer, timeout);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void disable_timer(event *timer) {
|
|
||||||
if (!timer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event_del(timer);
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void Downstream::reset_upstream_rtimer() {
|
void Downstream::reset_upstream_rtimer() {
|
||||||
reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
|
if (get_config()->stream_read_timeout == 0.) {
|
||||||
try_reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
reset_timer(loop, &upstream_rtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::reset_upstream_wtimer() {
|
void Downstream::reset_upstream_wtimer() {
|
||||||
reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
try_reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
|
if (get_config()->stream_write_timeout != 0.) {
|
||||||
|
reset_timer(loop, &upstream_wtimer_);
|
||||||
|
}
|
||||||
|
if (get_config()->stream_read_timeout != 0.) {
|
||||||
|
try_reset_timer(loop, &upstream_rtimer_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::ensure_upstream_wtimer() {
|
void Downstream::ensure_upstream_wtimer() {
|
||||||
ensure_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
|
if (get_config()->stream_write_timeout == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
ensure_timer(loop, &upstream_wtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::disable_upstream_rtimer() {
|
void Downstream::disable_upstream_rtimer() {
|
||||||
disable_timer(upstream_rtimerev_);
|
if (get_config()->stream_read_timeout == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
disable_timer(loop, &upstream_rtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::disable_upstream_wtimer() {
|
void Downstream::disable_upstream_wtimer() {
|
||||||
disable_timer(upstream_wtimerev_);
|
if (get_config()->stream_write_timeout == 0.) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
namespace {
|
|
||||||
void downstream_timeoutcb(evutil_socket_t fd, short event, void *arg) {
|
|
||||||
auto downstream = static_cast<Downstream *>(arg);
|
|
||||||
|
|
||||||
auto which = event == EV_READ ? "read" : "write";
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DLOG(INFO, downstream) << "downstream timeout stream_id="
|
|
||||||
<< downstream->get_downstream_stream_id()
|
|
||||||
<< " event=" << which;
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->disable_downstream_rtimer();
|
|
||||||
downstream->disable_downstream_wtimer();
|
|
||||||
|
|
||||||
auto dconn = downstream->get_downstream_connection();
|
|
||||||
|
|
||||||
if (dconn) {
|
|
||||||
dconn->on_timeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void downstream_rtimeoutcb(evutil_socket_t fd, short event, void *arg) {
|
|
||||||
downstream_timeoutcb(fd, EV_READ, arg);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void downstream_wtimeoutcb(evutil_socket_t fd, short event, void *arg) {
|
|
||||||
downstream_timeoutcb(fd, EV_WRITE, arg);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void Downstream::init_downstream_timer() {
|
|
||||||
auto evbase = upstream_->get_client_handler()->get_evbase();
|
|
||||||
|
|
||||||
if (get_config()->stream_read_timeout.tv_sec > 0) {
|
|
||||||
downstream_rtimerev_ = init_timer(evbase, downstream_rtimeoutcb, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (get_config()->stream_write_timeout.tv_sec > 0) {
|
|
||||||
downstream_wtimerev_ = init_timer(evbase, downstream_wtimeoutcb, this);
|
|
||||||
}
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
disable_timer(loop, &upstream_wtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::reset_downstream_rtimer() {
|
void Downstream::reset_downstream_rtimer() {
|
||||||
reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
|
if (get_config()->stream_read_timeout == 0.) {
|
||||||
try_reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
reset_timer(loop, &downstream_rtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::reset_downstream_wtimer() {
|
void Downstream::reset_downstream_wtimer() {
|
||||||
reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
try_reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
|
if (get_config()->stream_write_timeout != 0.) {
|
||||||
|
reset_timer(loop, &downstream_wtimer_);
|
||||||
|
}
|
||||||
|
if (get_config()->stream_read_timeout != 0.) {
|
||||||
|
try_reset_timer(loop, &downstream_rtimer_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::ensure_downstream_wtimer() {
|
void Downstream::ensure_downstream_wtimer() {
|
||||||
ensure_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
|
if (get_config()->stream_write_timeout == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
ensure_timer(loop, &downstream_wtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::disable_downstream_rtimer() {
|
void Downstream::disable_downstream_rtimer() {
|
||||||
disable_timer(downstream_rtimerev_);
|
if (get_config()->stream_read_timeout == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
disable_timer(loop, &downstream_rtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::disable_downstream_wtimer() {
|
void Downstream::disable_downstream_wtimer() {
|
||||||
disable_timer(downstream_wtimerev_);
|
if (get_config()->stream_write_timeout == 0.) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto loop = upstream_->get_client_handler()->get_loop();
|
||||||
|
disable_timer(loop, &downstream_wtimer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Downstream::accesslog_ready() const { return response_http_status_ > 0; }
|
bool Downstream::accesslog_ready() const { return response_http_status_ > 0; }
|
||||||
|
|
|
@ -34,13 +34,13 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#include "shrpx_io_control.h"
|
#include "shrpx_io_control.h"
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
|
#include "memchunk.h"
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public:
|
||||||
|
|
||||||
// Returns true if output buffer is full. If underlying dconn_ is
|
// Returns true if output buffer is full. If underlying dconn_ is
|
||||||
// NULL, this function always returns false.
|
// NULL, this function always returns false.
|
||||||
bool get_output_buffer_full();
|
bool request_buf_full();
|
||||||
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
|
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
|
||||||
void check_upgrade_fulfilled();
|
void check_upgrade_fulfilled();
|
||||||
// Returns true if the request is upgrade.
|
// Returns true if the request is upgrade.
|
||||||
|
@ -95,30 +95,27 @@ public:
|
||||||
const std::string &get_http2_settings() const;
|
const std::string &get_http2_settings() const;
|
||||||
// downstream request API
|
// downstream request API
|
||||||
const Headers &get_request_headers() const;
|
const Headers &get_request_headers() const;
|
||||||
void crumble_request_cookie();
|
// Crumbles (split cookie by ";") in request_headers_ and returns
|
||||||
|
// them. Headers::no_index is inherited.
|
||||||
|
Headers crumble_request_cookie();
|
||||||
void assemble_request_cookie();
|
void assemble_request_cookie();
|
||||||
const std::string &get_assembled_request_cookie() const;
|
const std::string &get_assembled_request_cookie() const;
|
||||||
// Makes key lowercase and sort headers by name using <
|
// Lower the request header field names and indexes request headers
|
||||||
void normalize_request_headers();
|
void index_request_headers();
|
||||||
// Returns iterator pointing to the request header with the name
|
// Returns pointer to the request header with the name |name|. If
|
||||||
// |name|. If multiple header have |name| as name, return first
|
// multiple header have |name| as name, return last occurrence from
|
||||||
// occurrence from the beginning. If no such header is found,
|
// the beginning. If no such header is found, returns nullptr.
|
||||||
// returns std::end(get_request_headers()). This function must be
|
// This function must be called after headers are indexed
|
||||||
// called after calling normalize_request_headers().
|
const Headers::value_type *get_request_header(int token) const;
|
||||||
Headers::const_iterator
|
// Returns pointer to the request header with the name |name|. If
|
||||||
get_norm_request_header(const std::string &name) const;
|
// no such header is found, returns nullptr.
|
||||||
// Returns iterator pointing to the request header with the name
|
const Headers::value_type *get_request_header(const std::string &name) const;
|
||||||
// |name|. This function acts like get_norm_request_header(), but
|
|
||||||
// if request_headers_ was not normalized, use linear search to find
|
|
||||||
// the header. Otherwise, get_norm_request_header() is used.
|
|
||||||
Headers::const_iterator get_request_header(const std::string &name) const;
|
|
||||||
bool get_request_headers_normalized() const;
|
|
||||||
void add_request_header(std::string name, std::string value);
|
void add_request_header(std::string name, std::string value);
|
||||||
void set_last_request_header_value(std::string value);
|
void set_last_request_header_value(std::string value);
|
||||||
|
|
||||||
void split_add_request_header(const uint8_t *name, size_t namelen,
|
void add_request_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen,
|
const uint8_t *value, size_t valuelen, bool no_index,
|
||||||
bool no_index);
|
int token);
|
||||||
|
|
||||||
bool get_request_header_key_prev() const;
|
bool get_request_header_key_prev() const;
|
||||||
void append_last_request_header_key(const char *data, size_t len);
|
void append_last_request_header_key(const char *data, size_t len);
|
||||||
|
@ -161,7 +158,7 @@ public:
|
||||||
size_t get_request_datalen() const;
|
size_t get_request_datalen() const;
|
||||||
void dec_request_datalen(size_t len);
|
void dec_request_datalen(size_t len);
|
||||||
void reset_request_datalen();
|
void reset_request_datalen();
|
||||||
bool request_pseudo_header_allowed() const;
|
bool request_pseudo_header_allowed(int token) const;
|
||||||
bool expect_response_body() const;
|
bool expect_response_body() const;
|
||||||
enum {
|
enum {
|
||||||
INITIAL,
|
INITIAL,
|
||||||
|
@ -174,28 +171,25 @@ public:
|
||||||
};
|
};
|
||||||
void set_request_state(int state);
|
void set_request_state(int state);
|
||||||
int get_request_state() const;
|
int get_request_state() const;
|
||||||
|
Memchunks4K *get_request_buf();
|
||||||
// downstream response API
|
// downstream response API
|
||||||
const Headers &get_response_headers() const;
|
const Headers &get_response_headers() const;
|
||||||
// Makes key lowercase and sort headers by name using <
|
// Lower the response header field names and indexes response headers
|
||||||
void normalize_response_headers();
|
void index_response_headers();
|
||||||
// Returns iterator pointing to the response header with the name
|
// Returns pointer to the response header with the name |name|. If
|
||||||
// |name|. If multiple header have |name| as name, return first
|
// multiple header have |name| as name, return last occurrence from
|
||||||
// occurrence from the beginning. If no such header is found,
|
// the beginning. If no such header is found, returns nullptr.
|
||||||
// returns std::end(get_response_headers()). This function must be
|
// This function must be called after response headers are indexed.
|
||||||
// called after calling normalize_response_headers().
|
const Headers::value_type *get_response_header(int token) const;
|
||||||
Headers::const_iterator
|
// Rewrites the location response header field.
|
||||||
get_norm_response_header(const std::string &name) const;
|
void rewrite_location_response_header(const std::string &upstream_scheme,
|
||||||
// Rewrites the location response header field. This function must
|
uint16_t upstream_port);
|
||||||
// be called after calling normalize_response_headers() and
|
|
||||||
// normalize_request_headers().
|
|
||||||
void rewrite_norm_location_response_header(const std::string &upstream_scheme,
|
|
||||||
uint16_t upstream_port);
|
|
||||||
void add_response_header(std::string name, std::string value);
|
void add_response_header(std::string name, std::string value);
|
||||||
void set_last_response_header_value(std::string value);
|
void set_last_response_header_value(std::string value);
|
||||||
|
|
||||||
void split_add_response_header(const uint8_t *name, size_t namelen,
|
void add_response_header(const uint8_t *name, size_t namelen,
|
||||||
const uint8_t *value, size_t valuelen,
|
const uint8_t *value, size_t valuelen, bool no_index,
|
||||||
bool no_index);
|
int token);
|
||||||
|
|
||||||
bool get_response_header_key_prev() const;
|
bool get_response_header_key_prev() const;
|
||||||
void append_last_response_header_key(const char *data, size_t len);
|
void append_last_response_header_key(const char *data, size_t len);
|
||||||
|
@ -218,8 +212,8 @@ public:
|
||||||
void set_response_connection_close(bool f);
|
void set_response_connection_close(bool f);
|
||||||
void set_response_state(int state);
|
void set_response_state(int state);
|
||||||
int get_response_state() const;
|
int get_response_state() const;
|
||||||
int init_response_body_buf();
|
Memchunks4K *get_response_buf();
|
||||||
evbuffer *get_response_body_buf();
|
bool response_buf_full();
|
||||||
void add_response_bodylen(size_t amount);
|
void add_response_bodylen(size_t amount);
|
||||||
int64_t get_response_bodylen() const;
|
int64_t get_response_bodylen() const;
|
||||||
void add_response_sent_bodylen(size_t amount);
|
void add_response_sent_bodylen(size_t amount);
|
||||||
|
@ -237,7 +231,7 @@ public:
|
||||||
void dec_response_datalen(size_t len);
|
void dec_response_datalen(size_t len);
|
||||||
size_t get_response_datalen() const;
|
size_t get_response_datalen() const;
|
||||||
void reset_response_datalen();
|
void reset_response_datalen();
|
||||||
bool response_pseudo_header_allowed() const;
|
bool response_pseudo_header_allowed(int token) const;
|
||||||
|
|
||||||
// Call this method when there is incoming data in downstream
|
// Call this method when there is incoming data in downstream
|
||||||
// connection.
|
// connection.
|
||||||
|
@ -252,18 +246,15 @@ public:
|
||||||
bool get_rst_stream_after_end_stream() const;
|
bool get_rst_stream_after_end_stream() const;
|
||||||
void set_rst_stream_after_end_stream(bool f);
|
void set_rst_stream_after_end_stream(bool f);
|
||||||
|
|
||||||
// Initializes upstream timers, but they are not pending.
|
// Resets upstream read timer. If it is active, timeout value is
|
||||||
void init_upstream_timer();
|
// reset. If it is not active, timer will be started.
|
||||||
// Makes upstream read timer pending. If it is already pending,
|
|
||||||
// timeout value is reset. This function also resets write timer if
|
|
||||||
// it is already pending.
|
|
||||||
void reset_upstream_rtimer();
|
void reset_upstream_rtimer();
|
||||||
// Makes upstream write timer pending. If it is already pending,
|
// Resets upstream write timer. If it is active, timeout value is
|
||||||
// timeout value is reset. This function also resets read timer if
|
// reset. If it is not active, timer will be started. This
|
||||||
// it is already pending.
|
// function also resets read timer if it has been started.
|
||||||
void reset_upstream_wtimer();
|
void reset_upstream_wtimer();
|
||||||
// Makes upstream write timer pending. If it is already pending, do
|
// Makes sure that upstream write timer is started. If it has been
|
||||||
// nothing.
|
// started, do nothing. Otherwise, write timer will be started.
|
||||||
void ensure_upstream_wtimer();
|
void ensure_upstream_wtimer();
|
||||||
// Disables upstream read timer.
|
// Disables upstream read timer.
|
||||||
void disable_upstream_rtimer();
|
void disable_upstream_rtimer();
|
||||||
|
@ -272,7 +263,6 @@ public:
|
||||||
|
|
||||||
// Downstream timer functions. They works in a similar way just
|
// Downstream timer functions. They works in a similar way just
|
||||||
// like the upstream timer function.
|
// like the upstream timer function.
|
||||||
void init_downstream_timer();
|
|
||||||
void reset_downstream_rtimer();
|
void reset_downstream_rtimer();
|
||||||
void reset_downstream_wtimer();
|
void reset_downstream_wtimer();
|
||||||
void ensure_downstream_wtimer();
|
void ensure_downstream_wtimer();
|
||||||
|
@ -282,6 +272,11 @@ public:
|
||||||
// Returns true if accesslog can be written for this downstream.
|
// Returns true if accesslog can be written for this downstream.
|
||||||
bool accesslog_ready() const;
|
bool accesslog_ready() const;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EVENT_ERROR = 0x1,
|
||||||
|
EVENT_TIMEOUT = 0x2,
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Headers request_headers_;
|
Headers request_headers_;
|
||||||
Headers response_headers_;
|
Headers response_headers_;
|
||||||
|
@ -292,7 +287,15 @@ private:
|
||||||
std::string request_http2_authority_;
|
std::string request_http2_authority_;
|
||||||
std::chrono::high_resolution_clock::time_point request_start_time_;
|
std::chrono::high_resolution_clock::time_point request_start_time_;
|
||||||
std::string assembled_request_cookie_;
|
std::string assembled_request_cookie_;
|
||||||
std::string http2_settings_;
|
|
||||||
|
Memchunks4K request_buf_;
|
||||||
|
Memchunks4K response_buf_;
|
||||||
|
|
||||||
|
ev_timer upstream_rtimer_;
|
||||||
|
ev_timer upstream_wtimer_;
|
||||||
|
|
||||||
|
ev_timer downstream_rtimer_;
|
||||||
|
ev_timer downstream_wtimer_;
|
||||||
|
|
||||||
// the length of request body
|
// the length of request body
|
||||||
int64_t request_bodylen_;
|
int64_t request_bodylen_;
|
||||||
|
@ -303,15 +306,6 @@ private:
|
||||||
|
|
||||||
Upstream *upstream_;
|
Upstream *upstream_;
|
||||||
std::unique_ptr<DownstreamConnection> dconn_;
|
std::unique_ptr<DownstreamConnection> dconn_;
|
||||||
// This buffer is used to temporarily store downstream response
|
|
||||||
// body. nghttp2 library reads data from this in the callback.
|
|
||||||
evbuffer *response_body_buf_;
|
|
||||||
|
|
||||||
event *upstream_rtimerev_;
|
|
||||||
event *upstream_wtimerev_;
|
|
||||||
|
|
||||||
event *downstream_rtimerev_;
|
|
||||||
event *downstream_wtimerev_;
|
|
||||||
|
|
||||||
size_t request_headers_sum_;
|
size_t request_headers_sum_;
|
||||||
size_t response_headers_sum_;
|
size_t response_headers_sum_;
|
||||||
|
@ -337,6 +331,9 @@ private:
|
||||||
int response_major_;
|
int response_major_;
|
||||||
int response_minor_;
|
int response_minor_;
|
||||||
|
|
||||||
|
int request_hdidx_[http2::HD_MAXIDX];
|
||||||
|
int response_hdidx_[http2::HD_MAXIDX];
|
||||||
|
|
||||||
// true if the request contains upgrade token (HTTP Upgrade or
|
// true if the request contains upgrade token (HTTP Upgrade or
|
||||||
// CONNECT)
|
// CONNECT)
|
||||||
bool upgrade_request_;
|
bool upgrade_request_;
|
||||||
|
@ -344,7 +341,6 @@ private:
|
||||||
bool upgraded_;
|
bool upgraded_;
|
||||||
|
|
||||||
bool http2_upgrade_seen_;
|
bool http2_upgrade_seen_;
|
||||||
bool http2_settings_seen_;
|
|
||||||
|
|
||||||
bool chunked_request_;
|
bool chunked_request_;
|
||||||
bool request_connection_close_;
|
bool request_connection_close_;
|
||||||
|
@ -355,9 +351,6 @@ private:
|
||||||
bool response_connection_close_;
|
bool response_connection_close_;
|
||||||
bool response_header_key_prev_;
|
bool response_header_key_prev_;
|
||||||
bool expect_final_response_;
|
bool expect_final_response_;
|
||||||
|
|
||||||
// true if request_headers_ is normalized
|
|
||||||
bool request_headers_normalized_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -51,7 +51,7 @@ public:
|
||||||
virtual int resume_read(IOCtrlReason reason, size_t consumed) = 0;
|
virtual int resume_read(IOCtrlReason reason, size_t consumed) = 0;
|
||||||
virtual void force_resume_read() = 0;
|
virtual void force_resume_read() = 0;
|
||||||
|
|
||||||
virtual bool get_output_buffer_full() = 0;
|
enum { ERR_EOF = -100, ERR_NET = -101 };
|
||||||
|
|
||||||
virtual int on_read() = 0;
|
virtual int on_read() = 0;
|
||||||
virtual int on_write() = 0;
|
virtual int on_write() = 0;
|
||||||
|
|
|
@ -33,10 +33,11 @@ namespace shrpx {
|
||||||
|
|
||||||
DownstreamQueue::HostEntry::HostEntry() : num_active(0) {}
|
DownstreamQueue::HostEntry::HostEntry() : num_active(0) {}
|
||||||
|
|
||||||
DownstreamQueue::DownstreamQueue(size_t conn_max_per_host)
|
DownstreamQueue::DownstreamQueue(size_t conn_max_per_host, bool unified_host)
|
||||||
: conn_max_per_host_(conn_max_per_host == 0
|
: conn_max_per_host_(conn_max_per_host == 0
|
||||||
? std::numeric_limits<size_t>::max()
|
? std::numeric_limits<size_t>::max()
|
||||||
: conn_max_per_host) {}
|
: conn_max_per_host),
|
||||||
|
unified_host_(unified_host) {}
|
||||||
|
|
||||||
DownstreamQueue::~DownstreamQueue() {}
|
DownstreamQueue::~DownstreamQueue() {}
|
||||||
|
|
||||||
|
@ -59,8 +60,19 @@ DownstreamQueue::find_host_entry(const std::string &host) {
|
||||||
return (*itr).second;
|
return (*itr).second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string &
|
||||||
|
DownstreamQueue::make_host_key(const std::string &host) const {
|
||||||
|
static std::string empty_key;
|
||||||
|
return unified_host_ ? empty_key : host;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &
|
||||||
|
DownstreamQueue::make_host_key(Downstream *downstream) const {
|
||||||
|
return make_host_key(downstream->get_request_http2_authority());
|
||||||
|
}
|
||||||
|
|
||||||
void DownstreamQueue::add_active(std::unique_ptr<Downstream> downstream) {
|
void DownstreamQueue::add_active(std::unique_ptr<Downstream> downstream) {
|
||||||
auto &ent = find_host_entry(downstream->get_request_http2_authority());
|
auto &ent = find_host_entry(make_host_key(downstream.get()));
|
||||||
++ent.num_active;
|
++ent.num_active;
|
||||||
|
|
||||||
auto stream_id = downstream->get_stream_id();
|
auto stream_id = downstream->get_stream_id();
|
||||||
|
@ -68,14 +80,14 @@ void DownstreamQueue::add_active(std::unique_ptr<Downstream> downstream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DownstreamQueue::add_blocked(std::unique_ptr<Downstream> downstream) {
|
void DownstreamQueue::add_blocked(std::unique_ptr<Downstream> downstream) {
|
||||||
auto &ent = find_host_entry(downstream->get_request_http2_authority());
|
auto &ent = find_host_entry(make_host_key(downstream.get()));
|
||||||
auto stream_id = downstream->get_stream_id();
|
auto stream_id = downstream->get_stream_id();
|
||||||
ent.blocked.insert(stream_id);
|
ent.blocked.insert(stream_id);
|
||||||
blocked_downstreams_[stream_id] = std::move(downstream);
|
blocked_downstreams_[stream_id] = std::move(downstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DownstreamQueue::can_activate(const std::string &host) const {
|
bool DownstreamQueue::can_activate(const std::string &host) const {
|
||||||
auto itr = host_entries_.find(host);
|
auto itr = host_entries_.find(make_host_key(host));
|
||||||
if (itr == std::end(host_entries_)) {
|
if (itr == std::end(host_entries_)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -119,7 +131,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
|
||||||
|
|
||||||
if (kv != std::end(active_downstreams_)) {
|
if (kv != std::end(active_downstreams_)) {
|
||||||
auto downstream = pop_downstream(kv, active_downstreams_);
|
auto downstream = pop_downstream(kv, active_downstreams_);
|
||||||
auto &host = downstream->get_request_http2_authority();
|
auto &host = make_host_key(downstream.get());
|
||||||
auto &ent = find_host_entry(host);
|
auto &ent = find_host_entry(host);
|
||||||
--ent.num_active;
|
--ent.num_active;
|
||||||
|
|
||||||
|
@ -148,7 +160,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
|
||||||
|
|
||||||
if (kv != std::end(blocked_downstreams_)) {
|
if (kv != std::end(blocked_downstreams_)) {
|
||||||
auto downstream = pop_downstream(kv, blocked_downstreams_);
|
auto downstream = pop_downstream(kv, blocked_downstreams_);
|
||||||
auto &host = downstream->get_request_http2_authority();
|
auto &host = make_host_key(downstream.get());
|
||||||
auto &ent = find_host_entry(host);
|
auto &ent = find_host_entry(host);
|
||||||
ent.blocked.erase(stream_id);
|
ent.blocked.erase(stream_id);
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ public:
|
||||||
typedef std::map<std::string, HostEntry> HostEntryMap;
|
typedef std::map<std::string, HostEntry> HostEntryMap;
|
||||||
|
|
||||||
// conn_max_per_host == 0 means no limit for downstream connection.
|
// conn_max_per_host == 0 means no limit for downstream connection.
|
||||||
DownstreamQueue(size_t conn_max_per_host = 0);
|
DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true);
|
||||||
~DownstreamQueue();
|
~DownstreamQueue();
|
||||||
void add_pending(std::unique_ptr<Downstream> downstream);
|
void add_pending(std::unique_ptr<Downstream> downstream);
|
||||||
void add_failure(std::unique_ptr<Downstream> downstream);
|
void add_failure(std::unique_ptr<Downstream> downstream);
|
||||||
|
@ -82,6 +82,8 @@ public:
|
||||||
Downstream *find(int32_t stream_id);
|
Downstream *find(int32_t stream_id);
|
||||||
const DownstreamMap &get_active_downstreams() const;
|
const DownstreamMap &get_active_downstreams() const;
|
||||||
HostEntry &find_host_entry(const std::string &host);
|
HostEntry &find_host_entry(const std::string &host);
|
||||||
|
const std::string &make_host_key(const std::string &host) const;
|
||||||
|
const std::string &make_host_key(Downstream *downstream) const;
|
||||||
|
|
||||||
// Maximum number of concurrent connections to the same host.
|
// Maximum number of concurrent connections to the same host.
|
||||||
size_t conn_max_per_host_;
|
size_t conn_max_per_host_;
|
||||||
|
@ -98,6 +100,9 @@ private:
|
||||||
DownstreamMap active_downstreams_;
|
DownstreamMap active_downstreams_;
|
||||||
// Downstream objects, blocked by conn_max_per_host_
|
// Downstream objects, blocked by conn_max_per_host_
|
||||||
DownstreamMap blocked_downstreams_;
|
DownstreamMap blocked_downstreams_;
|
||||||
|
// true if downstream host is treated as the same. Used for reverse
|
||||||
|
// proxying.
|
||||||
|
bool unified_host_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
void test_downstream_normalize_request_headers(void) {
|
void test_downstream_index_request_headers(void) {
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_request_header("1", "0");
|
d.add_request_header("1", "0");
|
||||||
d.add_request_header("2", "1");
|
d.add_request_header("2", "1");
|
||||||
|
@ -42,88 +42,83 @@ void test_downstream_normalize_request_headers(void) {
|
||||||
d.add_request_header("BravO", "5");
|
d.add_request_header("BravO", "5");
|
||||||
d.add_request_header(":method", "6");
|
d.add_request_header(":method", "6");
|
||||||
d.add_request_header(":authority", "7");
|
d.add_request_header(":authority", "7");
|
||||||
d.normalize_request_headers();
|
d.index_request_headers();
|
||||||
|
|
||||||
auto ans = Headers{{":authority", "7"},
|
auto ans = Headers{{"1", "0"},
|
||||||
{":method", "6"},
|
|
||||||
{"1", "0"},
|
|
||||||
{"2", "1"},
|
{"2", "1"},
|
||||||
{"alpha", "3"},
|
|
||||||
{"bravo", "5"},
|
|
||||||
{"charlie", "2"},
|
{"charlie", "2"},
|
||||||
{"delta", "4"}};
|
{"alpha", "3"},
|
||||||
|
{"delta", "4"},
|
||||||
|
{"bravo", "5"},
|
||||||
|
{":method", "6"},
|
||||||
|
{":authority", "7"}};
|
||||||
CU_ASSERT(ans == d.get_request_headers());
|
CU_ASSERT(ans == d.get_request_headers());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_normalize_response_headers(void) {
|
void test_downstream_index_response_headers(void) {
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_response_header("Charlie", "0");
|
d.add_response_header("Charlie", "0");
|
||||||
d.add_response_header("Alpha", "1");
|
d.add_response_header("Alpha", "1");
|
||||||
d.add_response_header("Delta", "2");
|
d.add_response_header("Delta", "2");
|
||||||
d.add_response_header("BravO", "3");
|
d.add_response_header("BravO", "3");
|
||||||
d.normalize_response_headers();
|
d.index_response_headers();
|
||||||
|
|
||||||
auto ans =
|
auto ans =
|
||||||
Headers{{"alpha", "1"}, {"bravo", "3"}, {"charlie", "0"}, {"delta", "2"}};
|
Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
|
||||||
CU_ASSERT(ans == d.get_response_headers());
|
CU_ASSERT(ans == d.get_response_headers());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_get_norm_request_header(void) {
|
void test_downstream_get_request_header(void) {
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_request_header("alpha", "0");
|
d.add_request_header("alpha", "0");
|
||||||
d.add_request_header("bravo", "1");
|
d.add_request_header(":authority", "1");
|
||||||
d.add_request_header("bravo", "2");
|
d.add_request_header("content-length", "2");
|
||||||
d.add_request_header("charlie", "3");
|
d.index_request_headers();
|
||||||
d.add_request_header("delta", "4");
|
|
||||||
d.add_request_header("echo", "5");
|
// By token
|
||||||
auto i = d.get_norm_request_header("alpha");
|
CU_ASSERT(Header(":authority", "1") ==
|
||||||
CU_ASSERT(Header("alpha", "0") == *i);
|
*d.get_request_header(http2::HD__AUTHORITY));
|
||||||
i = d.get_norm_request_header("bravo");
|
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
|
||||||
CU_ASSERT(Header("bravo", "1") == *i);
|
|
||||||
i = d.get_norm_request_header("delta");
|
// By name
|
||||||
CU_ASSERT(Header("delta", "4") == *i);
|
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
|
||||||
i = d.get_norm_request_header("echo");
|
CU_ASSERT(nullptr == d.get_request_header("bravo"));
|
||||||
CU_ASSERT(Header("echo", "5") == *i);
|
|
||||||
i = d.get_norm_request_header("foxtrot");
|
|
||||||
CU_ASSERT(i == std::end(d.get_request_headers()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_get_norm_response_header(void) {
|
void test_downstream_get_response_header(void) {
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_response_header("alpha", "0");
|
d.add_response_header("alpha", "0");
|
||||||
d.add_response_header("bravo", "1");
|
d.add_response_header(":status", "1");
|
||||||
d.add_response_header("bravo", "2");
|
d.add_response_header("content-length", "2");
|
||||||
d.add_response_header("charlie", "3");
|
d.index_response_headers();
|
||||||
d.add_response_header("delta", "4");
|
|
||||||
d.add_response_header("echo", "5");
|
// By token
|
||||||
auto i = d.get_norm_response_header("alpha");
|
CU_ASSERT(Header(":status", "1") ==
|
||||||
CU_ASSERT(Header("alpha", "0") == *i);
|
*d.get_response_header(http2::HD__STATUS));
|
||||||
i = d.get_norm_response_header("bravo");
|
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
|
||||||
CU_ASSERT(Header("bravo", "1") == *i);
|
|
||||||
i = d.get_norm_response_header("delta");
|
|
||||||
CU_ASSERT(Header("delta", "4") == *i);
|
|
||||||
i = d.get_norm_response_header("echo");
|
|
||||||
CU_ASSERT(Header("echo", "5") == *i);
|
|
||||||
i = d.get_norm_response_header("foxtrot");
|
|
||||||
CU_ASSERT(i == std::end(d.get_response_headers()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_crumble_request_cookie(void) {
|
void test_downstream_crumble_request_cookie(void) {
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_request_header(":method", "get");
|
d.add_request_header(":method", "get");
|
||||||
d.add_request_header(":path", "/");
|
d.add_request_header(":path", "/");
|
||||||
d.add_request_header("cookie", "alpha; bravo; ; ;; charlie;;");
|
auto val = "alpha; bravo; ; ;; charlie;;";
|
||||||
|
d.add_request_header(
|
||||||
|
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
|
||||||
|
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
|
||||||
d.add_request_header("cookie", ";delta");
|
d.add_request_header("cookie", ";delta");
|
||||||
d.add_request_header("cookie", "echo");
|
d.add_request_header("cookie", "echo");
|
||||||
d.crumble_request_cookie();
|
auto cookies = d.crumble_request_cookie();
|
||||||
Headers ans = {{":method", "get"},
|
|
||||||
{":path", "/"},
|
Headers ans = {{"cookie", "alpha"},
|
||||||
{"cookie", "alpha"},
|
|
||||||
{"cookie", "delta"},
|
|
||||||
{"cookie", "echo"},
|
|
||||||
{"cookie", "bravo"},
|
{"cookie", "bravo"},
|
||||||
{"cookie", "charlie"}};
|
{"cookie", "charlie"},
|
||||||
CU_ASSERT(ans == d.get_request_headers());
|
{"cookie", "delta"},
|
||||||
|
{"cookie", "echo"}};
|
||||||
|
CU_ASSERT(ans == cookies);
|
||||||
|
CU_ASSERT(cookies[0].no_index);
|
||||||
|
CU_ASSERT(cookies[1].no_index);
|
||||||
|
CU_ASSERT(cookies[2].no_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_assemble_request_cookie(void) {
|
void test_downstream_assemble_request_cookie(void) {
|
||||||
|
@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) {
|
||||||
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
|
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_downstream_rewrite_norm_location_response_header(void) {
|
void test_downstream_rewrite_location_response_header(void) {
|
||||||
{
|
{
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.add_request_header("host", "localhost:3000");
|
d.add_request_header("host", "localhost:3000");
|
||||||
d.add_response_header("location", "http://localhost:3000/");
|
d.add_response_header("location", "http://localhost:3000/");
|
||||||
d.rewrite_norm_location_response_header("https", 443);
|
d.index_request_headers();
|
||||||
auto location = d.get_norm_response_header("location");
|
d.index_response_headers();
|
||||||
|
d.rewrite_location_response_header("https", 443);
|
||||||
|
auto location = d.get_response_header(http2::HD_LOCATION);
|
||||||
CU_ASSERT("https://localhost/" == (*location).value);
|
CU_ASSERT("https://localhost/" == (*location).value);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Downstream d(nullptr, 0, 0);
|
Downstream d(nullptr, 0, 0);
|
||||||
d.set_request_http2_authority("localhost");
|
d.set_request_http2_authority("localhost");
|
||||||
d.add_response_header("location", "http://localhost/");
|
d.add_response_header("location", "http://localhost/");
|
||||||
d.rewrite_norm_location_response_header("https", 443);
|
d.index_response_headers();
|
||||||
auto location = d.get_norm_response_header("location");
|
d.rewrite_location_response_header("https", 443);
|
||||||
|
auto location = d.get_response_header(http2::HD_LOCATION);
|
||||||
CU_ASSERT("https://localhost/" == (*location).value);
|
CU_ASSERT("https://localhost/" == (*location).value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,13 @@
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
void test_downstream_normalize_request_headers(void);
|
void test_downstream_index_request_headers(void);
|
||||||
void test_downstream_normalize_response_headers(void);
|
void test_downstream_index_response_headers(void);
|
||||||
void test_downstream_get_norm_request_header(void);
|
void test_downstream_get_request_header(void);
|
||||||
void test_downstream_get_norm_response_header(void);
|
void test_downstream_get_response_header(void);
|
||||||
void test_downstream_crumble_request_cookie(void);
|
void test_downstream_crumble_request_cookie(void);
|
||||||
void test_downstream_assemble_request_cookie(void);
|
void test_downstream_assemble_request_cookie(void);
|
||||||
void test_downstream_rewrite_norm_location_response_header(void);
|
void test_downstream_rewrite_location_response_header(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,6 @@
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <openssl/err.h>
|
|
||||||
|
|
||||||
#include <event2/bufferevent_ssl.h>
|
|
||||||
|
|
||||||
#include "http-parser/http_parser.h"
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
#include "shrpx_client_handler.h"
|
#include "shrpx_client_handler.h"
|
||||||
|
@ -50,15 +46,12 @@ namespace shrpx {
|
||||||
Http2DownstreamConnection::Http2DownstreamConnection(
|
Http2DownstreamConnection::Http2DownstreamConnection(
|
||||||
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
|
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
|
||||||
: DownstreamConnection(dconn_pool), http2session_(http2session),
|
: DownstreamConnection(dconn_pool), http2session_(http2session),
|
||||||
request_body_buf_(nullptr), sd_(nullptr) {}
|
sd_(nullptr) {}
|
||||||
|
|
||||||
Http2DownstreamConnection::~Http2DownstreamConnection() {
|
Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Deleting";
|
DCLOG(INFO, this) << "Deleting";
|
||||||
}
|
}
|
||||||
if (request_body_buf_) {
|
|
||||||
evbuffer_free(request_body_buf_);
|
|
||||||
}
|
|
||||||
if (downstream_) {
|
if (downstream_) {
|
||||||
downstream_->disable_downstream_rtimer();
|
downstream_->disable_downstream_rtimer();
|
||||||
downstream_->disable_downstream_wtimer();
|
downstream_->disable_downstream_wtimer();
|
||||||
|
@ -73,24 +66,22 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||||
error_code = NGHTTP2_INTERNAL_ERROR;
|
error_code = NGHTTP2_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
|
|
||||||
<< ", stream_id="
|
|
||||||
<< downstream_->get_downstream_stream_id()
|
|
||||||
<< ", error_code=" << error_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (submit_rst_stream(downstream_, error_code) == 0) {
|
|
||||||
http2session_->notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downstream_->get_downstream_stream_id() != -1) {
|
if (downstream_->get_downstream_stream_id() != -1) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
|
||||||
|
<< ", stream_id="
|
||||||
|
<< downstream_->get_downstream_stream_id()
|
||||||
|
<< ", error_code=" << error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit_rst_stream(downstream_, error_code);
|
||||||
|
|
||||||
http2session_->consume(downstream_->get_downstream_stream_id(),
|
http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||||
downstream_->get_response_datalen());
|
downstream_->get_response_datalen());
|
||||||
|
|
||||||
downstream_->reset_response_datalen();
|
downstream_->reset_response_datalen();
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http2session_->remove_downstream_connection(this);
|
http2session_->remove_downstream_connection(this);
|
||||||
|
@ -104,38 +95,17 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2DownstreamConnection::init_request_body_buf() {
|
|
||||||
int rv;
|
|
||||||
if (request_body_buf_) {
|
|
||||||
rv = evbuffer_drain(request_body_buf_,
|
|
||||||
evbuffer_get_length(request_body_buf_));
|
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
request_body_buf_ = evbuffer_new();
|
|
||||||
if (request_body_buf_ == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
|
int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
||||||
}
|
}
|
||||||
if (init_request_body_buf() == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http2session_->add_downstream_connection(this);
|
http2session_->add_downstream_connection(this);
|
||||||
if (http2session_->get_state() == Http2Session::DISCONNECTED) {
|
if (http2session_->get_state() == Http2Session::DISCONNECTED) {
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream_ = downstream;
|
downstream_ = downstream;
|
||||||
|
downstream_->reset_downstream_rtimer();
|
||||||
downstream_->init_downstream_timer();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -145,7 +115,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
|
||||||
DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
|
DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
|
||||||
}
|
}
|
||||||
if (submit_rst_stream(downstream) == 0) {
|
if (submit_rst_stream(downstream) == 0) {
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream_->get_downstream_stream_id() != -1) {
|
if (downstream_->get_downstream_stream_id() != -1) {
|
||||||
|
@ -154,7 +124,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
|
||||||
|
|
||||||
downstream_->reset_response_datalen();
|
downstream_->reset_response_datalen();
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->disable_downstream_rtimer();
|
downstream->disable_downstream_rtimer();
|
||||||
|
@ -201,13 +171,9 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
// on the priority, DATA frame may come first.
|
// on the priority, DATA frame may come first.
|
||||||
return NGHTTP2_ERR_DEFERRED;
|
return NGHTTP2_ERR_DEFERRED;
|
||||||
}
|
}
|
||||||
auto body = dconn->get_request_body_buf();
|
auto input = downstream->get_request_buf();
|
||||||
|
auto nread = input->remove(buf, length);
|
||||||
auto nread = evbuffer_remove(body, buf, length);
|
auto input_empty = input->rleft() == 0;
|
||||||
if (nread == -1) {
|
|
||||||
DCLOG(FATAL, dconn) << "evbuffer_remove() failed";
|
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nread > 0) {
|
if (nread > 0) {
|
||||||
// This is important because it will handle flow control
|
// This is important because it will handle flow control
|
||||||
|
@ -225,7 +191,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_get_length(body) == 0 &&
|
if (input_empty &&
|
||||||
downstream->get_request_state() == Downstream::MSG_COMPLETE &&
|
downstream->get_request_state() == Downstream::MSG_COMPLETE &&
|
||||||
// If connection is upgraded, don't set EOF flag, since HTTP/1
|
// If connection is upgraded, don't set EOF flag, since HTTP/1
|
||||||
// will set MSG_COMPLETE to request state after upgrade response
|
// will set MSG_COMPLETE to request state after upgrade response
|
||||||
|
@ -237,7 +203,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_get_length(body) > 0) {
|
if (!input_empty) {
|
||||||
downstream->reset_downstream_wtimer();
|
downstream->reset_downstream_wtimer();
|
||||||
} else {
|
} else {
|
||||||
downstream->disable_downstream_wtimer();
|
downstream->disable_downstream_wtimer();
|
||||||
|
@ -266,14 +232,12 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
size_t nheader = downstream_->get_request_headers().size();
|
size_t nheader = downstream_->get_request_headers().size();
|
||||||
|
|
||||||
|
Headers cookies;
|
||||||
if (!get_config()->http2_no_cookie_crumbling) {
|
if (!get_config()->http2_no_cookie_crumbling) {
|
||||||
downstream_->crumble_request_cookie();
|
cookies = downstream_->crumble_request_cookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(downstream_->get_request_headers_normalized());
|
|
||||||
|
|
||||||
auto end_headers = std::end(downstream_->get_request_headers());
|
|
||||||
|
|
||||||
// 7 means:
|
// 7 means:
|
||||||
// 1. :method
|
// 1. :method
|
||||||
// 2. :scheme
|
// 2. :scheme
|
||||||
|
@ -283,10 +247,12 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
// 6. x-forwarded-for (optional)
|
// 6. x-forwarded-for (optional)
|
||||||
// 7. x-forwarded-proto (optional)
|
// 7. x-forwarded-proto (optional)
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
nva.reserve(nheader + 7);
|
nva.reserve(nheader + 7 + cookies.size());
|
||||||
|
|
||||||
std::string via_value;
|
std::string via_value;
|
||||||
std::string xff_value;
|
std::string xff_value;
|
||||||
std::string scheme, authority, path, query;
|
std::string scheme, authority, path, query;
|
||||||
|
|
||||||
// To reconstruct HTTP/1 status line and headers, proxy should
|
// To reconstruct HTTP/1 status line and headers, proxy should
|
||||||
// preserve host header field. See draft-09 section 8.1.3.1.
|
// preserve host header field. See draft-09 section 8.1.3.1.
|
||||||
if (downstream_->get_request_method() == "CONNECT") {
|
if (downstream_->get_request_method() == "CONNECT") {
|
||||||
|
@ -306,7 +272,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
if (!downstream_->get_request_http2_authority().empty()) {
|
if (!downstream_->get_request_http2_authority().empty()) {
|
||||||
nva.push_back(http2::make_nv_ls(
|
nva.push_back(http2::make_nv_ls(
|
||||||
":authority", downstream_->get_request_http2_authority()));
|
":authority", downstream_->get_request_http2_authority()));
|
||||||
} else if (downstream_->get_norm_request_header("host") == end_headers) {
|
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "host header field missing";
|
DCLOG(INFO, this) << "host header field missing";
|
||||||
}
|
}
|
||||||
|
@ -363,7 +329,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
authority += util::utos(u.port);
|
authority += util::utos(u.port);
|
||||||
}
|
}
|
||||||
nva.push_back(http2::make_nv_ls(":authority", authority));
|
nva.push_back(http2::make_nv_ls(":authority", authority));
|
||||||
} else if (downstream_->get_norm_request_header("host") == end_headers) {
|
} else if (!downstream_->get_request_header(http2::HD_HOST)) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "host header field missing";
|
DCLOG(INFO, this) << "host header field missing";
|
||||||
}
|
}
|
||||||
|
@ -374,27 +340,30 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
nva.push_back(
|
nva.push_back(
|
||||||
http2::make_nv_ls(":method", downstream_->get_request_method()));
|
http2::make_nv_ls(":method", downstream_->get_request_method()));
|
||||||
|
|
||||||
http2::copy_norm_headers_to_nva(nva, downstream_->get_request_headers());
|
http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
|
||||||
|
|
||||||
bool chunked_encoding = false;
|
bool chunked_encoding = false;
|
||||||
auto transfer_encoding =
|
auto transfer_encoding =
|
||||||
downstream_->get_norm_request_header("transfer-encoding");
|
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
|
||||||
if (transfer_encoding != end_headers &&
|
if (transfer_encoding &&
|
||||||
util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
|
util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
|
||||||
chunked_encoding = true;
|
chunked_encoding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
|
for (auto &nv : cookies) {
|
||||||
|
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
|
||||||
if (get_config()->add_x_forwarded_for) {
|
if (get_config()->add_x_forwarded_for) {
|
||||||
if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) {
|
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||||
xff_value = (*xff).value;
|
xff_value = (*xff).value;
|
||||||
xff_value += ", ";
|
xff_value += ", ";
|
||||||
}
|
}
|
||||||
xff_value +=
|
xff_value +=
|
||||||
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
|
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
|
||||||
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
|
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
|
||||||
} else if (xff != end_headers &&
|
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||||
!get_config()->strip_incoming_x_forwarded_for) {
|
|
||||||
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
|
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,13 +381,13 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto via = downstream_->get_norm_request_header("via");
|
auto via = downstream_->get_request_header(http2::HD_VIA);
|
||||||
if (get_config()->no_via) {
|
if (get_config()->no_via) {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
via_value = (*via).value;
|
via_value = (*via).value;
|
||||||
via_value += ", ";
|
via_value += ", ";
|
||||||
}
|
}
|
||||||
|
@ -440,7 +409,8 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto content_length =
|
auto content_length =
|
||||||
downstream_->get_norm_request_header("content-length") != end_headers;
|
downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
|
||||||
|
// TODO check content-length: 0 case
|
||||||
|
|
||||||
if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
|
if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
|
||||||
content_length || downstream_->get_request_http2_expect_body()) {
|
content_length || downstream_->get_request_http2_expect_body()) {
|
||||||
|
@ -461,17 +431,15 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
downstream_->reset_downstream_wtimer();
|
downstream_->reset_downstream_wtimer();
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
||||||
size_t datalen) {
|
size_t datalen) {
|
||||||
int rv = evbuffer_add(request_body_buf_, data, datalen);
|
int rv;
|
||||||
if (rv != 0) {
|
auto output = downstream_->get_request_buf();
|
||||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
output->append(data, datalen);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (downstream_->get_downstream_stream_id() != -1) {
|
if (downstream_->get_downstream_stream_id() != -1) {
|
||||||
rv = http2session_->resume_data(this);
|
rv = http2session_->resume_data(this);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
@ -480,7 +448,7 @@ int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
||||||
|
|
||||||
downstream_->ensure_downstream_wtimer();
|
downstream_->ensure_downstream_wtimer();
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -495,7 +463,7 @@ int Http2DownstreamConnection::end_upload_data() {
|
||||||
|
|
||||||
downstream_->ensure_downstream_wtimer();
|
downstream_->ensure_downstream_wtimer();
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -525,7 +493,7 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
|
||||||
|
|
||||||
downstream_->dec_response_datalen(consumed);
|
downstream_->dec_response_datalen(consumed);
|
||||||
|
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -535,10 +503,6 @@ int Http2DownstreamConnection::on_read() { return 0; }
|
||||||
|
|
||||||
int Http2DownstreamConnection::on_write() { return 0; }
|
int Http2DownstreamConnection::on_write() { return 0; }
|
||||||
|
|
||||||
evbuffer *Http2DownstreamConnection::get_request_body_buf() const {
|
|
||||||
return request_body_buf_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Http2DownstreamConnection::attach_stream_data(StreamData *sd) {
|
void Http2DownstreamConnection::attach_stream_data(StreamData *sd) {
|
||||||
// It is possible sd->dconn is not NULL. sd is detached when
|
// It is possible sd->dconn is not NULL. sd is detached when
|
||||||
// on_stream_close_callback. Before that, after MSG_COMPLETE is set
|
// on_stream_close_callback. Before that, after MSG_COMPLETE is set
|
||||||
|
@ -556,18 +520,8 @@ StreamData *Http2DownstreamConnection::detach_stream_data() {
|
||||||
sd_ = nullptr;
|
sd_ = nullptr;
|
||||||
sd->dconn = nullptr;
|
sd->dconn = nullptr;
|
||||||
return sd;
|
return sd;
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Http2DownstreamConnection::get_output_buffer_full() {
|
|
||||||
if (request_body_buf_) {
|
|
||||||
return evbuffer_get_length(request_body_buf_) >=
|
|
||||||
Http2Session::OUTBUF_MAX_THRES;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2DownstreamConnection::on_priority_change(int32_t pri) {
|
int Http2DownstreamConnection::on_priority_change(int32_t pri) {
|
||||||
|
@ -584,7 +538,7 @@ int Http2DownstreamConnection::on_priority_change(int32_t pri) {
|
||||||
DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
|
DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
http2session_->notify();
|
http2session_->signal_write();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,6 @@ public:
|
||||||
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
||||||
virtual void force_resume_read() {}
|
virtual void force_resume_read() {}
|
||||||
|
|
||||||
virtual bool get_output_buffer_full();
|
|
||||||
|
|
||||||
virtual int on_read();
|
virtual int on_read();
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual int on_timeout();
|
virtual int on_timeout();
|
||||||
|
@ -66,9 +64,6 @@ public:
|
||||||
|
|
||||||
int send();
|
int send();
|
||||||
|
|
||||||
int init_request_body_buf();
|
|
||||||
evbuffer *get_request_body_buf() const;
|
|
||||||
|
|
||||||
void attach_stream_data(StreamData *sd);
|
void attach_stream_data(StreamData *sd);
|
||||||
StreamData *detach_stream_data();
|
StreamData *detach_stream_data();
|
||||||
|
|
||||||
|
@ -77,7 +72,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Http2Session *http2session_;
|
Http2Session *http2session_;
|
||||||
evbuffer *request_body_buf_;
|
|
||||||
StreamData *sd_;
|
StreamData *sd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,13 +32,16 @@
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#include "http-parser/http_parser.h"
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
class Http2DownstreamConnection;
|
class Http2DownstreamConnection;
|
||||||
|
@ -49,10 +52,10 @@ struct StreamData {
|
||||||
|
|
||||||
class Http2Session {
|
class Http2Session {
|
||||||
public:
|
public:
|
||||||
Http2Session(event_base *evbase, SSL_CTX *ssl_ctx);
|
typedef RingBuf<65536> Buf;
|
||||||
~Http2Session();
|
|
||||||
|
|
||||||
int init_notification();
|
Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx);
|
||||||
|
~Http2Session();
|
||||||
|
|
||||||
int check_cert();
|
int check_cert();
|
||||||
|
|
||||||
|
@ -84,32 +87,45 @@ public:
|
||||||
|
|
||||||
int on_connect();
|
int on_connect();
|
||||||
|
|
||||||
|
int do_read();
|
||||||
|
int do_write();
|
||||||
|
|
||||||
int on_read();
|
int on_read();
|
||||||
int on_write();
|
int on_write();
|
||||||
int send();
|
|
||||||
|
|
||||||
int on_read_proxy();
|
int connected();
|
||||||
|
int read_clear();
|
||||||
|
int write_clear();
|
||||||
|
int tls_handshake();
|
||||||
|
int read_tls();
|
||||||
|
int write_tls();
|
||||||
|
|
||||||
void clear_notify();
|
int downstream_read_proxy();
|
||||||
void notify();
|
int downstream_connect_proxy();
|
||||||
|
|
||||||
bufferevent *get_bev() const;
|
int downstream_read();
|
||||||
void unwrap_free_bev();
|
int downstream_write();
|
||||||
|
|
||||||
|
int noop();
|
||||||
|
|
||||||
|
void signal_write();
|
||||||
|
void clear_write_request();
|
||||||
|
bool write_requested() const;
|
||||||
|
|
||||||
|
struct ev_loop *get_loop() const;
|
||||||
|
|
||||||
|
ev_io *get_wev();
|
||||||
|
|
||||||
int get_state() const;
|
int get_state() const;
|
||||||
void set_state(int state);
|
void set_state(int state);
|
||||||
|
|
||||||
int start_settings_timer();
|
void start_settings_timer();
|
||||||
void stop_settings_timer();
|
void stop_settings_timer();
|
||||||
|
|
||||||
size_t get_outbuf_length() const;
|
|
||||||
|
|
||||||
SSL *get_ssl() const;
|
SSL *get_ssl() const;
|
||||||
|
|
||||||
int consume(int32_t stream_id, size_t len);
|
int consume(int32_t stream_id, size_t len);
|
||||||
|
|
||||||
void reset_timeouts();
|
|
||||||
|
|
||||||
// Returns true if request can be issued on downstream connection.
|
// Returns true if request can be issued on downstream connection.
|
||||||
bool can_push_request() const;
|
bool can_push_request() const;
|
||||||
// Initiates the connection checking if downstream connection has
|
// Initiates the connection checking if downstream connection has
|
||||||
|
@ -117,13 +133,15 @@ public:
|
||||||
void start_checking_connection();
|
void start_checking_connection();
|
||||||
// Resets connection check timer. After timeout, we require
|
// Resets connection check timer. After timeout, we require
|
||||||
// connection checking.
|
// connection checking.
|
||||||
int reset_connection_check_timer();
|
void reset_connection_check_timer();
|
||||||
// Signals that connection is alive. Internally
|
// Signals that connection is alive. Internally
|
||||||
// reset_connection_check_timer() is called.
|
// reset_connection_check_timer() is called.
|
||||||
int connection_alive();
|
void connection_alive();
|
||||||
// Change connection check state.
|
// Change connection check state.
|
||||||
void set_connection_check_state(int state);
|
void set_connection_check_state(int state);
|
||||||
|
|
||||||
|
bool should_hard_fail() const;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
// Disconnected
|
// Disconnected
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
|
@ -136,7 +154,9 @@ public:
|
||||||
// Connecting to downstream and/or performing SSL/TLS handshake
|
// Connecting to downstream and/or performing SSL/TLS handshake
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
// Connected to downstream
|
// Connected to downstream
|
||||||
CONNECTED
|
CONNECTED,
|
||||||
|
// Connection is started to fail
|
||||||
|
CONNECT_FAILING,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const size_t OUTBUF_MAX_THRES = 64 * 1024;
|
static const size_t OUTBUF_MAX_THRES = 64 * 1024;
|
||||||
|
@ -151,20 +171,26 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
ev_io wev_;
|
||||||
|
ev_io rev_;
|
||||||
|
ev_timer wt_;
|
||||||
|
ev_timer rt_;
|
||||||
|
ev_timer settings_timer_;
|
||||||
|
ev_timer connchk_timer_;
|
||||||
|
ev_prepare wrsched_prep_;
|
||||||
std::set<Http2DownstreamConnection *> dconns_;
|
std::set<Http2DownstreamConnection *> dconns_;
|
||||||
std::set<StreamData *> streams_;
|
std::set<StreamData *> streams_;
|
||||||
|
std::function<int(Http2Session &)> read_, write_;
|
||||||
|
std::function<int(Http2Session &)> on_read_, on_write_;
|
||||||
// Used to parse the response from HTTP proxy
|
// Used to parse the response from HTTP proxy
|
||||||
std::unique_ptr<http_parser> proxy_htp_;
|
std::unique_ptr<http_parser> proxy_htp_;
|
||||||
event_base *evbase_;
|
struct ev_loop *loop_;
|
||||||
// NULL if no TLS is configured
|
// NULL if no TLS is configured
|
||||||
SSL_CTX *ssl_ctx_;
|
SSL_CTX *ssl_ctx_;
|
||||||
SSL *ssl_;
|
SSL *ssl_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
bufferevent *bev_;
|
const uint8_t *data_pending_;
|
||||||
bufferevent *wrbev_;
|
size_t data_pendinglen_;
|
||||||
bufferevent *rdbev_;
|
|
||||||
event *settings_timerev_;
|
|
||||||
event *connection_check_timerev_;
|
|
||||||
// fd_ is used for proxy connection and no TLS connection. For
|
// fd_ is used for proxy connection and no TLS connection. For
|
||||||
// direct or TLS connection, it may be -1 even after connection is
|
// direct or TLS connection, it may be -1 even after connection is
|
||||||
// established. Use bufferevent_getfd(bev_) to get file descriptor
|
// established. Use bufferevent_getfd(bev_) to get file descriptor
|
||||||
|
@ -172,8 +198,10 @@ private:
|
||||||
int fd_;
|
int fd_;
|
||||||
int state_;
|
int state_;
|
||||||
int connection_check_state_;
|
int connection_check_state_;
|
||||||
bool notified_;
|
|
||||||
bool flow_control_;
|
bool flow_control_;
|
||||||
|
bool write_requested_;
|
||||||
|
Buf wb_;
|
||||||
|
Buf rb_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -45,10 +45,6 @@ using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
|
||||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
uint32_t error_code, void *user_data) {
|
uint32_t error_code, void *user_data) {
|
||||||
|
@ -127,9 +123,7 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
||||||
auto downstream = http->pop_downstream();
|
auto downstream = http->pop_downstream();
|
||||||
downstream->reset_upstream(this);
|
downstream->reset_upstream(this);
|
||||||
downstream->set_stream_id(1);
|
downstream->set_stream_id(1);
|
||||||
downstream->init_upstream_timer();
|
|
||||||
downstream->reset_upstream_rtimer();
|
downstream->reset_upstream_rtimer();
|
||||||
downstream->init_response_body_buf();
|
|
||||||
downstream->set_stream_id(1);
|
downstream->set_stream_id(1);
|
||||||
downstream->set_priority(0);
|
downstream->set_priority(0);
|
||||||
|
|
||||||
|
@ -142,46 +136,12 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
void Http2Upstream::start_settings_timer() {
|
||||||
void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) {
|
ev_timer_start(handler_->get_loop(), &settings_timer_);
|
||||||
auto upstream = static_cast<Http2Upstream *>(arg);
|
|
||||||
ULOG(INFO, upstream) << "SETTINGS timeout";
|
|
||||||
if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (upstream->send() != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int Http2Upstream::start_settings_timer() {
|
|
||||||
int rv;
|
|
||||||
// We submit SETTINGS only once
|
|
||||||
if (settings_timerev_) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
settings_timerev_ =
|
|
||||||
evtimer_new(handler_->get_evbase(), settings_timeout_cb, this);
|
|
||||||
if (settings_timerev_ == nullptr) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// SETTINGS ACK timeout is 10 seconds for now
|
|
||||||
timeval settings_timeout = {10, 0};
|
|
||||||
rv = evtimer_add(settings_timerev_, &settings_timeout);
|
|
||||||
if (rv == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Upstream::stop_settings_timer() {
|
void Http2Upstream::stop_settings_timer() {
|
||||||
if (settings_timerev_ == nullptr) {
|
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
||||||
return;
|
|
||||||
}
|
|
||||||
event_free(settings_timerev_);
|
|
||||||
settings_timerev_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -224,17 +184,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (namelen > 0 && name[0] == ':') {
|
auto token = http2::lookup_token(name, namelen);
|
||||||
if (!downstream->request_pseudo_header_allowed() ||
|
|
||||||
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
|
||||||
|
|
||||||
|
if (name[0] == ':') {
|
||||||
|
if (!downstream->request_pseudo_header_allowed(token)) {
|
||||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->split_add_request_header(name, namelen, value, valuelen,
|
if (!http2::http2_header_allowed(token)) {
|
||||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->add_request_header(name, namelen, value, valuelen,
|
||||||
|
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -256,9 +221,7 @@ int on_begin_headers_callback(nghttp2_session *session,
|
||||||
auto downstream =
|
auto downstream =
|
||||||
util::make_unique<Downstream>(upstream, frame->hd.stream_id, 0);
|
util::make_unique<Downstream>(upstream, frame->hd.stream_id, 0);
|
||||||
|
|
||||||
downstream->init_upstream_timer();
|
|
||||||
downstream->reset_upstream_rtimer();
|
downstream->reset_upstream_rtimer();
|
||||||
downstream->init_response_body_buf();
|
|
||||||
|
|
||||||
// Although, we deprecated minor version from HTTP/2, we supply
|
// Although, we deprecated minor version from HTTP/2, we supply
|
||||||
// minor version 0 to use via header field in a conventional way.
|
// minor version 0 to use via header field in a conventional way.
|
||||||
|
@ -278,7 +241,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->normalize_request_headers();
|
|
||||||
auto &nva = downstream->get_request_headers();
|
auto &nva = downstream->get_request_headers();
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -294,17 +256,11 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
|
||||||
http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
|
http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!http2::check_http2_request_headers(nva)) {
|
auto host = downstream->get_request_header(http2::HD_HOST);
|
||||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
|
||||||
|
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||||
return 0;
|
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||||
}
|
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||||
|
|
||||||
auto host = http2::get_unique_header(nva, "host");
|
|
||||||
auto authority = http2::get_unique_header(nva, ":authority");
|
|
||||||
auto path = http2::get_unique_header(nva, ":path");
|
|
||||||
auto method = http2::get_unique_header(nva, ":method");
|
|
||||||
auto scheme = http2::get_unique_header(nva, ":scheme");
|
|
||||||
|
|
||||||
bool is_connect = method && "CONNECT" == method->value;
|
bool is_connect = method && "CONNECT" == method->value;
|
||||||
bool having_host = http2::non_empty_value(host);
|
bool having_host = http2::non_empty_value(host);
|
||||||
|
@ -540,10 +496,8 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
|
|
||||||
switch (frame->hd.type) {
|
switch (frame->hd.type) {
|
||||||
case NGHTTP2_SETTINGS:
|
case NGHTTP2_SETTINGS:
|
||||||
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
|
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
||||||
upstream->start_settings_timer() != 0) {
|
upstream->start_settings_timer();
|
||||||
|
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NGHTTP2_GOAWAY:
|
case NGHTTP2_GOAWAY:
|
||||||
|
@ -598,19 +552,28 @@ uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void write_notify_cb(evutil_socket_t fd, short what, void *arg) {
|
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
auto upstream = static_cast<Http2Upstream *>(arg);
|
auto upstream = static_cast<Http2Upstream *>(w->data);
|
||||||
upstream->perform_send();
|
auto handler = upstream->get_client_handler();
|
||||||
|
ULOG(INFO, upstream) << "SETTINGS timeout";
|
||||||
|
if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
|
||||||
|
delete handler;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler->signal_write();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Http2Upstream::Http2Upstream(ClientHandler *handler)
|
Http2Upstream::Http2Upstream(ClientHandler *handler)
|
||||||
: downstream_queue_(get_config()->http2_proxy
|
: downstream_queue_(
|
||||||
? get_config()->downstream_connections_per_host
|
get_config()->http2_proxy
|
||||||
: 0),
|
? get_config()->downstream_connections_per_host
|
||||||
handler_(handler), session_(nullptr), settings_timerev_(nullptr),
|
: get_config()->downstream_proto == PROTO_HTTP
|
||||||
write_notifyev_(nullptr), deferred_(false) {
|
? get_config()->downstream_connections_per_frontend
|
||||||
reset_timeouts();
|
: 0,
|
||||||
|
!get_config()->http2_proxy),
|
||||||
|
handler_(handler), session_(nullptr), data_pending_(nullptr),
|
||||||
|
data_pendinglen_(0), deferred_(false) {
|
||||||
|
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
|
@ -699,74 +662,76 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write_notifyev_ = evtimer_new(handler_->get_evbase(), write_notify_cb, this);
|
// We wait for SETTINGS ACK at least 10 seconds.
|
||||||
|
ev_timer_init(&settings_timer_, settings_timeout_cb, 10., 0.);
|
||||||
|
|
||||||
|
settings_timer_.data = this;
|
||||||
|
|
||||||
|
handler_->reset_upstream_read_timeout(
|
||||||
|
get_config()->http2_upstream_read_timeout);
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
Http2Upstream::~Http2Upstream() {
|
Http2Upstream::~Http2Upstream() {
|
||||||
nghttp2_session_del(session_);
|
nghttp2_session_del(session_);
|
||||||
if (settings_timerev_) {
|
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
||||||
event_free(settings_timerev_);
|
|
||||||
}
|
|
||||||
if (write_notifyev_) {
|
|
||||||
event_free(write_notifyev_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Upstream::on_read() {
|
int Http2Upstream::on_read() {
|
||||||
ssize_t rv = 0;
|
ssize_t rv = 0;
|
||||||
auto bev = handler_->get_bev();
|
auto rb = handler_->get_rb();
|
||||||
auto input = bufferevent_get_input(bev);
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
const void *data;
|
||||||
|
size_t nread;
|
||||||
if (inputlen == 0) {
|
std::tie(data, nread) = rb->get();
|
||||||
assert(evbuffer_get_length(input) == 0);
|
if (nread == 0) {
|
||||||
|
break;
|
||||||
return send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mem = evbuffer_pullup(input, inputlen);
|
rv = nghttp2_session_mem_recv(
|
||||||
|
session_, reinterpret_cast<const uint8_t *>(data), nread);
|
||||||
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
|
|
||||||
if (rv < 0) {
|
if (rv < 0) {
|
||||||
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
||||||
<< nghttp2_strerror(rv);
|
<< nghttp2_strerror(rv);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_drain(input, rv) != 0) {
|
rb->drain(nread);
|
||||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int Http2Upstream::on_write() { return send(); }
|
auto wb = handler_->get_wb();
|
||||||
|
if (nghttp2_session_want_read(session_) == 0 &&
|
||||||
int Http2Upstream::send() {
|
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
|
||||||
if (write_notifyev_ == nullptr) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
event_active(write_notifyev_, 0, 0);
|
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// After this function call, downstream may be deleted.
|
// After this function call, downstream may be deleted.
|
||||||
int Http2Upstream::perform_send() {
|
int Http2Upstream::on_write() {
|
||||||
int rv;
|
auto wb = handler_->get_wb();
|
||||||
uint8_t buf[16384];
|
|
||||||
auto bev = handler_->get_bev();
|
|
||||||
auto output = bufferevent_get_output(bev);
|
|
||||||
|
|
||||||
sendbuf.reset(output, buf, sizeof(buf), handler_->get_write_limit());
|
if (data_pending_) {
|
||||||
for (;;) {
|
auto n = std::min(wb->wleft(), data_pendinglen_);
|
||||||
// Check buffer length and break if it is large enough.
|
wb->write(data_pending_, n);
|
||||||
if (handler_->get_outbuf_length() > 0) {
|
if (n < data_pendinglen_) {
|
||||||
break;
|
data_pending_ += n;
|
||||||
|
data_pendinglen_ -= n;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data_pending_ = nullptr;
|
||||||
|
data_pendinglen_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
const uint8_t *data;
|
const uint8_t *data;
|
||||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||||
|
|
||||||
|
@ -778,55 +743,38 @@ int Http2Upstream::perform_send() {
|
||||||
if (datalen == 0) {
|
if (datalen == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rv = sendbuf.add(data, datalen);
|
auto n = wb->write(data, datalen);
|
||||||
if (rv != 0) {
|
if (n < static_cast<decltype(n)>(datalen)) {
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
data_pending_ = data + n;
|
||||||
return -1;
|
data_pendinglen_ = datalen - n;
|
||||||
}
|
return 0;
|
||||||
if (deferred_) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deferred_ = false;
|
|
||||||
|
|
||||||
rv = sendbuf.flush();
|
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler_->update_warmup_writelen(sendbuf.get_writelen());
|
|
||||||
|
|
||||||
if (nghttp2_session_want_read(session_) == 0 &&
|
if (nghttp2_session_want_read(session_) == 0 &&
|
||||||
nghttp2_session_want_write(session_) == 0 &&
|
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
|
||||||
handler_->get_outbuf_length() == 0) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Upstream::on_event() { return 0; }
|
|
||||||
|
|
||||||
ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
|
ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
|
||||||
|
|
||||||
namespace {
|
int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
void downstream_readcb(bufferevent *bev, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
|
||||||
|
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
// If upstream HTTP2 stream was closed, we just close downstream,
|
// If upstream HTTP2 stream was closed, we just close downstream,
|
||||||
// because there is no consumer now. Downstream connection is also
|
// because there is no consumer now. Downstream connection is also
|
||||||
// closed in this case.
|
// closed in this case.
|
||||||
upstream->remove_downstream(downstream);
|
remove_downstream(downstream);
|
||||||
// downstream was deleted
|
// downstream was deleted
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
||||||
|
@ -834,186 +782,150 @@ void downstream_readcb(bufferevent *bev, void *ptr) {
|
||||||
// RST_STREAM to the upstream and delete downstream connection
|
// RST_STREAM to the upstream and delete downstream connection
|
||||||
// here. Deleting downstream will be taken place at
|
// here. Deleting downstream will be taken place at
|
||||||
// on_stream_close_callback.
|
// on_stream_close_callback.
|
||||||
upstream->rst_stream(downstream,
|
rst_stream(downstream,
|
||||||
infer_upstream_rst_stream_error_code(
|
infer_upstream_rst_stream_error_code(
|
||||||
downstream->get_response_rst_stream_error_code()));
|
downstream->get_response_rst_stream_error_code()));
|
||||||
downstream->pop_downstream_connection();
|
downstream->pop_downstream_connection();
|
||||||
// dconn was deleted
|
// dconn was deleted
|
||||||
dconn = nullptr;
|
dconn = nullptr;
|
||||||
} else {
|
} else {
|
||||||
auto rv = downstream->on_read();
|
auto rv = downstream->on_read();
|
||||||
|
if (rv == DownstreamConnection::ERR_EOF) {
|
||||||
|
return downstream_eof(dconn);
|
||||||
|
}
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (rv != DownstreamConnection::ERR_NET) {
|
||||||
DCLOG(INFO, dconn) << "HTTP parser failure";
|
if (LOG_ENABLED(INFO)) {
|
||||||
}
|
DCLOG(INFO, dconn) << "HTTP parser failure";
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
|
||||||
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
||||||
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
|
||||||
// If response was completed, then don't issue RST_STREAM
|
|
||||||
if (upstream->error_reply(downstream, 502) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
// Clearly, we have to close downstream connection on http parser
|
|
||||||
// failure.
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
// dconn was deleted
|
|
||||||
dconn = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (upstream->send() != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
handler_->signal_write();
|
||||||
return;
|
|
||||||
}
|
|
||||||
// At this point, downstream may be deleted.
|
// At this point, downstream may be deleted.
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
return 0;
|
||||||
void downstream_writecb(bufferevent *bev, void *ptr) {
|
}
|
||||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
|
||||||
return;
|
int Http2Upstream::downstream_write(DownstreamConnection *dconn) {
|
||||||
|
int rv;
|
||||||
|
rv = dconn->on_write();
|
||||||
|
if (rv == DownstreamConnection::ERR_NET) {
|
||||||
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
}
|
}
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
if (rv != 0) {
|
||||||
dconn->on_write();
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
|
||||||
void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Connection established. stream_id="
|
|
||||||
<< downstream->get_stream_id();
|
|
||||||
}
|
|
||||||
auto fd = bufferevent_getfd(bev);
|
|
||||||
int val = 1;
|
|
||||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
|
|
||||||
sizeof(val)) == -1) {
|
|
||||||
DCLOG(WARN, dconn) << "Setting option TCP_NODELAY failed: errno="
|
|
||||||
<< errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
|
||||||
|
}
|
||||||
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
|
// If stream was closed already, we don't need to send reply at
|
||||||
|
// the first place. We can delete downstream.
|
||||||
|
remove_downstream(downstream);
|
||||||
|
// downstream was deleted
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events & BEV_EVENT_EOF) {
|
// Delete downstream connection. If we don't delete it here, it will
|
||||||
|
// be pooled in on_stream_close_callback.
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
// dconn was deleted
|
||||||
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
|
ULOG(INFO, this) << "Downstream body was ended by EOF";
|
||||||
}
|
}
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
// If stream was closed already, we don't need to send reply at
|
|
||||||
// the first place. We can delete downstream.
|
|
||||||
upstream->remove_downstream(downstream);
|
|
||||||
// downstream was deleted
|
|
||||||
|
|
||||||
return;
|
// 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.
|
||||||
|
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 (error_reply(downstream, 502) != 0) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
}
|
||||||
|
handler_->signal_write();
|
||||||
|
// At this point, downstream may be deleted.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete downstream connection. If we don't delete it here, it
|
int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||||
// will be pooled in on_stream_close_callback.
|
auto downstream = dconn->get_downstream();
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
// dconn was deleted
|
if (LOG_ENABLED(INFO)) {
|
||||||
dconn = nullptr;
|
if (events & Downstream::EVENT_ERROR) {
|
||||||
// downstream wil be deleted in on_stream_close_callback.
|
DCLOG(INFO, dconn) << "Downstream network/general error";
|
||||||
|
} else {
|
||||||
|
DCLOG(INFO, dconn) << "Timeout";
|
||||||
|
}
|
||||||
|
if (downstream->get_upgraded()) {
|
||||||
|
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
|
remove_downstream(downstream);
|
||||||
|
// downstream was deleted
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete downstream connection. If we don't delete it here, it will
|
||||||
|
// be pooled in on_stream_close_callback.
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
// dconn was deleted
|
||||||
|
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()) {
|
||||||
|
rst_stream(downstream, NGHTTP2_NO_ERROR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||||
// Server may indicate the end of the request by EOF
|
if (downstream->get_upgraded()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
on_downstream_body_complete(downstream);
|
||||||
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);
|
|
||||||
}
|
|
||||||
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: "
|
|
||||||
<< evutil_socket_error_to_string(
|
|
||||||
EVUTIL_SOCKET_ERROR());
|
|
||||||
} else {
|
} else {
|
||||||
DCLOG(INFO, dconn) << "Timeout";
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||||
}
|
|
||||||
if (downstream->get_upgraded()) {
|
|
||||||
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
|
||||||
upstream->remove_downstream(downstream);
|
|
||||||
// downstream was deleted
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete downstream connection. If we don't delete it here, it
|
|
||||||
// will be pooled in on_stream_close_callback.
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
// dconn was deleted
|
|
||||||
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_NO_ERROR);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
unsigned int status;
|
||||||
if (downstream->get_upgraded()) {
|
if (events & Downstream::EVENT_TIMEOUT) {
|
||||||
upstream->on_downstream_body_complete(downstream);
|
status = 504;
|
||||||
} else {
|
|
||||||
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
unsigned int status;
|
status = 502;
|
||||||
if (events & BEV_EVENT_TIMEOUT) {
|
}
|
||||||
status = 504;
|
if (error_reply(downstream, status) != 0) {
|
||||||
} else {
|
return -1;
|
||||||
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) {
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// At this point, downstream may be deleted.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
handler_->signal_write();
|
||||||
|
// At this point, downstream may be deleted.
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
|
int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -1048,7 +960,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
||||||
void *user_data) {
|
void *user_data) {
|
||||||
auto downstream = static_cast<Downstream *>(source->ptr);
|
auto downstream = static_cast<Downstream *>(source->ptr);
|
||||||
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
||||||
auto body = downstream->get_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
auto handler = upstream->get_client_handler();
|
auto handler = upstream->get_client_handler();
|
||||||
assert(body);
|
assert(body);
|
||||||
|
|
||||||
|
@ -1062,13 +974,8 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
||||||
length = std::min(length, static_cast<size_t>(limit - 9));
|
length = std::min(length, static_cast<size_t>(limit - 9));
|
||||||
}
|
}
|
||||||
|
|
||||||
int nread = evbuffer_remove(body, buf, length);
|
auto nread = body->remove(buf, length);
|
||||||
if (nread == -1) {
|
auto body_empty = body->rleft() == 0;
|
||||||
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
|
|
||||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto body_empty = evbuffer_get_length(body) == 0;
|
|
||||||
|
|
||||||
if (body_empty &&
|
if (body_empty &&
|
||||||
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
@ -1106,6 +1013,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
||||||
// a chance to read another incoming data from backend to this
|
// a chance to read another incoming data from backend to this
|
||||||
// stream.
|
// stream.
|
||||||
upstream->set_deferred(true);
|
upstream->set_deferred(true);
|
||||||
|
|
||||||
return NGHTTP2_ERR_DEFERRED;
|
return NGHTTP2_ERR_DEFERRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,13 +1030,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
|
||||||
int rv;
|
int rv;
|
||||||
auto html = http::create_error_html(status_code);
|
auto html = http::create_error_html(status_code);
|
||||||
downstream->set_response_http_status(status_code);
|
downstream->set_response_http_status(status_code);
|
||||||
downstream->init_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
auto body = downstream->get_response_body_buf();
|
body->append(html.c_str(), html.size());
|
||||||
rv = evbuffer_add(body, html.c_str(), html.size());
|
|
||||||
if (rv == -1) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
|
||||||
nghttp2_data_provider data_prd;
|
nghttp2_data_provider data_prd;
|
||||||
|
@ -1154,18 +1057,6 @@ int Http2Upstream::error_reply(Downstream *downstream,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_data_cb Http2Upstream::get_downstream_readcb() {
|
|
||||||
return downstream_readcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent_data_cb Http2Upstream::get_downstream_writecb() {
|
|
||||||
return downstream_writecb;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent_event_cb Http2Upstream::get_downstream_eventcb() {
|
|
||||||
return downstream_eventcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) {
|
Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) {
|
||||||
downstream_queue_.add_pending(std::move(downstream));
|
downstream_queue_.add_pending(std::move(downstream));
|
||||||
|
@ -1182,6 +1073,9 @@ void Http2Upstream::remove_downstream(Downstream *downstream) {
|
||||||
if (next_downstream) {
|
if (next_downstream) {
|
||||||
initiate_downstream(std::move(next_downstream));
|
initiate_downstream(std::move(next_downstream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mcpool_.shrink((downstream_queue_.get_active_downstreams().size() + 1) *
|
||||||
|
65536);
|
||||||
}
|
}
|
||||||
|
|
||||||
Downstream *Http2Upstream::find_downstream(int32_t stream_id) {
|
Downstream *Http2Upstream::find_downstream(int32_t stream_id) {
|
||||||
|
@ -1203,14 +1097,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->normalize_response_headers();
|
|
||||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||||
!get_config()->no_location_rewrite) {
|
!get_config()->no_location_rewrite) {
|
||||||
downstream->rewrite_norm_location_response_header(
|
downstream->rewrite_location_response_header(
|
||||||
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto end_headers = std::end(downstream->get_response_headers());
|
|
||||||
size_t nheader = downstream->get_response_headers().size();
|
size_t nheader = downstream->get_response_headers().size();
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
// 3 means :status and possible server and via header field.
|
// 3 means :status and possible server and via header field.
|
||||||
|
@ -1219,7 +1111,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
auto response_status = util::utos(downstream->get_response_http_status());
|
auto response_status = util::utos(downstream->get_response_http_status());
|
||||||
nva.push_back(http2::make_nv_ls(":status", response_status));
|
nva.push_back(http2::make_nv_ls(":status", response_status));
|
||||||
|
|
||||||
http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers());
|
http2::copy_headers_to_nva(nva, downstream->get_response_headers());
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -1243,19 +1135,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||||
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
|
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
|
||||||
} else {
|
} else {
|
||||||
auto server = downstream->get_norm_response_header("server");
|
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||||
if (server != end_headers) {
|
if (server) {
|
||||||
nva.push_back(http2::make_nv_ls("server", (*server).value));
|
nva.push_back(http2::make_nv_ls("server", (*server).value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto via = downstream->get_norm_response_header("via");
|
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||||
if (get_config()->no_via) {
|
if (get_config()->no_via) {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
via_value = (*via).value;
|
via_value = (*via).value;
|
||||||
via_value += ", ";
|
via_value += ", ";
|
||||||
}
|
}
|
||||||
|
@ -1304,12 +1196,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
int Http2Upstream::on_downstream_body(Downstream *downstream,
|
int Http2Upstream::on_downstream_body(Downstream *downstream,
|
||||||
const uint8_t *data, size_t len,
|
const uint8_t *data, size_t len,
|
||||||
bool flush) {
|
bool flush) {
|
||||||
auto body = downstream->get_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
int rv = evbuffer_add(body, data, len);
|
body->append(data, len);
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flush) {
|
if (flush) {
|
||||||
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
||||||
|
@ -1317,16 +1205,6 @@ int Http2Upstream::on_downstream_body(Downstream *downstream,
|
||||||
downstream->ensure_upstream_wtimer();
|
downstream->ensure_upstream_wtimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_get_length(body) >= INBUF_MAX_THRES) {
|
|
||||||
if (!flush) {
|
|
||||||
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
|
||||||
|
|
||||||
downstream->ensure_upstream_wtimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->pause_read(SHRPX_NO_BUFFER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1358,7 +1236,8 @@ int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
downstream->dec_request_datalen(consumed);
|
downstream->dec_request_datalen(consumed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return send();
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
||||||
|
@ -1371,7 +1250,8 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return send();
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Http2Upstream::consume(int32_t stream_id, size_t len) {
|
int Http2Upstream::consume(int32_t stream_id, size_t len) {
|
||||||
|
@ -1414,11 +1294,6 @@ int Http2Upstream::on_timeout(Downstream *downstream) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Upstream::reset_timeouts() {
|
|
||||||
handler_->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout,
|
|
||||||
&get_config()->upstream_write_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Http2Upstream::on_handler_delete() {
|
void Http2Upstream::on_handler_delete() {
|
||||||
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
||||||
if (ent.second->accesslog_ready()) {
|
if (ent.second->accesslog_ready()) {
|
||||||
|
@ -1453,14 +1328,13 @@ int Http2Upstream::on_downstream_reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = send();
|
handler_->signal_write();
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Upstream::set_deferred(bool f) { deferred_ = f; }
|
void Http2Upstream::set_deferred(bool f) { deferred_ = f; }
|
||||||
|
|
||||||
|
MemchunkPool4K *Http2Upstream::get_mcpool() { return &mcpool_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -29,11 +29,15 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#include "shrpx_upstream.h"
|
#include "shrpx_upstream.h"
|
||||||
#include "shrpx_downstream_queue.h"
|
#include "shrpx_downstream_queue.h"
|
||||||
#include "libevent_util.h"
|
#include "memchunk.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
@ -46,16 +50,16 @@ public:
|
||||||
virtual ~Http2Upstream();
|
virtual ~Http2Upstream();
|
||||||
virtual int on_read();
|
virtual int on_read();
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual int on_event();
|
|
||||||
virtual int on_timeout(Downstream *downstream);
|
virtual int on_timeout(Downstream *downstream);
|
||||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||||
unsigned int status_code);
|
unsigned int status_code);
|
||||||
int send();
|
|
||||||
int perform_send();
|
|
||||||
virtual ClientHandler *get_client_handler() const;
|
virtual ClientHandler *get_client_handler() const;
|
||||||
virtual bufferevent_data_cb get_downstream_readcb();
|
|
||||||
virtual bufferevent_data_cb get_downstream_writecb();
|
virtual int downstream_read(DownstreamConnection *dconn);
|
||||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
virtual int downstream_write(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||||
|
|
||||||
void add_pending_downstream(std::unique_ptr<Downstream> downstream);
|
void add_pending_downstream(std::unique_ptr<Downstream> downstream);
|
||||||
void remove_downstream(Downstream *downstream);
|
void remove_downstream(Downstream *downstream);
|
||||||
Downstream *find_downstream(int32_t stream_id);
|
Downstream *find_downstream(int32_t stream_id);
|
||||||
|
@ -78,14 +82,14 @@ public:
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset();
|
virtual int on_downstream_reset();
|
||||||
|
|
||||||
virtual void reset_timeouts();
|
virtual MemchunkPool4K *get_mcpool();
|
||||||
|
|
||||||
bool get_flow_control() const;
|
bool get_flow_control() const;
|
||||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||||
// takes ownership of the |upstream|. This function returns 0 if it
|
// takes ownership of the |upstream|. This function returns 0 if it
|
||||||
// succeeds, or -1.
|
// succeeds, or -1.
|
||||||
int upgrade_upstream(HttpsUpstream *upstream);
|
int upgrade_upstream(HttpsUpstream *upstream);
|
||||||
int start_settings_timer();
|
void start_settings_timer();
|
||||||
void stop_settings_timer();
|
void stop_settings_timer();
|
||||||
int consume(int32_t stream_id, size_t len);
|
int consume(int32_t stream_id, size_t len);
|
||||||
void log_response_headers(Downstream *downstream,
|
void log_response_headers(Downstream *downstream,
|
||||||
|
@ -95,15 +99,16 @@ public:
|
||||||
|
|
||||||
void set_deferred(bool f);
|
void set_deferred(bool f);
|
||||||
|
|
||||||
nghttp2::util::EvbufferBuffer sendbuf;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DownstreamQueue downstream_queue_;
|
// must be put before downstream_queue_
|
||||||
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
||||||
|
MemchunkPool4K mcpool_;
|
||||||
|
DownstreamQueue downstream_queue_;
|
||||||
|
ev_timer settings_timer_;
|
||||||
ClientHandler *handler_;
|
ClientHandler *handler_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
event *settings_timerev_;
|
const uint8_t *data_pending_;
|
||||||
event *write_notifyev_;
|
size_t data_pendinglen_;
|
||||||
bool flow_control_;
|
bool flow_control_;
|
||||||
bool deferred_;
|
bool deferred_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,25 +36,99 @@
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const size_t OUTBUF_MAX_THRES = 64 * 1024;
|
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "Time out";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto downstream = dconn->get_downstream();
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
auto handler = upstream->get_client_handler();
|
||||||
|
|
||||||
|
// Do this so that dconn is not pooled
|
||||||
|
downstream->set_response_connection_close(true);
|
||||||
|
|
||||||
|
if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
|
auto downstream = dconn->get_downstream();
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
auto handler = upstream->get_client_handler();
|
||||||
|
|
||||||
|
if (upstream->downstream_read(dconn) != 0) {
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
|
auto downstream = dconn->get_downstream();
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
auto handler = upstream->get_client_handler();
|
||||||
|
|
||||||
|
if (upstream->downstream_write(dconn) != 0) {
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
|
auto downstream = dconn->get_downstream();
|
||||||
|
auto upstream = downstream->get_upstream();
|
||||||
|
auto handler = upstream->get_client_handler();
|
||||||
|
if (dconn->on_connect() != 0) {
|
||||||
|
delete handler;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writecb(loop, w, revents);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HttpDownstreamConnection::HttpDownstreamConnection(
|
HttpDownstreamConnection::HttpDownstreamConnection(
|
||||||
DownstreamConnectionPool *dconn_pool)
|
DownstreamConnectionPool *dconn_pool, struct ev_loop *loop)
|
||||||
: DownstreamConnection(dconn_pool), bev_(nullptr), ioctrl_(nullptr),
|
: DownstreamConnection(dconn_pool), rlimit_(loop, &rev_, 0, 0),
|
||||||
response_htp_{0} {}
|
ioctrl_(&rlimit_), response_htp_{0}, loop_(loop), fd_(-1) {
|
||||||
|
// We do not know fd yet, so just set dummy fd 0
|
||||||
|
ev_io_init(&wev_, connectcb, 0, EV_WRITE);
|
||||||
|
ev_io_init(&rev_, readcb, 0, EV_READ);
|
||||||
|
|
||||||
|
wev_.data = this;
|
||||||
|
rev_.data = this;
|
||||||
|
|
||||||
|
ev_timer_init(&wt_, timeoutcb, 0., get_config()->downstream_write_timeout);
|
||||||
|
ev_timer_init(&rt_, timeoutcb, 0., get_config()->downstream_read_timeout);
|
||||||
|
|
||||||
|
wt_.data = this;
|
||||||
|
rt_.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
HttpDownstreamConnection::~HttpDownstreamConnection() {
|
HttpDownstreamConnection::~HttpDownstreamConnection() {
|
||||||
if (bev_) {
|
ev_timer_stop(loop_, &rt_);
|
||||||
util::bev_disable_unless(bev_, EV_READ | EV_WRITE);
|
ev_timer_stop(loop_, &wt_);
|
||||||
bufferevent_free(bev_);
|
ev_io_stop(loop_, &rev_);
|
||||||
|
ev_io_stop(loop_, &wev_);
|
||||||
|
|
||||||
|
if (fd_ != -1) {
|
||||||
|
shutdown(fd_, SHUT_WR);
|
||||||
|
close(fd_);
|
||||||
}
|
}
|
||||||
// Downstream and DownstreamConnection may be deleted
|
// Downstream and DownstreamConnection may be deleted
|
||||||
// asynchronously.
|
// asynchronously.
|
||||||
|
@ -67,25 +141,25 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
||||||
}
|
}
|
||||||
auto upstream = downstream->get_upstream();
|
|
||||||
if (!bev_) {
|
if (fd_ == -1) {
|
||||||
auto connect_blocker = client_handler_->get_http1_connect_blocker();
|
auto connect_blocker = client_handler_->get_http1_connect_blocker();
|
||||||
|
|
||||||
if (connect_blocker->blocked()) {
|
if (connect_blocker->blocked()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto evbase = client_handler_->get_evbase();
|
|
||||||
auto worker_stat = client_handler_->get_worker_stat();
|
auto worker_stat = client_handler_->get_worker_stat();
|
||||||
|
auto end = worker_stat->next_downstream;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto i = worker_stat->next_downstream;
|
auto i = worker_stat->next_downstream;
|
||||||
++worker_stat->next_downstream;
|
++worker_stat->next_downstream;
|
||||||
worker_stat->next_downstream %= get_config()->downstream_addrs.size();
|
worker_stat->next_downstream %= get_config()->downstream_addrs.size();
|
||||||
|
|
||||||
auto fd = socket(get_config()->downstream_addrs[i].addr.storage.ss_family,
|
fd_ = util::create_nonblock_socket(
|
||||||
SOCK_STREAM | SOCK_CLOEXEC, 0);
|
get_config()->downstream_addrs[i].addr.storage.ss_family);
|
||||||
|
|
||||||
if (fd == -1) {
|
if (fd_ == -1) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
DCLOG(WARN, this) << "socket() failed; errno=" << error;
|
DCLOG(WARN, this) << "socket() failed; errno=" << error;
|
||||||
|
|
||||||
|
@ -94,32 +168,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bev_ = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE |
|
int rv;
|
||||||
BEV_OPT_DEFER_CALLBACKS);
|
rv = connect(fd_, const_cast<sockaddr *>(
|
||||||
if (!bev_) {
|
&get_config()->downstream_addrs[i].addr.sa),
|
||||||
|
get_config()->downstream_addrs[i].addrlen);
|
||||||
|
if (rv != 0 && errno != EINPROGRESS) {
|
||||||
auto error = errno;
|
auto error = errno;
|
||||||
DCLOG(WARN, this) << "bufferevent_socket_new() failed; errno=" << error;
|
DCLOG(WARN, this) << "connect() failed; errno=" << error;
|
||||||
|
|
||||||
connect_blocker->on_failure();
|
connect_blocker->on_failure();
|
||||||
close(fd);
|
close(fd_);
|
||||||
|
fd_ = -1;
|
||||||
|
|
||||||
return SHRPX_ERR_NETWORK;
|
if (end == worker_stat->next_downstream) {
|
||||||
}
|
|
||||||
int rv = bufferevent_socket_connect(
|
|
||||||
bev_,
|
|
||||||
// TODO maybe not thread-safe?
|
|
||||||
const_cast<sockaddr *>(&get_config()->downstream_addrs[i].addr.sa),
|
|
||||||
get_config()->downstream_addrs[i].addrlen);
|
|
||||||
if (rv != 0) {
|
|
||||||
auto error = errno;
|
|
||||||
DCLOG(WARN, this) << "bufferevent_socket_connect() failed; errno="
|
|
||||||
<< error;
|
|
||||||
|
|
||||||
connect_blocker->on_failure();
|
|
||||||
bufferevent_free(bev_);
|
|
||||||
bev_ = nullptr;
|
|
||||||
|
|
||||||
if (i == worker_stat->next_downstream) {
|
|
||||||
return SHRPX_ERR_NETWORK;
|
return SHRPX_ERR_NETWORK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,34 +194,33 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
DCLOG(INFO, this) << "Connecting to downstream server";
|
DCLOG(INFO, this) << "Connecting to downstream server";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ev_io_set(&wev_, fd_, EV_WRITE);
|
||||||
|
ev_io_set(&rev_, fd_, EV_READ);
|
||||||
|
|
||||||
|
ev_io_start(loop_, &wev_);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream_ = downstream;
|
downstream_ = downstream;
|
||||||
|
|
||||||
ioctrl_.set_bev(bev_);
|
|
||||||
|
|
||||||
http_parser_init(&response_htp_, HTTP_RESPONSE);
|
http_parser_init(&response_htp_, HTTP_RESPONSE);
|
||||||
response_htp_.data = downstream_;
|
response_htp_.data = downstream_;
|
||||||
|
|
||||||
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
|
ev_set_cb(&rev_, readcb);
|
||||||
util::bev_enable_unless(bev_, EV_READ);
|
|
||||||
bufferevent_setcb(bev_, upstream->get_downstream_readcb(),
|
|
||||||
upstream->get_downstream_writecb(),
|
|
||||||
upstream->get_downstream_eventcb(), this);
|
|
||||||
|
|
||||||
reset_timeouts();
|
ev_timer_set(&rt_, 0., get_config()->downstream_read_timeout);
|
||||||
|
|
||||||
|
// TODO we should have timeout for connection establishment
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::push_request_headers() {
|
int HttpDownstreamConnection::push_request_headers() {
|
||||||
assert(downstream_->get_request_headers_normalized());
|
|
||||||
|
|
||||||
downstream_->assemble_request_cookie();
|
downstream_->assemble_request_cookie();
|
||||||
|
|
||||||
auto end_headers = std::end(downstream_->get_request_headers());
|
|
||||||
// Assume that method and request path do not contain \r\n.
|
// Assume that method and request path do not contain \r\n.
|
||||||
std::string hdrs = downstream_->get_request_method();
|
std::string hdrs = downstream_->get_request_method();
|
||||||
hdrs += " ";
|
hdrs += " ";
|
||||||
|
@ -196,14 +256,14 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
hdrs += downstream_->get_request_path();
|
hdrs += downstream_->get_request_path();
|
||||||
}
|
}
|
||||||
hdrs += " HTTP/1.1\r\n";
|
hdrs += " HTTP/1.1\r\n";
|
||||||
if (downstream_->get_norm_request_header("host") == end_headers &&
|
if (!downstream_->get_request_header(http2::HD_HOST) &&
|
||||||
!downstream_->get_request_http2_authority().empty()) {
|
!downstream_->get_request_http2_authority().empty()) {
|
||||||
hdrs += "Host: ";
|
hdrs += "Host: ";
|
||||||
hdrs += downstream_->get_request_http2_authority();
|
hdrs += downstream_->get_request_http2_authority();
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
}
|
}
|
||||||
http2::build_http1_headers_from_norm_headers(
|
http2::build_http1_headers_from_headers(hdrs,
|
||||||
hdrs, downstream_->get_request_headers());
|
downstream_->get_request_headers());
|
||||||
|
|
||||||
if (!downstream_->get_assembled_request_cookie().empty()) {
|
if (!downstream_->get_assembled_request_cookie().empty()) {
|
||||||
hdrs += "Cookie: ";
|
hdrs += "Cookie: ";
|
||||||
|
@ -213,7 +273,7 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
if (downstream_->get_request_method() != "CONNECT" &&
|
if (downstream_->get_request_method() != "CONNECT" &&
|
||||||
downstream_->get_request_http2_expect_body() &&
|
downstream_->get_request_http2_expect_body() &&
|
||||||
downstream_->get_norm_request_header("content-length") == end_headers) {
|
!downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
|
||||||
|
|
||||||
downstream_->set_chunked_request(true);
|
downstream_->set_chunked_request(true);
|
||||||
hdrs += "Transfer-Encoding: chunked\r\n";
|
hdrs += "Transfer-Encoding: chunked\r\n";
|
||||||
|
@ -222,18 +282,17 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
if (downstream_->get_request_connection_close()) {
|
if (downstream_->get_request_connection_close()) {
|
||||||
hdrs += "Connection: close\r\n";
|
hdrs += "Connection: close\r\n";
|
||||||
}
|
}
|
||||||
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
|
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
|
||||||
if (get_config()->add_x_forwarded_for) {
|
if (get_config()->add_x_forwarded_for) {
|
||||||
hdrs += "X-Forwarded-For: ";
|
hdrs += "X-Forwarded-For: ";
|
||||||
if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) {
|
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||||
hdrs += (*xff).value;
|
hdrs += (*xff).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
||||||
hdrs += ", ";
|
hdrs += ", ";
|
||||||
}
|
}
|
||||||
hdrs += client_handler_->get_ipaddr();
|
hdrs += client_handler_->get_ipaddr();
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
} else if (xff != end_headers &&
|
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||||
!get_config()->strip_incoming_x_forwarded_for) {
|
|
||||||
hdrs += "X-Forwarded-For: ";
|
hdrs += "X-Forwarded-For: ";
|
||||||
hdrs += (*xff).value;
|
hdrs += (*xff).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
||||||
|
@ -251,17 +310,16 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
hdrs += "http\r\n";
|
hdrs += "http\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto expect = downstream_->get_norm_request_header("expect");
|
auto expect = downstream_->get_request_header(http2::HD_EXPECT);
|
||||||
if (expect != end_headers &&
|
if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) {
|
||||||
!util::strifind((*expect).value.c_str(), "100-continue")) {
|
|
||||||
hdrs += "Expect: ";
|
hdrs += "Expect: ";
|
||||||
hdrs += (*expect).value;
|
hdrs += (*expect).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size());
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
}
|
}
|
||||||
auto via = downstream_->get_norm_request_header("via");
|
auto via = downstream_->get_request_header(http2::HD_VIA);
|
||||||
if (get_config()->no_via) {
|
if (get_config()->no_via) {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
hdrs += "Via: ";
|
hdrs += "Via: ";
|
||||||
hdrs += (*via).value;
|
hdrs += (*via).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||||
|
@ -269,7 +327,7 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hdrs += "Via: ";
|
hdrs += "Via: ";
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
hdrs += (*via).value;
|
hdrs += (*via).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||||
hdrs += ", ";
|
hdrs += ", ";
|
||||||
|
@ -292,65 +350,55 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
DCLOG(INFO, this) << "HTTP request headers. stream_id="
|
DCLOG(INFO, this) << "HTTP request headers. stream_id="
|
||||||
<< downstream_->get_stream_id() << "\n" << hdrp;
|
<< downstream_->get_stream_id() << "\n" << hdrp;
|
||||||
}
|
}
|
||||||
auto output = bufferevent_get_output(bev_);
|
auto output = downstream_->get_request_buf();
|
||||||
int rv;
|
output->append(hdrs.c_str(), hdrs.size());
|
||||||
rv = evbuffer_add(output, hdrs.c_str(), hdrs.size());
|
|
||||||
if (rv != 0) {
|
signal_write();
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
||||||
size_t datalen) {
|
size_t datalen) {
|
||||||
int rv;
|
auto chunked = downstream_->get_chunked_request();
|
||||||
int chunked = downstream_->get_chunked_request();
|
auto output = downstream_->get_request_buf();
|
||||||
auto output = bufferevent_get_output(bev_);
|
|
||||||
|
|
||||||
if (chunked) {
|
if (chunked) {
|
||||||
auto chunk_size_hex = util::utox(datalen);
|
auto chunk_size_hex = util::utox(datalen);
|
||||||
chunk_size_hex += "\r\n";
|
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||||
|
output->append_cstr("\r\n");
|
||||||
rv = evbuffer_add(output, chunk_size_hex.c_str(), chunk_size_hex.size());
|
|
||||||
if (rv == -1) {
|
|
||||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = evbuffer_add(output, data, datalen);
|
output->append(data, datalen);
|
||||||
|
|
||||||
if (rv == -1) {
|
|
||||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunked) {
|
if (chunked) {
|
||||||
rv = evbuffer_add(output, "\r\n", 2);
|
output->append_cstr("\r\n");
|
||||||
if (rv == -1) {
|
|
||||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signal_write();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::end_upload_data() {
|
int HttpDownstreamConnection::end_upload_data() {
|
||||||
if (downstream_->get_chunked_request()) {
|
if (!downstream_->get_chunked_request()) {
|
||||||
auto output = bufferevent_get_output(bev_);
|
return 0;
|
||||||
if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) {
|
|
||||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto output = downstream_->get_request_buf();
|
||||||
|
output->append_cstr("0\r\n\r\n");
|
||||||
|
|
||||||
|
signal_write();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void idle_readcb(bufferevent *bev, void *arg) {
|
void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||||
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "Idle connection EOF";
|
||||||
|
}
|
||||||
auto dconn_pool = dconn->get_dconn_pool();
|
auto dconn_pool = dconn->get_dconn_pool();
|
||||||
dconn_pool->remove_downstream_connection(dconn);
|
dconn_pool->remove_downstream_connection(dconn);
|
||||||
// dconn was deleted
|
// dconn was deleted
|
||||||
|
@ -358,26 +406,10 @@ void idle_readcb(bufferevent *bev, void *arg) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Gets called when DownstreamConnection is pooled in ClientHandler.
|
void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
void idle_eventcb(bufferevent *bev, short events, void *arg) {
|
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||||
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
|
if (LOG_ENABLED(INFO)) {
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
DCLOG(INFO, dconn) << "Idle connection timeout";
|
||||||
// Downstream was detached before connection established?
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Idle connection connected?";
|
|
||||||
}
|
|
||||||
} else if (events & BEV_EVENT_EOF) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Idle connection EOF";
|
|
||||||
}
|
|
||||||
} else if (events & BEV_EVENT_TIMEOUT) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Idle connection timeout";
|
|
||||||
}
|
|
||||||
} else if (events & BEV_EVENT_ERROR) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Idle connection network error";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
auto dconn_pool = dconn->get_dconn_pool();
|
auto dconn_pool = dconn->get_dconn_pool();
|
||||||
dconn_pool->remove_downstream_connection(dconn);
|
dconn_pool->remove_downstream_connection(dconn);
|
||||||
|
@ -391,15 +423,17 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
downstream_ = nullptr;
|
downstream_ = nullptr;
|
||||||
ioctrl_.force_resume_read();
|
ioctrl_.force_resume_read();
|
||||||
util::bev_enable_unless(bev_, EV_READ);
|
|
||||||
bufferevent_setcb(bev_, idle_readcb, nullptr, idle_eventcb, this);
|
|
||||||
// On idle state, just enable read timeout. Normally idle downstream
|
|
||||||
// connection will get EOF from the downstream server and closed.
|
|
||||||
bufferevent_set_timeouts(bev_, &get_config()->downstream_idle_read_timeout,
|
|
||||||
&get_config()->downstream_write_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent *HttpDownstreamConnection::get_bev() { return bev_; }
|
ev_io_start(loop_, &rev_);
|
||||||
|
ev_io_stop(loop_, &wev_);
|
||||||
|
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
|
||||||
|
ev_set_cb(&rev_, idle_readcb);
|
||||||
|
ev_timer_set(&rt_, 0., get_config()->downstream_idle_read_timeout);
|
||||||
|
ev_set_cb(&rt_, idle_timeoutcb);
|
||||||
|
ev_timer_again(loop_, &rt_);
|
||||||
|
}
|
||||||
|
|
||||||
void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
|
void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
|
||||||
ioctrl_.pause_read(reason);
|
ioctrl_.pause_read(reason);
|
||||||
|
@ -407,7 +441,10 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
|
||||||
|
|
||||||
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
|
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
|
||||||
size_t consumed) {
|
size_t consumed) {
|
||||||
ioctrl_.resume_read(reason);
|
if (!downstream_->response_buf_full()) {
|
||||||
|
ioctrl_.resume_read(reason);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,11 +452,6 @@ void HttpDownstreamConnection::force_resume_read() {
|
||||||
ioctrl_.force_resume_read();
|
ioctrl_.force_resume_read();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpDownstreamConnection::get_output_buffer_full() {
|
|
||||||
auto output = bufferevent_get_output(bev_);
|
|
||||||
return evbuffer_get_length(output) >= OUTBUF_MAX_THRES;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int htp_msg_begincb(http_parser *htp) {
|
int htp_msg_begincb(http_parser *htp) {
|
||||||
auto downstream = static_cast<Downstream *>(htp->data);
|
auto downstream = static_cast<Downstream *>(htp->data);
|
||||||
|
@ -442,6 +474,8 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
downstream->set_response_major(htp->http_major);
|
downstream->set_response_major(htp->http_major);
|
||||||
downstream->set_response_minor(htp->http_minor);
|
downstream->set_response_minor(htp->http_minor);
|
||||||
|
|
||||||
|
downstream->index_response_headers();
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
// For non-final response code, we just call
|
// For non-final response code, we just call
|
||||||
// on_downstream_header_complete() without changing response
|
// on_downstream_header_complete() without changing response
|
||||||
|
@ -577,52 +611,62 @@ http_parser_settings htp_hooks = {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int HttpDownstreamConnection::on_read() {
|
int HttpDownstreamConnection::on_read() {
|
||||||
reset_timeouts();
|
ev_timer_again(loop_, &rt_);
|
||||||
|
uint8_t buf[8192];
|
||||||
auto input = bufferevent_get_input(bev_);
|
int rv;
|
||||||
|
|
||||||
if (downstream_->get_upgraded()) {
|
if (downstream_->get_upgraded()) {
|
||||||
// For upgraded connection, just pass data to the upstream.
|
// For upgraded connection, just pass data to the upstream.
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
ssize_t nread;
|
||||||
|
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||||
if (inputlen == 0) {
|
;
|
||||||
assert(evbuffer_get_length(input) == 0);
|
if (nread == -1) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
return DownstreamConnection::ERR_NET;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mem = evbuffer_pullup(input, inputlen);
|
if (nread == 0) {
|
||||||
|
return DownstreamConnection::ERR_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
int rv;
|
rv = downstream_->get_upstream()->on_downstream_body(downstream_, buf,
|
||||||
rv = downstream_->get_upstream()->on_downstream_body(
|
nread, true);
|
||||||
downstream_, reinterpret_cast<const uint8_t *>(mem), inputlen, true);
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
if (evbuffer_drain(input, inputlen) != 0) {
|
|
||||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
if (downstream_->response_buf_full()) {
|
||||||
return -1;
|
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
ssize_t nread;
|
||||||
|
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||||
if (inputlen == 0) {
|
;
|
||||||
assert(evbuffer_get_length(input) == 0);
|
if (nread == -1) {
|
||||||
return 0;
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return DownstreamConnection::ERR_NET;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mem = evbuffer_pullup(input, inputlen);
|
if (nread == 0) {
|
||||||
|
return DownstreamConnection::ERR_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
auto nread =
|
auto nproc = http_parser_execute(&response_htp_, &htp_hooks,
|
||||||
http_parser_execute(&response_htp_, &htp_hooks,
|
reinterpret_cast<char *>(buf), nread);
|
||||||
reinterpret_cast<const char *>(mem), inputlen);
|
|
||||||
|
|
||||||
if (evbuffer_drain(input, nread) != 0) {
|
if (nproc != static_cast<size_t>(nread)) {
|
||||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, this) << "nproc != nread";
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -635,29 +679,70 @@ int HttpDownstreamConnection::on_read() {
|
||||||
<< http_errno_description(htperr);
|
<< http_errno_description(htperr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SHRPX_ERR_HTTP_PARSE;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream_->response_buf_full()) {
|
||||||
|
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpDownstreamConnection::on_write() {
|
int HttpDownstreamConnection::on_write() {
|
||||||
reset_timeouts();
|
ev_timer_again(loop_, &rt_);
|
||||||
|
|
||||||
auto upstream = downstream_->get_upstream();
|
auto upstream = downstream_->get_upstream();
|
||||||
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
|
auto input = downstream_->get_request_buf();
|
||||||
downstream_->get_request_datalen());
|
|
||||||
|
while (input->rleft() > 0) {
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = input->riovec(iov, util::array_size(iov));
|
||||||
|
|
||||||
|
ssize_t nwrite;
|
||||||
|
while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
if (nwrite == -1) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
ev_io_start(loop_, &wev_);
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
return DownstreamConnection::ERR_NET;
|
||||||
|
}
|
||||||
|
input->drain(nwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input->rleft() == 0) {
|
||||||
|
ev_io_stop(loop_, &wev_);
|
||||||
|
ev_timer_stop(loop_, &wt_);
|
||||||
|
} else {
|
||||||
|
ev_io_start(loop_, &wev_);
|
||||||
|
ev_timer_again(loop_, &wt_);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (input->rleft() == 0) {
|
||||||
|
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
|
||||||
|
downstream_->get_request_datalen());
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {
|
int HttpDownstreamConnection::on_connect() {
|
||||||
bufferevent_setcb(bev_, upstream->get_downstream_readcb(),
|
if (!util::check_socket_connected(fd_)) {
|
||||||
upstream->get_downstream_writecb(),
|
return -1;
|
||||||
upstream->get_downstream_eventcb(), this);
|
}
|
||||||
|
|
||||||
|
ev_io_start(loop_, &rev_);
|
||||||
|
ev_set_cb(&wev_, writecb);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpDownstreamConnection::reset_timeouts() {
|
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
|
||||||
bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout,
|
|
||||||
&get_config()->downstream_write_timeout);
|
void HttpDownstreamConnection::signal_write() { ev_io_start(loop_, &wev_); }
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -27,9 +27,6 @@
|
||||||
|
|
||||||
#include "shrpx.h"
|
#include "shrpx.h"
|
||||||
|
|
||||||
#include <event.h>
|
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include "http-parser/http_parser.h"
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
#include "shrpx_downstream_connection.h"
|
#include "shrpx_downstream_connection.h"
|
||||||
|
@ -41,7 +38,8 @@ class DownstreamConnectionPool;
|
||||||
|
|
||||||
class HttpDownstreamConnection : public DownstreamConnection {
|
class HttpDownstreamConnection : public DownstreamConnection {
|
||||||
public:
|
public:
|
||||||
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool);
|
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool,
|
||||||
|
struct ev_loop *loop);
|
||||||
virtual ~HttpDownstreamConnection();
|
virtual ~HttpDownstreamConnection();
|
||||||
virtual int attach_downstream(Downstream *downstream);
|
virtual int attach_downstream(Downstream *downstream);
|
||||||
virtual void detach_downstream(Downstream *downstream);
|
virtual void detach_downstream(Downstream *downstream);
|
||||||
|
@ -54,22 +52,25 @@ public:
|
||||||
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
||||||
virtual void force_resume_read();
|
virtual void force_resume_read();
|
||||||
|
|
||||||
virtual bool get_output_buffer_full();
|
|
||||||
|
|
||||||
virtual int on_read();
|
virtual int on_read();
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
|
|
||||||
virtual void on_upstream_change(Upstream *upstream);
|
virtual void on_upstream_change(Upstream *upstream);
|
||||||
virtual int on_priority_change(int32_t pri) { return 0; }
|
virtual int on_priority_change(int32_t pri) { return 0; }
|
||||||
|
|
||||||
bufferevent *get_bev();
|
int on_connect();
|
||||||
|
void signal_write();
|
||||||
void reset_timeouts();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bufferevent *bev_;
|
ev_io wev_;
|
||||||
|
ev_io rev_;
|
||||||
|
ev_timer wt_;
|
||||||
|
ev_timer rt_;
|
||||||
|
RateLimit rlimit_;
|
||||||
IOControl ioctrl_;
|
IOControl ioctrl_;
|
||||||
http_parser response_htp_;
|
http_parser response_htp_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
|
int fd_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
#include "shrpx_client_handler.h"
|
#include "shrpx_client_handler.h"
|
||||||
#include "shrpx_downstream.h"
|
#include "shrpx_downstream.h"
|
||||||
#include "shrpx_downstream_connection.h"
|
#include "shrpx_downstream_connection.h"
|
||||||
#include "shrpx_http2_downstream_connection.h"
|
//#include "shrpx_http2_downstream_connection.h"
|
||||||
#include "shrpx_http.h"
|
#include "shrpx_http.h"
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "shrpx_error.h"
|
#include "shrpx_error.h"
|
||||||
|
@ -43,13 +43,9 @@ using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
|
||||||
const size_t OUTBUF_MAX_THRES = 16 * 1024;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
HttpsUpstream::HttpsUpstream(ClientHandler *handler)
|
HttpsUpstream::HttpsUpstream(ClientHandler *handler)
|
||||||
: handler_(handler), current_header_length_(0),
|
: handler_(handler), current_header_length_(0),
|
||||||
ioctrl_(handler->get_bev()) {
|
ioctrl_(handler->get_rlimit()) {
|
||||||
http_parser_init(&htp_, HTTP_REQUEST);
|
http_parser_init(&htp_, HTTP_REQUEST);
|
||||||
htp_.data = this;
|
htp_.data = this;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +149,7 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
|
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->normalize_request_headers();
|
downstream->index_request_headers();
|
||||||
|
|
||||||
downstream->inspect_http1_request();
|
downstream->inspect_http1_request();
|
||||||
|
|
||||||
|
@ -241,37 +237,32 @@ http_parser_settings htp_hooks = {
|
||||||
// on_read() does not consume all available data in input buffer if
|
// on_read() does not consume all available data in input buffer if
|
||||||
// one http request is fully received.
|
// one http request is fully received.
|
||||||
int HttpsUpstream::on_read() {
|
int HttpsUpstream::on_read() {
|
||||||
auto bev = handler_->get_bev();
|
auto rb = handler_->get_rb();
|
||||||
auto input = bufferevent_get_input(bev);
|
|
||||||
auto downstream = get_downstream();
|
auto downstream = get_downstream();
|
||||||
|
const void *data;
|
||||||
|
size_t datalen;
|
||||||
|
|
||||||
// downstream can be nullptr here, because it is initialized in the
|
// downstream can be nullptr here, because it is initialized in the
|
||||||
// callback chain called by http_parser_execute()
|
// callback chain called by http_parser_execute()
|
||||||
if (downstream && downstream->get_upgraded()) {
|
if (downstream && downstream->get_upgraded()) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
std::tie(data, datalen) = rb->get();
|
||||||
|
if (datalen == 0) {
|
||||||
if (inputlen == 0) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mem = evbuffer_pullup(input, inputlen);
|
|
||||||
|
|
||||||
auto rv = downstream->push_upload_data_chunk(
|
auto rv = downstream->push_upload_data_chunk(
|
||||||
reinterpret_cast<const uint8_t *>(mem), inputlen);
|
reinterpret_cast<const uint8_t *>(data), datalen);
|
||||||
|
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_drain(input, inputlen) != 0) {
|
rb->drain(datalen);
|
||||||
ULOG(FATAL, this) << "evbuffer_drain() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downstream->get_output_buffer_full()) {
|
if (downstream->request_buf_full()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, this) << "Downstream output buffer is full";
|
ULOG(INFO, this) << "Downstream request buf is full";
|
||||||
}
|
}
|
||||||
pause_read(SHRPX_NO_BUFFER);
|
pause_read(SHRPX_NO_BUFFER);
|
||||||
|
|
||||||
|
@ -281,21 +272,15 @@ int HttpsUpstream::on_read() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
std::tie(data, datalen) = rb->get();
|
||||||
|
if (datalen == 0) {
|
||||||
if (inputlen == 0) {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto mem = evbuffer_pullup(input, inputlen);
|
|
||||||
|
|
||||||
auto nread = http_parser_execute(
|
auto nread = http_parser_execute(
|
||||||
&htp_, &htp_hooks, reinterpret_cast<const char *>(mem), inputlen);
|
&htp_, &htp_hooks, reinterpret_cast<const char *>(data), datalen);
|
||||||
|
|
||||||
if (evbuffer_drain(input, nread) != 0) {
|
rb->drain(nread);
|
||||||
ULOG(FATAL, this) << "evbuffer_drain() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Well, actually header length + some body bytes
|
// Well, actually header length + some body bytes
|
||||||
current_header_length_ += nread;
|
current_header_length_ += nread;
|
||||||
|
@ -318,6 +303,7 @@ int HttpsUpstream::on_read() {
|
||||||
if (error_reply(503) != 0) {
|
if (error_reply(503) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
handler_->signal_write();
|
||||||
// Downstream gets deleted after response body is read.
|
// Downstream gets deleted after response body is read.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -339,6 +325,8 @@ int HttpsUpstream::on_read() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,13 +358,15 @@ int HttpsUpstream::on_read() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// downstream can be NULL here.
|
// downstream can be NULL here.
|
||||||
if (downstream && downstream->get_output_buffer_full()) {
|
if (downstream && downstream->request_buf_full()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, this) << "Downstream output buffer is full";
|
ULOG(INFO, this) << "Downstream request buffer is full";
|
||||||
}
|
}
|
||||||
|
|
||||||
pause_read(SHRPX_NO_BUFFER);
|
pause_read(SHRPX_NO_BUFFER);
|
||||||
|
@ -387,31 +377,45 @@ int HttpsUpstream::on_read() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpsUpstream::on_write() {
|
int HttpsUpstream::on_write() {
|
||||||
int rv = 0;
|
|
||||||
auto downstream = get_downstream();
|
auto downstream = get_downstream();
|
||||||
if (downstream) {
|
if (!downstream) {
|
||||||
// We need to postpone detachment until all data are sent so that
|
return 0;
|
||||||
// we can notify nghttp2 library all data consumed.
|
|
||||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
|
||||||
if (downstream->get_response_connection_close()) {
|
|
||||||
// Connection close
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
// dconn was deleted
|
|
||||||
} else {
|
|
||||||
// Keep-alive
|
|
||||||
downstream->detach_downstream_connection();
|
|
||||||
}
|
|
||||||
// We need this if response ends before request.
|
|
||||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
|
||||||
delete_downstream();
|
|
||||||
return resume_read(SHRPX_MSG_BLOCK, nullptr, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = downstream->resume_read(SHRPX_NO_BUFFER,
|
|
||||||
downstream->get_response_datalen());
|
|
||||||
}
|
}
|
||||||
return rv;
|
auto wb = handler_->get_wb();
|
||||||
|
struct iovec iov[2];
|
||||||
|
auto iovcnt = wb->wiovec(iov);
|
||||||
|
if (iovcnt == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
auto output = downstream->get_response_buf();
|
||||||
|
for (int i = 0; i < iovcnt; ++i) {
|
||||||
|
auto n = output->remove(iov[i].iov_base, iov[i].iov_len);
|
||||||
|
wb->write(n);
|
||||||
|
}
|
||||||
|
if (wb->rleft() > 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to postpone detachment until all data are sent so that
|
||||||
|
// we can notify nghttp2 library all data consumed.
|
||||||
|
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
if (downstream->get_response_connection_close()) {
|
||||||
|
// Connection close
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
// dconn was deleted
|
||||||
|
} else {
|
||||||
|
// Keep-alive
|
||||||
|
downstream->detach_downstream_connection();
|
||||||
|
}
|
||||||
|
// We need this if response ends before request.
|
||||||
|
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
||||||
|
delete_downstream();
|
||||||
|
return resume_read(SHRPX_MSG_BLOCK, nullptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return downstream->resume_read(SHRPX_NO_BUFFER,
|
||||||
|
downstream->get_response_datalen());
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpsUpstream::on_event() { return 0; }
|
int HttpsUpstream::on_event() { return 0; }
|
||||||
|
@ -424,6 +428,10 @@ void HttpsUpstream::pause_read(IOCtrlReason reason) {
|
||||||
|
|
||||||
int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
size_t consumed) {
|
size_t consumed) {
|
||||||
|
// downstream could be nullptr if reason is SHRPX_MSG_BLOCK.
|
||||||
|
if (downstream && downstream->request_buf_full()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (ioctrl_.resume_read(reason)) {
|
if (ioctrl_.resume_read(reason)) {
|
||||||
// Process remaining data in input buffer here because these bytes
|
// Process remaining data in input buffer here because these bytes
|
||||||
// are not notified by readcb until new data arrive.
|
// are not notified by readcb until new data arrive.
|
||||||
|
@ -434,272 +442,147 @@ int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
void https_downstream_readcb(bufferevent *bev, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
|
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
rv = downstream->on_read();
|
rv = downstream->on_read();
|
||||||
|
|
||||||
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
||||||
delete upstream->get_client_handler();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rv != 0) {
|
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
|
||||||
// We already sent HTTP response headers to upstream
|
|
||||||
// client. Just close the upstream connection.
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We did not sent any HTTP response, so sent error
|
|
||||||
// response. Cannot reuse downstream connection in this case.
|
|
||||||
if (upstream->error_reply(502) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
|
||||||
upstream->delete_downstream();
|
|
||||||
|
|
||||||
// Process next HTTP request
|
|
||||||
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 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 pending data exist, we defer detachment to correctly notify
|
|
||||||
// the all consumed data to nghttp2 library.
|
|
||||||
if (handler->get_outbuf_length() == 0) {
|
|
||||||
if (downstream->get_response_connection_close()) {
|
|
||||||
// Connection close
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
|
|
||||||
dconn = nullptr;
|
|
||||||
} else {
|
|
||||||
// Keep-alive
|
|
||||||
downstream->detach_downstream_connection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, nullptr, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete handler here if we have no pending write.
|
|
||||||
if (handler->get_should_close_after_write() &&
|
|
||||||
handler->get_outbuf_length() == 0) {
|
|
||||||
delete handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void https_downstream_writecb(bufferevent *bev, void *ptr) {
|
|
||||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
|
||||||
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
|
|
||||||
// May return -1
|
|
||||||
upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
|
||||||
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
|
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Connection established";
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events & BEV_EVENT_EOF) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "EOF";
|
|
||||||
}
|
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
|
||||||
// Server may indicate the end of the request by EOF
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "The end of the response body was indicated by "
|
|
||||||
<< "EOF";
|
|
||||||
}
|
|
||||||
upstream->on_downstream_body_complete(downstream);
|
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
|
||||||
|
|
||||||
auto handler = upstream->get_client_handler();
|
|
||||||
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 if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
|
||||||
// error
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
DCLOG(INFO, dconn) << "Treated as error";
|
|
||||||
}
|
|
||||||
if (upstream->error_reply(502) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
|
||||||
upstream->delete_downstream();
|
|
||||||
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
if (events & BEV_EVENT_ERROR) {
|
|
||||||
DCLOG(INFO, dconn) << "Network error";
|
|
||||||
} else {
|
|
||||||
DCLOG(INFO, dconn) << "Timeout";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
|
||||||
unsigned int status;
|
|
||||||
if (events & BEV_EVENT_TIMEOUT) {
|
|
||||||
status = 504;
|
|
||||||
} else {
|
|
||||||
status = 502;
|
|
||||||
}
|
|
||||||
if (upstream->error_reply(status) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
|
|
||||||
upstream->delete_downstream();
|
|
||||||
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int HttpsUpstream::error_reply(unsigned int status_code) {
|
|
||||||
auto html = http::create_error_html(status_code);
|
|
||||||
auto downstream = get_downstream();
|
|
||||||
|
|
||||||
if (downstream) {
|
|
||||||
downstream->set_response_http_status(status_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string header;
|
|
||||||
header.reserve(512);
|
|
||||||
header += "HTTP/1.1 ";
|
|
||||||
header += http2::get_status_string(status_code);
|
|
||||||
header += "\r\nServer: ";
|
|
||||||
header += get_config()->server_name;
|
|
||||||
header += "\r\nContent-Length: ";
|
|
||||||
header += util::utos(html.size());
|
|
||||||
header += "\r\nContent-Type: text/html; charset=UTF-8\r\n";
|
|
||||||
if (get_client_handler()->get_should_close_after_write()) {
|
|
||||||
header += "Connection: close\r\n";
|
|
||||||
}
|
|
||||||
header += "\r\n";
|
|
||||||
auto output = bufferevent_get_output(handler_->get_bev());
|
|
||||||
if (evbuffer_add(output, header.c_str(), header.size()) != 0 ||
|
|
||||||
evbuffer_add(output, html.c_str(), html.size()) != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream) {
|
if (rv == DownstreamConnection::ERR_EOF) {
|
||||||
downstream->add_response_sent_bodylen(html.size());
|
return downstream_eof(dconn);
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
}
|
||||||
} else {
|
|
||||||
handler_->write_accesslog(1, 1, status_code, html.size());
|
if (rv == DownstreamConnection::ERR_NET) {
|
||||||
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpsUpstream::downstream_write(DownstreamConnection *dconn) {
|
||||||
|
int rv;
|
||||||
|
rv = dconn->on_write();
|
||||||
|
if (rv == DownstreamConnection::ERR_NET) {
|
||||||
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rv != 0) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_data_cb HttpsUpstream::get_downstream_readcb() {
|
int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
|
||||||
return https_downstream_readcb;
|
auto downstream = dconn->get_downstream();
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "EOF";
|
||||||
|
}
|
||||||
|
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||||
|
// Server may indicate the end of the request by EOF
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "The end of the response body was indicated by "
|
||||||
|
<< "EOF";
|
||||||
|
}
|
||||||
|
on_downstream_body_complete(downstream);
|
||||||
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||||
|
// error
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
DCLOG(INFO, dconn) << "Treated as error";
|
||||||
|
}
|
||||||
|
if (error_reply(502) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we don't know how to recover from this situation. Just
|
||||||
|
// drop connection.
|
||||||
|
return -1;
|
||||||
|
end:
|
||||||
|
handler_->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_data_cb HttpsUpstream::get_downstream_writecb() {
|
int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||||
return https_downstream_writecb;
|
auto downstream = dconn->get_downstream();
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
if (events & Downstream::EVENT_ERROR) {
|
||||||
|
DCLOG(INFO, dconn) << "Network error/general error";
|
||||||
|
} else {
|
||||||
|
DCLOG(INFO, dconn) << "Timeout";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (downstream->get_response_state() != Downstream::INITIAL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int status;
|
||||||
|
if (events & Downstream::EVENT_TIMEOUT) {
|
||||||
|
status = 504;
|
||||||
|
} else {
|
||||||
|
status = 502;
|
||||||
|
}
|
||||||
|
if (error_reply(status) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_event_cb HttpsUpstream::get_downstream_eventcb() {
|
int HttpsUpstream::error_reply(unsigned int status_code) {
|
||||||
return https_downstream_eventcb;
|
auto html = http::create_error_html(status_code);
|
||||||
|
auto downstream = get_downstream();
|
||||||
|
|
||||||
|
if (!downstream) {
|
||||||
|
attach_downstream(util::make_unique<Downstream>(this, 1, 1));
|
||||||
|
downstream = get_downstream();
|
||||||
|
}
|
||||||
|
|
||||||
|
downstream->set_response_http_status(status_code);
|
||||||
|
|
||||||
|
auto output = downstream->get_response_buf();
|
||||||
|
|
||||||
|
output->append_cstr("HTTP/1.1 ");
|
||||||
|
auto status_str = http2::get_status_string(status_code);
|
||||||
|
output->append(status_str.c_str(), status_str.size());
|
||||||
|
output->append_cstr("\r\nServer: ");
|
||||||
|
output->append(get_config()->server_name, strlen(get_config()->server_name));
|
||||||
|
output->append_cstr("\r\nContent-Length: ");
|
||||||
|
auto cl = util::utos(html.size());
|
||||||
|
output->append(cl.c_str(), cl.size());
|
||||||
|
output->append_cstr("\r\nContent-Type: text/html; charset=UTF-8\r\n");
|
||||||
|
if (get_client_handler()->get_should_close_after_write()) {
|
||||||
|
output->append_cstr("Connection: close\r\n");
|
||||||
|
}
|
||||||
|
output->append_cstr("\r\n");
|
||||||
|
output->append(html.c_str(), html.size());
|
||||||
|
|
||||||
|
downstream->add_response_sent_bodylen(html.size());
|
||||||
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
|
void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
|
||||||
|
@ -737,15 +620,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
hdrs += " ";
|
hdrs += " ";
|
||||||
hdrs += http2::get_status_string(downstream->get_response_http_status());
|
hdrs += http2::get_status_string(downstream->get_response_http_status());
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
downstream->normalize_response_headers();
|
|
||||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||||
!get_config()->no_location_rewrite) {
|
!get_config()->no_location_rewrite) {
|
||||||
downstream->rewrite_norm_location_response_header(
|
downstream->rewrite_location_response_header(
|
||||||
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
||||||
}
|
}
|
||||||
auto end_headers = std::end(downstream->get_response_headers());
|
|
||||||
http2::build_http1_headers_from_norm_headers(
|
http2::build_http1_headers_from_headers(hdrs,
|
||||||
hdrs, downstream->get_response_headers());
|
downstream->get_response_headers());
|
||||||
|
|
||||||
|
auto output = downstream->get_response_buf();
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
|
@ -754,11 +639,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
log_response_headers(hdrs);
|
log_response_headers(hdrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output = bufferevent_get_output(handler_->get_bev());
|
output->append(hdrs.c_str(), hdrs.size());
|
||||||
if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->clear_response_headers();
|
downstream->clear_response_headers();
|
||||||
|
|
||||||
|
@ -779,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
hdrs += "Connection: close\r\n";
|
hdrs += "Connection: close\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream->get_norm_response_header("alt-svc") == end_headers) {
|
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
|
||||||
// We won't change or alter alt-svc from backend at the moment.
|
// We won't change or alter alt-svc from backend for now
|
||||||
if (!get_config()->altsvcs.empty()) {
|
if (!get_config()->altsvcs.empty()) {
|
||||||
hdrs += "Alt-Svc: ";
|
hdrs += "Alt-Svc: ";
|
||||||
|
|
||||||
|
@ -803,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
hdrs += get_config()->server_name;
|
hdrs += get_config()->server_name;
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
} else {
|
} else {
|
||||||
auto server = downstream->get_norm_response_header("server");
|
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||||
if (server != end_headers) {
|
if (server) {
|
||||||
hdrs += "Server: ";
|
hdrs += "Server: ";
|
||||||
hdrs += (*server).value;
|
hdrs += (*server).value;
|
||||||
hdrs += "\r\n";
|
hdrs += "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto via = downstream->get_norm_response_header("via");
|
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||||
if (get_config()->no_via) {
|
if (get_config()->no_via) {
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
hdrs += "Via: ";
|
hdrs += "Via: ";
|
||||||
hdrs += (*via).value;
|
hdrs += (*via).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||||
|
@ -821,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hdrs += "Via: ";
|
hdrs += "Via: ";
|
||||||
if (via != end_headers) {
|
if (via) {
|
||||||
hdrs += (*via).value;
|
hdrs += (*via).value;
|
||||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||||
hdrs += ", ";
|
hdrs += ", ";
|
||||||
|
@ -844,11 +725,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
log_response_headers(hdrs);
|
log_response_headers(hdrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output = bufferevent_get_output(handler_->get_bev());
|
output->append(hdrs.c_str(), hdrs.size());
|
||||||
if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -856,45 +733,30 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
int HttpsUpstream::on_downstream_body(Downstream *downstream,
|
int HttpsUpstream::on_downstream_body(Downstream *downstream,
|
||||||
const uint8_t *data, size_t len,
|
const uint8_t *data, size_t len,
|
||||||
bool flush) {
|
bool flush) {
|
||||||
int rv;
|
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto output = bufferevent_get_output(handler_->get_bev());
|
auto output = downstream->get_response_buf();
|
||||||
if (downstream->get_chunked_response()) {
|
if (downstream->get_chunked_response()) {
|
||||||
auto chunk_size_hex = util::utox(len);
|
auto chunk_size_hex = util::utox(len);
|
||||||
chunk_size_hex += "\r\n";
|
chunk_size_hex += "\r\n";
|
||||||
|
|
||||||
rv = evbuffer_add(output, chunk_size_hex.c_str(), chunk_size_hex.size());
|
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||||
|
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (evbuffer_add(output, data, len) != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
output->append(data, len);
|
||||||
|
|
||||||
downstream->add_response_sent_bodylen(len);
|
downstream->add_response_sent_bodylen(len);
|
||||||
|
|
||||||
if (downstream->get_chunked_response()) {
|
if (downstream->get_chunked_response()) {
|
||||||
if (evbuffer_add(output, "\r\n", 2) != 0) {
|
output->append_cstr("\r\n");
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
||||||
if (downstream->get_chunked_response()) {
|
if (downstream->get_chunked_response()) {
|
||||||
auto output = bufferevent_get_output(handler_->get_bev());
|
auto output = downstream->get_response_buf();
|
||||||
if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) {
|
output->append_cstr("0\r\n\r\n");
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DLOG(INFO, downstream) << "HTTP response completed";
|
DLOG(INFO, downstream) << "HTTP response completed";
|
||||||
|
@ -925,11 +787,6 @@ void HttpsUpstream::log_response_headers(const std::string &hdrs) const {
|
||||||
ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
|
ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpsUpstream::reset_timeouts() {
|
|
||||||
handler_->set_upstream_timeouts(&get_config()->upstream_read_timeout,
|
|
||||||
&get_config()->upstream_write_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpsUpstream::on_handler_delete() {
|
void HttpsUpstream::on_handler_delete() {
|
||||||
if (downstream_ && downstream_->accesslog_ready()) {
|
if (downstream_ && downstream_->accesslog_ready()) {
|
||||||
handler_->write_accesslog(downstream_.get());
|
handler_->write_accesslog(downstream_.get());
|
||||||
|
@ -956,4 +813,6 @@ int HttpsUpstream::on_downstream_reset() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemchunkPool4K *HttpsUpstream::get_mcpool() { return &mcpool_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
#include "http-parser/http_parser.h"
|
#include "http-parser/http_parser.h"
|
||||||
|
|
||||||
#include "shrpx_upstream.h"
|
#include "shrpx_upstream.h"
|
||||||
|
#include "memchunk.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
@ -49,9 +52,12 @@ public:
|
||||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||||
unsigned int status_code);
|
unsigned int status_code);
|
||||||
virtual ClientHandler *get_client_handler() const;
|
virtual ClientHandler *get_client_handler() const;
|
||||||
virtual bufferevent_data_cb get_downstream_readcb();
|
|
||||||
virtual bufferevent_data_cb get_downstream_writecb();
|
virtual int downstream_read(DownstreamConnection *dconn);
|
||||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
virtual int downstream_write(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||||
|
|
||||||
void attach_downstream(std::unique_ptr<Downstream> downstream);
|
void attach_downstream(std::unique_ptr<Downstream> downstream);
|
||||||
void delete_downstream();
|
void delete_downstream();
|
||||||
Downstream *get_downstream() const;
|
Downstream *get_downstream() const;
|
||||||
|
@ -70,7 +76,7 @@ public:
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset();
|
virtual int on_downstream_reset();
|
||||||
|
|
||||||
virtual void reset_timeouts();
|
virtual MemchunkPool4K *get_mcpool();
|
||||||
|
|
||||||
void reset_current_header_length();
|
void reset_current_header_length();
|
||||||
void log_response_headers(const std::string &hdrs) const;
|
void log_response_headers(const std::string &hdrs) const;
|
||||||
|
@ -79,6 +85,8 @@ private:
|
||||||
ClientHandler *handler_;
|
ClientHandler *handler_;
|
||||||
http_parser htp_;
|
http_parser htp_;
|
||||||
size_t current_header_length_;
|
size_t current_header_length_;
|
||||||
|
// must be put before downstream_
|
||||||
|
MemchunkPool4K mcpool_;
|
||||||
std::unique_ptr<Downstream> downstream_;
|
std::unique_ptr<Downstream> downstream_;
|
||||||
IOControl ioctrl_;
|
IOControl ioctrl_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,42 +26,40 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "shrpx_rate_limit.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
IOControl::IOControl(bufferevent *bev) : bev_(bev), rdbits_(0) {}
|
IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {}
|
||||||
|
|
||||||
IOControl::~IOControl() {}
|
IOControl::~IOControl() {}
|
||||||
|
|
||||||
void IOControl::set_bev(bufferevent *bev) { bev_ = bev; }
|
|
||||||
|
|
||||||
void IOControl::pause_read(IOCtrlReason reason) {
|
void IOControl::pause_read(IOCtrlReason reason) {
|
||||||
rdbits_ |= reason;
|
rdbits_ |= reason;
|
||||||
if (bev_) {
|
if (lim_) {
|
||||||
util::bev_disable_unless(bev_, EV_READ);
|
lim_->stopw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IOControl::resume_read(IOCtrlReason reason) {
|
bool IOControl::resume_read(IOCtrlReason reason) {
|
||||||
rdbits_ &= ~reason;
|
rdbits_ &= ~reason;
|
||||||
if (rdbits_ == 0) {
|
if (rdbits_ == 0) {
|
||||||
if (bev_) {
|
if (lim_) {
|
||||||
util::bev_enable_unless(bev_, EV_READ);
|
lim_->startw();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IOControl::force_resume_read() {
|
void IOControl::force_resume_read() {
|
||||||
rdbits_ = 0;
|
rdbits_ = 0;
|
||||||
if (bev_) {
|
if (lim_) {
|
||||||
util::bev_enable_unless(bev_, EV_READ);
|
lim_->startw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,9 @@
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
#include "shrpx_rate_limit.h"
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
@ -38,9 +39,8 @@ enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 };
|
||||||
|
|
||||||
class IOControl {
|
class IOControl {
|
||||||
public:
|
public:
|
||||||
IOControl(bufferevent *bev);
|
IOControl(RateLimit *lim);
|
||||||
~IOControl();
|
~IOControl();
|
||||||
void set_bev(bufferevent *bev);
|
|
||||||
void pause_read(IOCtrlReason reason);
|
void pause_read(IOCtrlReason reason);
|
||||||
// Returns true if read operation is enabled after this call
|
// Returns true if read operation is enabled after this call
|
||||||
bool resume_read(IOCtrlReason reason);
|
bool resume_read(IOCtrlReason reason);
|
||||||
|
@ -48,7 +48,7 @@ public:
|
||||||
void force_resume_read();
|
void force_resume_read();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bufferevent *bev_;
|
RateLimit *lim_;
|
||||||
uint32_t rdbits_;
|
uint32_t rdbits_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,7 @@
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <event2/bufferevent_ssl.h>
|
|
||||||
|
|
||||||
#include "shrpx_client_handler.h"
|
#include "shrpx_client_handler.h"
|
||||||
#include "shrpx_thread_event_receiver.h"
|
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
#include "shrpx_worker_config.h"
|
#include "shrpx_worker_config.h"
|
||||||
|
@ -40,16 +37,16 @@
|
||||||
#include "shrpx_http2_session.h"
|
#include "shrpx_http2_session.h"
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
#include "shrpx_downstream_connection.h"
|
#include "shrpx_downstream_connection.h"
|
||||||
|
#include "shrpx_accept_handler.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
||||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
auto h = static_cast<ListenHandler *>(w->data);
|
||||||
|
|
||||||
// If we are in graceful shutdown period, we must not enable
|
// If we are in graceful shutdown period, we must not enable
|
||||||
// evlisteners again.
|
// evlisteners again.
|
||||||
|
@ -57,23 +54,24 @@ void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
listener_handler->enable_evlistener();
|
h->enable_acceptor();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *sv_ssl_ctx,
|
ListenHandler::ListenHandler(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx,
|
||||||
SSL_CTX *cl_ssl_ctx)
|
SSL_CTX *cl_ssl_ctx)
|
||||||
: evbase_(evbase), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
: loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||||
rate_limit_group_(bufferevent_rate_limit_group_new(
|
// rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||||
evbase, get_config()->worker_rate_limit_cfg)),
|
// evbase, get_config()->worker_rate_limit_cfg)),
|
||||||
evlistener4_(nullptr), evlistener6_(nullptr),
|
worker_stat_(util::make_unique<WorkerStat>()),
|
||||||
evlistener_disable_timerev_(
|
worker_round_robin_cnt_(0) {
|
||||||
evtimer_new(evbase, evlistener_disable_cb, this)),
|
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||||
worker_stat_(util::make_unique<WorkerStat>()), num_worker_shutdown_(0),
|
disable_acceptor_timer_.data = this;
|
||||||
worker_round_robin_cnt_(0) {}
|
}
|
||||||
|
|
||||||
ListenHandler::~ListenHandler() {
|
ListenHandler::~ListenHandler() {
|
||||||
bufferevent_rate_limit_group_free(rate_limit_group_);
|
// bufferevent_rate_limit_group_free(rate_limit_group_);
|
||||||
|
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListenHandler::worker_reopen_log_files() {
|
void ListenHandler::worker_reopen_log_files() {
|
||||||
|
@ -82,68 +80,19 @@ void ListenHandler::worker_reopen_log_files() {
|
||||||
memset(&wev, 0, sizeof(wev));
|
memset(&wev, 0, sizeof(wev));
|
||||||
wev.type = REOPEN_LOG;
|
wev.type = REOPEN_LOG;
|
||||||
|
|
||||||
for (auto &info : workers_) {
|
for (auto &worker : workers_) {
|
||||||
bufferevent_write(info->bev, &wev, sizeof(wev));
|
worker->send(wev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NOTHREADS
|
|
||||||
namespace {
|
|
||||||
void worker_writecb(bufferevent *bev, void *ptr) {
|
|
||||||
auto listener_handler = static_cast<ListenHandler *>(ptr);
|
|
||||||
auto output = bufferevent_get_output(bev);
|
|
||||||
|
|
||||||
if (!worker_config->graceful_shutdown || evbuffer_get_length(output) != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If graceful_shutdown is true and nothing left to send, we sent
|
|
||||||
// graceful shutdown event to worker successfully. The worker is
|
|
||||||
// now doing shutdown.
|
|
||||||
listener_handler->notify_worker_shutdown();
|
|
||||||
|
|
||||||
// Disable bev so that this won' be called accidentally in the
|
|
||||||
// future.
|
|
||||||
util::bev_disable_unless(bev, EV_READ | EV_WRITE);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
#endif // NOTHREADS
|
|
||||||
|
|
||||||
void ListenHandler::create_worker_thread(size_t num) {
|
void ListenHandler::create_worker_thread(size_t num) {
|
||||||
#ifndef NOTHREADS
|
#ifndef NOTHREADS
|
||||||
workers_.resize(0);
|
assert(workers_.size() == 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < num; ++i) {
|
for (size_t i = 0; i < num; ++i) {
|
||||||
int rv;
|
auto worker = util::make_unique<Worker>(sv_ssl_ctx_, cl_ssl_ctx_);
|
||||||
auto info = util::make_unique<WorkerInfo>();
|
worker->run();
|
||||||
rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
|
workers_.push_back(std::move(worker));
|
||||||
info->sv);
|
|
||||||
if (rv == -1) {
|
|
||||||
auto error = errno;
|
|
||||||
LLOG(ERROR, this) << "socketpair() failed: errno=" << error;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
info->sv_ssl_ctx = sv_ssl_ctx_;
|
|
||||||
info->cl_ssl_ctx = cl_ssl_ctx_;
|
|
||||||
|
|
||||||
info->fut =
|
|
||||||
std::async(std::launch::async, start_threaded_worker, info.get());
|
|
||||||
|
|
||||||
auto bev =
|
|
||||||
bufferevent_socket_new(evbase_, info->sv[0], BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
if (!bev) {
|
|
||||||
LLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
|
||||||
for (size_t j = 0; j < 2; ++j) {
|
|
||||||
close(info->sv[j]);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent_setcb(bev, nullptr, worker_writecb, nullptr, this);
|
|
||||||
|
|
||||||
info->bev = bev;
|
|
||||||
|
|
||||||
workers_.push_back(std::move(info));
|
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||||
|
@ -162,7 +111,7 @@ void ListenHandler::join_worker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &worker : workers_) {
|
for (auto &worker : workers_) {
|
||||||
worker->fut.get();
|
worker->wait();
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LLOG(INFO, this) << "Thread #" << n << " joined";
|
LLOG(INFO, this) << "Thread #" << n << " joined";
|
||||||
}
|
}
|
||||||
|
@ -185,29 +134,22 @@ void ListenHandler::graceful_shutdown_worker() {
|
||||||
LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
|
LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto output = bufferevent_get_output(worker->bev);
|
worker->send(wev);
|
||||||
|
|
||||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
|
||||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
int ListenHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
||||||
int addrlen) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
evutil_make_socket_closeonexec(fd);
|
|
||||||
|
|
||||||
if (get_config()->num_worker == 1) {
|
if (get_config()->num_worker == 1) {
|
||||||
|
|
||||||
if (worker_stat_->num_connections >=
|
if (worker_stat_->num_connections >=
|
||||||
get_config()->worker_frontend_connections) {
|
get_config()->worker_frontend_connections) {
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
TLOG(INFO, this) << "Too many connections >="
|
LLOG(INFO, this) << "Too many connections >="
|
||||||
<< get_config()->worker_frontend_connections;
|
<< get_config()->worker_frontend_connections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,9 +157,8 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto client =
|
auto client = ssl::accept_connection(loop_, sv_ssl_ctx_, fd, addr, addrlen,
|
||||||
ssl::accept_connection(evbase_, rate_limit_group_, sv_ssl_ctx_, fd,
|
worker_stat_.get(), &dconn_pool_);
|
||||||
addr, addrlen, worker_stat_.get(), &dconn_pool_);
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
LLOG(ERROR, this) << "ClientHandler creation failed";
|
LLOG(ERROR, this) << "ClientHandler creation failed";
|
||||||
|
|
||||||
|
@ -230,6 +171,7 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t idx = worker_round_robin_cnt_ % workers_.size();
|
size_t idx = worker_round_robin_cnt_ % workers_.size();
|
||||||
++worker_round_robin_cnt_;
|
++worker_round_robin_cnt_;
|
||||||
WorkerEvent wev;
|
WorkerEvent wev;
|
||||||
|
@ -238,140 +180,77 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
||||||
wev.client_fd = fd;
|
wev.client_fd = fd;
|
||||||
memcpy(&wev.client_addr, addr, addrlen);
|
memcpy(&wev.client_addr, addr, addrlen);
|
||||||
wev.client_addrlen = addrlen;
|
wev.client_addrlen = addrlen;
|
||||||
auto output = bufferevent_get_output(workers_[idx]->bev);
|
|
||||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
workers_[idx]->send(wev);
|
||||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
close(fd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_base *ListenHandler::get_evbase() const { return evbase_; }
|
struct ev_loop *ListenHandler::get_loop() const {
|
||||||
|
return loop_;
|
||||||
int ListenHandler::create_http2_session() {
|
|
||||||
int rv;
|
|
||||||
http2session_ = util::make_unique<Http2Session>(evbase_, cl_ssl_ctx_);
|
|
||||||
rv = http2session_->init_notification();
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int ListenHandler::create_http1_connect_blocker() {
|
void ListenHandler::create_http2_session() {
|
||||||
int rv;
|
http2session_ = util::make_unique<Http2Session>(loop_, cl_ssl_ctx_);
|
||||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>();
|
}
|
||||||
|
|
||||||
rv = http1_connect_blocker_->init(evbase_);
|
void ListenHandler::create_http1_connect_blocker() {
|
||||||
|
http1_connect_blocker_ = util::make_unique<ConnectBlocker>(loop_);
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkerStat *ListenHandler::get_worker_stat() const {
|
const WorkerStat *ListenHandler::get_worker_stat() const {
|
||||||
return worker_stat_.get();
|
return worker_stat_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListenHandler::set_evlistener4(evconnlistener *evlistener4) {
|
void ListenHandler::set_acceptor4(std::unique_ptr<AcceptHandler> h) {
|
||||||
evlistener4_ = evlistener4;
|
acceptor4_ = std::move(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
evconnlistener *ListenHandler::get_evlistener4() const { return evlistener4_; }
|
AcceptHandler *ListenHandler::get_acceptor4() const { return acceptor4_.get(); }
|
||||||
|
|
||||||
void ListenHandler::set_evlistener6(evconnlistener *evlistener6) {
|
void ListenHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
|
||||||
evlistener6_ = evlistener6;
|
acceptor6_ = std::move(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
evconnlistener *ListenHandler::get_evlistener6() const { return evlistener6_; }
|
AcceptHandler *ListenHandler::get_acceptor6() const { return acceptor6_.get(); }
|
||||||
|
|
||||||
void ListenHandler::enable_evlistener() {
|
void ListenHandler::enable_acceptor() {
|
||||||
if (evlistener4_) {
|
if (acceptor4_) {
|
||||||
evconnlistener_enable(evlistener4_);
|
acceptor4_->enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evlistener6_) {
|
if (acceptor6_) {
|
||||||
evconnlistener_enable(evlistener6_);
|
acceptor6_->enable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListenHandler::disable_evlistener() {
|
void ListenHandler::disable_acceptor() {
|
||||||
if (evlistener4_) {
|
if (acceptor4_) {
|
||||||
evconnlistener_disable(evlistener4_);
|
acceptor4_->disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evlistener6_) {
|
if (acceptor6_) {
|
||||||
evconnlistener_disable(evlistener6_);
|
acceptor6_->disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListenHandler::disable_evlistener_temporary(const timeval *timeout) {
|
void ListenHandler::disable_acceptor_temporary(ev_tstamp t) {
|
||||||
int rv;
|
if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
|
||||||
|
|
||||||
if (timeout->tv_sec == 0 ||
|
|
||||||
evtimer_pending(evlistener_disable_timerev_, nullptr)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
disable_evlistener();
|
disable_acceptor();
|
||||||
|
|
||||||
rv = evtimer_add(evlistener_disable_timerev_, timeout);
|
ev_timer_set(&disable_acceptor_timer_, t, 0.);
|
||||||
|
ev_timer_start(loop_, &disable_acceptor_timer_);
|
||||||
if (rv < 0) {
|
|
||||||
LOG(ERROR) << "evtimer_add for evlistener_disable_timerev_ failed";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
void perform_accept_pending_connection(ListenHandler *listener_handler,
|
|
||||||
evconnlistener *listener) {
|
|
||||||
if (!listener) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto server_fd = evconnlistener_get_fd(listener);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
sockaddr_union sockaddr;
|
|
||||||
socklen_t addrlen = sizeof(sockaddr);
|
|
||||||
|
|
||||||
auto fd = accept(server_fd, &sockaddr.sa, &addrlen);
|
|
||||||
|
|
||||||
if (fd == -1) {
|
|
||||||
switch (errno) {
|
|
||||||
case EINTR:
|
|
||||||
case ENETDOWN:
|
|
||||||
case EPROTO:
|
|
||||||
case ENOPROTOOPT:
|
|
||||||
case EHOSTDOWN:
|
|
||||||
#ifdef ENONET
|
|
||||||
case ENONET:
|
|
||||||
#endif // ENONET
|
|
||||||
case EHOSTUNREACH:
|
|
||||||
case EOPNOTSUPP:
|
|
||||||
case ENETUNREACH:
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
evutil_make_socket_nonblocking(fd);
|
|
||||||
|
|
||||||
listener_handler->accept_connection(fd, &sockaddr.sa, addrlen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void ListenHandler::accept_pending_connection() {
|
void ListenHandler::accept_pending_connection() {
|
||||||
perform_accept_pending_connection(this, evlistener4_);
|
if (acceptor4_) {
|
||||||
perform_accept_pending_connection(this, evlistener6_);
|
acceptor4_->accept_connection();
|
||||||
}
|
}
|
||||||
|
if (acceptor6_) {
|
||||||
void ListenHandler::notify_worker_shutdown() {
|
acceptor6_->accept_connection();
|
||||||
if (++num_worker_shutdown_ == workers_.size()) {
|
|
||||||
event_base_loopbreak(evbase_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,61 +32,48 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#ifndef NOTHREADS
|
|
||||||
#include <future>
|
|
||||||
#endif // NOTHREADS
|
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
#include <event2/listener.h>
|
|
||||||
|
|
||||||
#include "shrpx_downstream_connection_pool.h"
|
#include "shrpx_downstream_connection_pool.h"
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
struct WorkerInfo {
|
|
||||||
#ifndef NOTHREADS
|
|
||||||
std::future<void> fut;
|
|
||||||
#endif // NOTHREADS
|
|
||||||
SSL_CTX *sv_ssl_ctx;
|
|
||||||
SSL_CTX *cl_ssl_ctx;
|
|
||||||
bufferevent *bev;
|
|
||||||
int sv[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
class Http2Session;
|
class Http2Session;
|
||||||
class ConnectBlocker;
|
class ConnectBlocker;
|
||||||
|
class AcceptHandler;
|
||||||
|
class Worker;
|
||||||
struct WorkerStat;
|
struct WorkerStat;
|
||||||
|
|
||||||
|
// TODO should be renamed as ConnectionHandler
|
||||||
class ListenHandler {
|
class ListenHandler {
|
||||||
public:
|
public:
|
||||||
ListenHandler(event_base *evbase, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
ListenHandler(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
||||||
~ListenHandler();
|
~ListenHandler();
|
||||||
int accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen);
|
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
||||||
void create_worker_thread(size_t num);
|
void create_worker_thread(size_t num);
|
||||||
void worker_reopen_log_files();
|
void worker_reopen_log_files();
|
||||||
event_base *get_evbase() const;
|
struct ev_loop *get_loop() const;
|
||||||
int create_http2_session();
|
void create_http2_session();
|
||||||
int create_http1_connect_blocker();
|
void create_http1_connect_blocker();
|
||||||
const WorkerStat *get_worker_stat() const;
|
const WorkerStat *get_worker_stat() const;
|
||||||
void set_evlistener4(evconnlistener *evlistener4);
|
void set_acceptor4(std::unique_ptr<AcceptHandler> h);
|
||||||
evconnlistener *get_evlistener4() const;
|
AcceptHandler *get_acceptor4() const;
|
||||||
void set_evlistener6(evconnlistener *evlistener6);
|
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
|
||||||
evconnlistener *get_evlistener6() const;
|
AcceptHandler *get_acceptor6() const;
|
||||||
void enable_evlistener();
|
void enable_acceptor();
|
||||||
void disable_evlistener();
|
void disable_acceptor();
|
||||||
void disable_evlistener_temporary(const timeval *timeout);
|
void disable_acceptor_temporary(ev_tstamp t);
|
||||||
void accept_pending_connection();
|
void accept_pending_connection();
|
||||||
void graceful_shutdown_worker();
|
void graceful_shutdown_worker();
|
||||||
void join_worker();
|
void join_worker();
|
||||||
void notify_worker_shutdown();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DownstreamConnectionPool dconn_pool_;
|
DownstreamConnectionPool dconn_pool_;
|
||||||
std::vector<std::unique_ptr<WorkerInfo>> workers_;
|
std::vector<std::unique_ptr<Worker>> workers_;
|
||||||
event_base *evbase_;
|
struct ev_loop *loop_;
|
||||||
// The frontend server SSL_CTX
|
// The frontend server SSL_CTX
|
||||||
SSL_CTX *sv_ssl_ctx_;
|
SSL_CTX *sv_ssl_ctx_;
|
||||||
// The backend server SSL_CTX
|
// The backend server SSL_CTX
|
||||||
|
@ -95,12 +82,11 @@ private:
|
||||||
// multi-threaded case, see shrpx_worker.cc.
|
// multi-threaded case, see shrpx_worker.cc.
|
||||||
std::unique_ptr<Http2Session> http2session_;
|
std::unique_ptr<Http2Session> http2session_;
|
||||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
||||||
bufferevent_rate_limit_group *rate_limit_group_;
|
// bufferevent_rate_limit_group *rate_limit_group_;
|
||||||
evconnlistener *evlistener4_;
|
std::unique_ptr<AcceptHandler> acceptor4_;
|
||||||
evconnlistener *evlistener6_;
|
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||||
event *evlistener_disable_timerev_;
|
ev_timer disable_acceptor_timer_;
|
||||||
std::unique_ptr<WorkerStat> worker_stat_;
|
std::unique_ptr<WorkerStat> worker_stat_;
|
||||||
size_t num_worker_shutdown_;
|
|
||||||
unsigned int worker_round_robin_cnt_;
|
unsigned int worker_round_robin_cnt_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
|
||||||
case SHRPX_LOGF_HTTP:
|
case SHRPX_LOGF_HTTP:
|
||||||
if (downstream) {
|
if (downstream) {
|
||||||
auto hd = downstream->get_request_header(lf.value.get());
|
auto hd = downstream->get_request_header(lf.value.get());
|
||||||
if (hd != std::end(downstream->get_request_headers())) {
|
if (hd) {
|
||||||
std::tie(p, avail) = copy((*hd).value.c_str(), avail, p);
|
std::tie(p, avail) = copy((*hd).value.c_str(), avail, p);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,9 @@ class Downstream;
|
||||||
#define LLOG(SEVERITY, LISTEN) \
|
#define LLOG(SEVERITY, LISTEN) \
|
||||||
(Log(SEVERITY, __FILE__, __LINE__) << "[LISTEN:" << LISTEN << "] ")
|
(Log(SEVERITY, __FILE__, __LINE__) << "[LISTEN:" << LISTEN << "] ")
|
||||||
|
|
||||||
// ThreadEventReceiver log
|
// Worker log
|
||||||
#define TLOG(SEVERITY, THREAD_RECV) \
|
#define WLOG(SEVERITY, WORKER) \
|
||||||
(Log(SEVERITY, __FILE__, __LINE__) << "[THREAD_RECV:" << THREAD_RECV << "]" \
|
(Log(SEVERITY, __FILE__, __LINE__) << "[WORKER:" << WORKER << "] ")
|
||||||
" ")
|
|
||||||
|
|
||||||
// ClientHandler log
|
// ClientHandler log
|
||||||
#define CLOG(SEVERITY, CLIENT_HANDLER) \
|
#define CLOG(SEVERITY, CLIENT_HANDLER) \
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "shrpx_rate_limit.h"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
|
auto r = static_cast<RateLimit *>(w->data);
|
||||||
|
r->regen();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst)
|
||||||
|
: w_(w), loop_(loop), rate_(rate), burst_(burst), avail_(burst),
|
||||||
|
startw_req_(false) {
|
||||||
|
ev_timer_init(&t_, regencb, 0., 1.);
|
||||||
|
t_.data = this;
|
||||||
|
if (rate_ > 0) {
|
||||||
|
ev_timer_start(loop_, &t_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimit::~RateLimit() {
|
||||||
|
ev_timer_stop(loop_, &t_);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t RateLimit::avail() const {
|
||||||
|
if (rate_ == 0) {
|
||||||
|
return std::numeric_limits<ssize_t>::max();
|
||||||
|
}
|
||||||
|
return avail_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimit::drain(size_t n) {
|
||||||
|
if (rate_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
n = std::min(avail_, n);
|
||||||
|
avail_ -= n;
|
||||||
|
if (avail_ == 0) {
|
||||||
|
ev_io_stop(loop_, w_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimit::regen() {
|
||||||
|
if (rate_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (avail_ + rate_ > burst_) {
|
||||||
|
avail_ = burst_;
|
||||||
|
} else {
|
||||||
|
avail_ += rate_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avail_ > 0 && startw_req_) {
|
||||||
|
ev_io_start(loop_, w_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimit::startw() {
|
||||||
|
startw_req_ = true;
|
||||||
|
if (rate_ == 0 || avail_ > 0) {
|
||||||
|
ev_io_start(loop_, w_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RateLimit::stopw() {
|
||||||
|
startw_req_ = false;
|
||||||
|
ev_io_stop(loop_, w_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace shrpx
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* nghttp2 - HTTP/2 C Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Tatsuhiro Tsujikawa
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* "Software"), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#ifndef SHRPX_RATE_LIMIT_H
|
||||||
|
#define SHRPX_RATE_LIMIT_H
|
||||||
|
|
||||||
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
|
namespace shrpx {
|
||||||
|
|
||||||
|
class RateLimit {
|
||||||
|
public:
|
||||||
|
RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst);
|
||||||
|
~RateLimit();
|
||||||
|
size_t avail() const;
|
||||||
|
void drain(size_t n);
|
||||||
|
void regen();
|
||||||
|
void startw();
|
||||||
|
void stopw();
|
||||||
|
private:
|
||||||
|
ev_io *w_;
|
||||||
|
ev_timer t_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
|
size_t rate_;
|
||||||
|
size_t burst_;
|
||||||
|
size_t avail_;
|
||||||
|
bool startw_req_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace shrpx
|
||||||
|
|
||||||
|
#endif // SHRPX_RATE_LIMIT_H
|
|
@ -44,48 +44,45 @@ using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
namespace {
|
|
||||||
const size_t OUTBUF_MAX_THRES = 16 * 1024;
|
|
||||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
|
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
|
||||||
int flags, void *user_data) {
|
int flags, void *user_data) {
|
||||||
int rv;
|
|
||||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||||
auto handler = upstream->get_client_handler();
|
auto handler = upstream->get_client_handler();
|
||||||
|
auto wb = handler->get_wb();
|
||||||
|
|
||||||
// Check buffer length and return WOULDBLOCK if it is large enough.
|
if (wb->wleft() == 0) {
|
||||||
if (handler->get_outbuf_length() + upstream->sendbuf.get_buflen() >=
|
|
||||||
OUTBUF_MAX_THRES) {
|
|
||||||
return SPDYLAY_ERR_WOULDBLOCK;
|
return SPDYLAY_ERR_WOULDBLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = upstream->sendbuf.add(data, len);
|
auto nread = wb->write(data, len);
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, upstream) << "evbuffer_add() failed";
|
handler->update_warmup_writelen(nread);
|
||||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
return nread;
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
ssize_t recv_callback(spdylay_session *session, uint8_t *data, size_t len,
|
ssize_t recv_callback(spdylay_session *session, uint8_t *buf, size_t len,
|
||||||
int flags, void *user_data) {
|
int flags, void *user_data) {
|
||||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||||
auto handler = upstream->get_client_handler();
|
auto handler = upstream->get_client_handler();
|
||||||
auto bev = handler->get_bev();
|
auto rb = handler->get_rb();
|
||||||
auto input = bufferevent_get_input(bev);
|
const void *data;
|
||||||
int nread = evbuffer_remove(input, data, len);
|
size_t nread;
|
||||||
if (nread == -1) {
|
|
||||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
std::tie(data, nread) = rb->get();
|
||||||
} else if (nread == 0) {
|
if (nread == 0) {
|
||||||
return SPDYLAY_ERR_WOULDBLOCK;
|
return SPDYLAY_ERR_WOULDBLOCK;
|
||||||
} else {
|
|
||||||
return nread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nread = std::min(nread, len);
|
||||||
|
|
||||||
|
memcpy(buf, data, nread);
|
||||||
|
rb->drain(nread);
|
||||||
|
|
||||||
|
return nread;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -155,47 +152,36 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
||||||
auto downstream = upstream->add_pending_downstream(
|
auto downstream = upstream->add_pending_downstream(
|
||||||
frame->syn_stream.stream_id, frame->syn_stream.pri);
|
frame->syn_stream.stream_id, frame->syn_stream.pri);
|
||||||
|
|
||||||
downstream->init_upstream_timer();
|
|
||||||
downstream->reset_upstream_rtimer();
|
downstream->reset_upstream_rtimer();
|
||||||
downstream->init_response_body_buf();
|
|
||||||
|
|
||||||
auto nv = frame->syn_stream.nv;
|
auto nv = frame->syn_stream.nv;
|
||||||
const char *path = nullptr;
|
|
||||||
const char *scheme = nullptr;
|
|
||||||
const char *host = nullptr;
|
|
||||||
const char *method = nullptr;
|
|
||||||
|
|
||||||
for (size_t i = 0; nv[i]; i += 2) {
|
for (size_t i = 0; nv[i]; i += 2) {
|
||||||
if (strcmp(nv[i], ":path") == 0) {
|
downstream->add_request_header(nv[i], nv[i + 1]);
|
||||||
path = nv[i + 1];
|
|
||||||
} else if (strcmp(nv[i], ":scheme") == 0) {
|
|
||||||
scheme = nv[i + 1];
|
|
||||||
} else if (strcmp(nv[i], ":method") == 0) {
|
|
||||||
method = nv[i + 1];
|
|
||||||
} else if (strcmp(nv[i], ":host") == 0) {
|
|
||||||
host = nv[i + 1];
|
|
||||||
} else if (nv[i][0] != ':') {
|
|
||||||
downstream->add_request_header(nv[i], nv[i + 1]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->normalize_request_headers();
|
downstream->index_request_headers();
|
||||||
|
|
||||||
bool is_connect = method && strcmp("CONNECT", method) == 0;
|
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||||
if (!path || !host || !method || http2::lws(host) || http2::lws(path) ||
|
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||||
http2::lws(method) ||
|
auto host = downstream->get_request_header(http2::HD__HOST);
|
||||||
(!is_connect && (!scheme || http2::lws(scheme)))) {
|
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||||
|
|
||||||
|
bool is_connect = method && "CONNECT" == method->value;
|
||||||
|
if (!path || !host || !method || !http2::non_empty_value(host) ||
|
||||||
|
!http2::non_empty_value(path) || !http2::non_empty_value(method) ||
|
||||||
|
(!is_connect && (!scheme || !http2::non_empty_value(scheme)))) {
|
||||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->set_request_method(method);
|
downstream->set_request_method(method->value);
|
||||||
if (is_connect) {
|
if (is_connect) {
|
||||||
downstream->set_request_http2_authority(path);
|
downstream->set_request_http2_authority(path->value);
|
||||||
} else {
|
} else {
|
||||||
downstream->set_request_http2_scheme(scheme);
|
downstream->set_request_http2_scheme(scheme->value);
|
||||||
downstream->set_request_http2_authority(host);
|
downstream->set_request_http2_authority(host->value);
|
||||||
downstream->set_request_path(path);
|
downstream->set_request_path(path->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->set_request_start_time(
|
downstream->set_request_start_time(
|
||||||
|
@ -404,13 +390,14 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
||||||
: downstream_queue_(get_config()->http2_proxy
|
: downstream_queue_(
|
||||||
? get_config()->downstream_connections_per_host
|
get_config()->http2_proxy
|
||||||
: 0),
|
? get_config()->downstream_connections_per_host
|
||||||
|
: get_config()->downstream_proto == PROTO_HTTP
|
||||||
|
? get_config()->downstream_connections_per_frontend
|
||||||
|
: 0,
|
||||||
|
!get_config()->http2_proxy),
|
||||||
handler_(handler), session_(nullptr) {
|
handler_(handler), session_(nullptr) {
|
||||||
// handler->set_bev_cb(spdy_readcb, 0, spdy_eventcb);
|
|
||||||
reset_timeouts();
|
|
||||||
|
|
||||||
spdylay_session_callbacks callbacks;
|
spdylay_session_callbacks callbacks;
|
||||||
memset(&callbacks, 0, sizeof(callbacks));
|
memset(&callbacks, 0, sizeof(callbacks));
|
||||||
callbacks.send_callback = send_callback;
|
callbacks.send_callback = send_callback;
|
||||||
|
@ -461,8 +448,10 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
||||||
assert(rv == 0);
|
assert(rv == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Maybe call from outside?
|
handler_->reset_upstream_read_timeout(
|
||||||
send();
|
get_config()->http2_upstream_read_timeout);
|
||||||
|
|
||||||
|
handler_->signal_write();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); }
|
SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); }
|
||||||
|
@ -478,18 +467,15 @@ int SpdyUpstream::on_read() {
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
return send();
|
|
||||||
|
handler_->signal_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdyUpstream::on_write() { return send(); }
|
|
||||||
|
|
||||||
// After this function call, downstream may be deleted.
|
// After this function call, downstream may be deleted.
|
||||||
int SpdyUpstream::send() {
|
int SpdyUpstream::on_write() {
|
||||||
int rv = 0;
|
int rv = 0;
|
||||||
uint8_t buf[16384];
|
|
||||||
|
|
||||||
sendbuf.reset(bufferevent_get_output(handler_->get_bev()), buf, sizeof(buf),
|
|
||||||
handler_->get_write_limit());
|
|
||||||
|
|
||||||
rv = spdylay_session_send(session_);
|
rv = spdylay_session_send(session_);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
|
@ -498,17 +484,9 @@ int SpdyUpstream::send() {
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = sendbuf.flush();
|
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler_->update_warmup_writelen(sendbuf.get_writelen());
|
|
||||||
|
|
||||||
if (spdylay_session_want_read(session_) == 0 &&
|
if (spdylay_session_want_read(session_) == 0 &&
|
||||||
spdylay_session_want_write(session_) == 0 &&
|
spdylay_session_want_write(session_) == 0 &&
|
||||||
handler_->get_outbuf_length() == 0) {
|
handler_->get_wb()->rleft() == 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
ULOG(INFO, this) << "No more read/write for this SPDY session";
|
ULOG(INFO, this) << "No more read/write for this SPDY session";
|
||||||
}
|
}
|
||||||
|
@ -517,23 +495,19 @@ int SpdyUpstream::send() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdyUpstream::on_event() { return 0; }
|
|
||||||
|
|
||||||
ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; }
|
ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; }
|
||||||
|
|
||||||
namespace {
|
int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||||
void spdy_downstream_readcb(bufferevent *bev, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
// If upstream SPDY stream was closed, we just close downstream,
|
// If upstream SPDY stream was closed, we just close downstream,
|
||||||
// because there is no consumer now. Downstream connection is also
|
// because there is no consumer now. Downstream connection is also
|
||||||
// closed in this case.
|
// closed in this case.
|
||||||
upstream->remove_downstream(downstream);
|
remove_downstream(downstream);
|
||||||
// downstrea was deleted
|
// downstrea was deleted
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
||||||
|
@ -541,178 +515,148 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr) {
|
||||||
// RST_STREAM to the upstream and delete downstream connection
|
// RST_STREAM to the upstream and delete downstream connection
|
||||||
// here. Deleting downstream will be taken place at
|
// here. Deleting downstream will be taken place at
|
||||||
// on_stream_close_callback.
|
// on_stream_close_callback.
|
||||||
upstream->rst_stream(downstream,
|
rst_stream(downstream,
|
||||||
infer_upstream_rst_stream_status_code(
|
infer_upstream_rst_stream_status_code(
|
||||||
downstream->get_response_rst_stream_error_code()));
|
downstream->get_response_rst_stream_error_code()));
|
||||||
downstream->pop_downstream_connection();
|
downstream->pop_downstream_connection();
|
||||||
dconn = nullptr;
|
dconn = nullptr;
|
||||||
} else {
|
} else {
|
||||||
auto rv = downstream->on_read();
|
auto rv = downstream->on_read();
|
||||||
|
if (rv == DownstreamConnection::ERR_EOF) {
|
||||||
|
return downstream_eof(dconn);
|
||||||
|
}
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (rv != DownstreamConnection::ERR_NET) {
|
||||||
DCLOG(INFO, dconn) << "HTTP parser failure";
|
if (LOG_ENABLED(INFO)) {
|
||||||
}
|
DCLOG(INFO, dconn) << "HTTP parser failure";
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
|
||||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
|
||||||
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
|
||||||
// If response was completed, then don't issue RST_STREAM
|
|
||||||
if (upstream->error_reply(downstream, 502) != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
// Clearly, we have to close downstream connection on http parser
|
|
||||||
// failure.
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
dconn = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (upstream->send() != 0) {
|
|
||||||
delete upstream->get_client_handler();
|
handler_->signal_write();
|
||||||
return;
|
|
||||||
}
|
|
||||||
// At this point, downstream may be deleted.
|
// At this point, downstream may be deleted.
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
return 0;
|
||||||
void spdy_downstream_writecb(bufferevent *bev, void *ptr) {
|
}
|
||||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
|
||||||
return;
|
int SpdyUpstream::downstream_write(DownstreamConnection *dconn) {
|
||||||
|
int rv;
|
||||||
|
rv = dconn->on_write();
|
||||||
|
if (rv == DownstreamConnection::ERR_NET) {
|
||||||
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||||
}
|
}
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
if (rv != 0) {
|
||||||
dconn->on_write();
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) {
|
||||||
void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
|
|
||||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
|
||||||
auto downstream = dconn->get_downstream();
|
auto downstream = dconn->get_downstream();
|
||||||
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
|
||||||
|
|
||||||
if (events & BEV_EVENT_CONNECTED) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
|
||||||
DCLOG(INFO, dconn) << "Connection established. stream_id="
|
}
|
||||||
<< downstream->get_stream_id();
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
}
|
// If stream was closed already, we don't need to send reply at
|
||||||
int fd = bufferevent_getfd(bev);
|
// the first place. We can delete downstream.
|
||||||
int val = 1;
|
remove_downstream(downstream);
|
||||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
|
// downstream was deleted
|
||||||
sizeof(val)) == -1) {
|
|
||||||
DCLOG(WARN, dconn) << "Setting option TCP_NODELAY failed: errno="
|
return 0;
|
||||||
<< errno;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events & BEV_EVENT_EOF) {
|
// Delete downstream connection. If we don't delete it here, it will
|
||||||
|
// be pooled in on_stream_close_callback.
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
// dconn was deleted
|
||||||
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
|
ULOG(INFO, this) << "Downstream body was ended by EOF";
|
||||||
}
|
}
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
// If stream was closed already, we don't need to send reply at
|
|
||||||
// the first place. We can delete downstream.
|
|
||||||
upstream->remove_downstream(downstream);
|
|
||||||
// downstrea was deleted
|
|
||||||
|
|
||||||
return;
|
// 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.
|
||||||
|
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 (error_reply(downstream, 502) != 0) {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
}
|
||||||
|
handler_->signal_write();
|
||||||
|
// At this point, downstream may be deleted.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete downstream connection. If we don't delete it here, it
|
int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||||
// will be pooled in on_stream_close_callback.
|
auto downstream = dconn->get_downstream();
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
dconn = nullptr;
|
if (LOG_ENABLED(INFO)) {
|
||||||
// downstream wil be deleted in on_stream_close_callback.
|
if (events & Downstream::EVENT_ERROR) {
|
||||||
|
DCLOG(INFO, dconn) << "Downstream network/general error";
|
||||||
|
} else {
|
||||||
|
DCLOG(INFO, dconn) << "Timeout";
|
||||||
|
}
|
||||||
|
if (downstream->get_upgraded()) {
|
||||||
|
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||||
|
remove_downstream(downstream);
|
||||||
|
// downstream was deleted
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete downstream connection. If we don't delete it here, it will
|
||||||
|
// be pooled in on_stream_close_callback.
|
||||||
|
downstream->pop_downstream_connection();
|
||||||
|
// dconn was deleted
|
||||||
|
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()) {
|
||||||
|
rst_stream(downstream, NGHTTP2_NO_ERROR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||||
// Server may indicate the end of the request by EOF
|
if (downstream->get_upgraded()) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
on_downstream_body_complete(downstream);
|
||||||
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
|
||||||
if (events & BEV_EVENT_ERROR) {
|
|
||||||
DCLOG(INFO, dconn) << "Downstream network error: "
|
|
||||||
<< evutil_socket_error_to_string(
|
|
||||||
EVUTIL_SOCKET_ERROR());
|
|
||||||
} else {
|
} else {
|
||||||
DCLOG(INFO, dconn) << "Timeout";
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||||
}
|
|
||||||
if (downstream->get_upgraded()) {
|
|
||||||
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
|
||||||
upstream->remove_downstream(downstream);
|
|
||||||
// downstrea was deleted
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete downstream connection. If we don't delete it here, it
|
|
||||||
// will be pooled in on_stream_close_callback.
|
|
||||||
downstream->pop_downstream_connection();
|
|
||||||
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 {
|
} else {
|
||||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
unsigned int status;
|
||||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
if (events & Downstream::EVENT_TIMEOUT) {
|
||||||
|
status = 504;
|
||||||
} else {
|
} else {
|
||||||
unsigned int status;
|
status = 502;
|
||||||
if (events & BEV_EVENT_TIMEOUT) {
|
}
|
||||||
status = 504;
|
if (error_reply(downstream, status) != 0) {
|
||||||
} else {
|
return -1;
|
||||||
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) {
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
delete upstream->get_client_handler();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// At this point, downstream may be deleted.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
handler_->signal_write();
|
||||||
|
// At this point, downstream may be deleted.
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) {
|
int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
@ -735,7 +679,7 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
spdylay_data_source *source, void *user_data) {
|
spdylay_data_source *source, void *user_data) {
|
||||||
auto downstream = static_cast<Downstream *>(source->ptr);
|
auto downstream = static_cast<Downstream *>(source->ptr);
|
||||||
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
||||||
auto body = downstream->get_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
auto handler = upstream->get_client_handler();
|
auto handler = upstream->get_client_handler();
|
||||||
assert(body);
|
assert(body);
|
||||||
|
|
||||||
|
@ -749,11 +693,9 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
length = std::min(length, static_cast<size_t>(limit - 9));
|
length = std::min(length, static_cast<size_t>(limit - 9));
|
||||||
}
|
}
|
||||||
|
|
||||||
int nread = evbuffer_remove(body, buf, length);
|
auto nread = body->remove(buf, length);
|
||||||
if (nread == -1) {
|
auto body_empty = body->rleft() == 0;
|
||||||
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
|
|
||||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
|
||||||
}
|
|
||||||
if (nread == 0 &&
|
if (nread == 0 &&
|
||||||
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||||
if (!downstream->get_upgraded()) {
|
if (!downstream->get_upgraded()) {
|
||||||
|
@ -770,10 +712,10 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_get_length(body) > 0) {
|
if (body_empty) {
|
||||||
downstream->reset_upstream_wtimer();
|
|
||||||
} else {
|
|
||||||
downstream->disable_upstream_wtimer();
|
downstream->disable_upstream_wtimer();
|
||||||
|
} else {
|
||||||
|
downstream->reset_upstream_wtimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) {
|
if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) {
|
||||||
|
@ -797,13 +739,8 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
||||||
int rv;
|
int rv;
|
||||||
auto html = http::create_error_html(status_code);
|
auto html = http::create_error_html(status_code);
|
||||||
downstream->set_response_http_status(status_code);
|
downstream->set_response_http_status(status_code);
|
||||||
downstream->init_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
auto body = downstream->get_response_body_buf();
|
body->append(html.c_str(), html.size());
|
||||||
rv = evbuffer_add(body, html.c_str(), html.size());
|
|
||||||
if (rv == -1) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||||
|
|
||||||
spdylay_data_provider data_prd;
|
spdylay_data_provider data_prd;
|
||||||
|
@ -830,18 +767,6 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferevent_data_cb SpdyUpstream::get_downstream_readcb() {
|
|
||||||
return spdy_downstream_readcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent_data_cb SpdyUpstream::get_downstream_writecb() {
|
|
||||||
return spdy_downstream_writecb;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferevent_event_cb SpdyUpstream::get_downstream_eventcb() {
|
|
||||||
return spdy_downstream_eventcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id,
|
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id,
|
||||||
int32_t priority) {
|
int32_t priority) {
|
||||||
auto downstream = util::make_unique<Downstream>(this, stream_id, priority);
|
auto downstream = util::make_unique<Downstream>(this, stream_id, priority);
|
||||||
|
@ -863,6 +788,9 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) {
|
||||||
if (next_downstream) {
|
if (next_downstream) {
|
||||||
initiate_downstream(std::move(next_downstream));
|
initiate_downstream(std::move(next_downstream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mcpool_.shrink((downstream_queue_.get_active_downstreams().size() + 1) *
|
||||||
|
65536);
|
||||||
}
|
}
|
||||||
|
|
||||||
Downstream *SpdyUpstream::find_downstream(int32_t stream_id) {
|
Downstream *SpdyUpstream::find_downstream(int32_t stream_id) {
|
||||||
|
@ -886,10 +814,10 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
DLOG(INFO, downstream) << "HTTP response header completed";
|
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||||
}
|
}
|
||||||
downstream->normalize_response_headers();
|
|
||||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||||
!get_config()->no_location_rewrite) {
|
!get_config()->no_location_rewrite) {
|
||||||
downstream->rewrite_norm_location_response_header(
|
downstream->rewrite_location_response_header(
|
||||||
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
get_client_handler()->get_upstream_scheme(), get_config()->port);
|
||||||
}
|
}
|
||||||
size_t nheader = downstream->get_response_headers().size();
|
size_t nheader = downstream->get_response_headers().size();
|
||||||
|
@ -906,30 +834,39 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
nv[hdidx++] = ":version";
|
nv[hdidx++] = ":version";
|
||||||
nv[hdidx++] = "HTTP/1.1";
|
nv[hdidx++] = "HTTP/1.1";
|
||||||
for (auto &hd : downstream->get_response_headers()) {
|
for (auto &hd : downstream->get_response_headers()) {
|
||||||
if (hd.name.empty() || hd.name.c_str()[0] == ':' ||
|
if (hd.name.empty() || hd.name.c_str()[0] == ':') {
|
||||||
util::strieq(hd.name.c_str(), "transfer-encoding") ||
|
continue;
|
||||||
util::strieq(hd.name.c_str(), "keep-alive") || // HTTP/1.0?
|
|
||||||
util::strieq(hd.name.c_str(), "connection") ||
|
|
||||||
util::strieq(hd.name.c_str(), "proxy-connection")) {
|
|
||||||
// These are ignored
|
|
||||||
} else if (!get_config()->no_via && util::strieq(hd.name.c_str(), "via")) {
|
|
||||||
via_value = hd.value;
|
|
||||||
} else if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
|
||||||
util::strieq(hd.name.c_str(), "server")) {
|
|
||||||
// Rewrite server header field later
|
|
||||||
} else {
|
|
||||||
nv[hdidx++] = hd.name.c_str();
|
|
||||||
nv[hdidx++] = hd.value.c_str();
|
|
||||||
}
|
}
|
||||||
|
auto token = http2::lookup_token(hd.name);
|
||||||
|
switch (token) {
|
||||||
|
case http2::HD_CONNECTION:
|
||||||
|
case http2::HD_KEEP_ALIVE:
|
||||||
|
case http2::HD_PROXY_CONNECTION:
|
||||||
|
case http2::HD_TRANSFER_ENCODING:
|
||||||
|
case http2::HD_VIA:
|
||||||
|
case http2::HD_SERVER:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nv[hdidx++] = hd.name.c_str();
|
||||||
|
nv[hdidx++] = hd.value.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||||
nv[hdidx++] = "server";
|
nv[hdidx++] = "server";
|
||||||
nv[hdidx++] = get_config()->server_name;
|
nv[hdidx++] = get_config()->server_name;
|
||||||
|
} else {
|
||||||
|
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||||
|
if (server) {
|
||||||
|
nv[hdidx++] = "server";
|
||||||
|
nv[hdidx++] = server->value.c_str();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get_config()->no_via) {
|
if (!get_config()->no_via) {
|
||||||
if (!via_value.empty()) {
|
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||||
|
if (via) {
|
||||||
|
via_value = via->value;
|
||||||
via_value += ", ";
|
via_value += ", ";
|
||||||
}
|
}
|
||||||
via_value += http::create_via_header_value(
|
via_value += http::create_via_header_value(
|
||||||
|
@ -972,12 +909,8 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
int SpdyUpstream::on_downstream_body(Downstream *downstream,
|
int SpdyUpstream::on_downstream_body(Downstream *downstream,
|
||||||
const uint8_t *data, size_t len,
|
const uint8_t *data, size_t len,
|
||||||
bool flush) {
|
bool flush) {
|
||||||
auto body = downstream->get_response_body_buf();
|
auto body = downstream->get_response_buf();
|
||||||
int rv = evbuffer_add(body, data, len);
|
body->append(data, len);
|
||||||
if (rv != 0) {
|
|
||||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flush) {
|
if (flush) {
|
||||||
spdylay_session_resume_data(session_, downstream->get_stream_id());
|
spdylay_session_resume_data(session_, downstream->get_stream_id());
|
||||||
|
@ -985,16 +918,6 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream,
|
||||||
downstream->ensure_upstream_wtimer();
|
downstream->ensure_upstream_wtimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evbuffer_get_length(body) >= INBUF_MAX_THRES) {
|
|
||||||
if (!flush) {
|
|
||||||
spdylay_session_resume_data(session_, downstream->get_stream_id());
|
|
||||||
|
|
||||||
downstream->ensure_upstream_wtimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
downstream->pause_read(SHRPX_NO_BUFFER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,7 +950,8 @@ int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
downstream->dec_request_datalen(consumed);
|
downstream->dec_request_datalen(consumed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return send();
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
||||||
|
@ -1040,7 +964,8 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return send();
|
handler_->signal_write();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SpdyUpstream::consume(int32_t stream_id, size_t len) {
|
int SpdyUpstream::consume(int32_t stream_id, size_t len) {
|
||||||
|
@ -1068,11 +993,6 @@ int SpdyUpstream::on_timeout(Downstream *downstream) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpdyUpstream::reset_timeouts() {
|
|
||||||
handler_->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout,
|
|
||||||
&get_config()->upstream_write_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpdyUpstream::on_handler_delete() {
|
void SpdyUpstream::on_handler_delete() {
|
||||||
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
||||||
if (ent.second->accesslog_ready()) {
|
if (ent.second->accesslog_ready()) {
|
||||||
|
@ -1107,12 +1027,11 @@ int SpdyUpstream::on_downstream_reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = send();
|
handler_->signal_write();
|
||||||
if (rv != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemchunkPool4K *SpdyUpstream::get_mcpool() { return &mcpool_; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -29,12 +29,14 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <ev.h>
|
||||||
|
|
||||||
#include <spdylay/spdylay.h>
|
#include <spdylay/spdylay.h>
|
||||||
|
|
||||||
#include "shrpx_upstream.h"
|
#include "shrpx_upstream.h"
|
||||||
#include "shrpx_downstream_queue.h"
|
#include "shrpx_downstream_queue.h"
|
||||||
|
#include "memchunk.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
@ -46,15 +48,14 @@ public:
|
||||||
virtual ~SpdyUpstream();
|
virtual ~SpdyUpstream();
|
||||||
virtual int on_read();
|
virtual int on_read();
|
||||||
virtual int on_write();
|
virtual int on_write();
|
||||||
virtual int on_event();
|
|
||||||
virtual int on_timeout(Downstream *downstream);
|
virtual int on_timeout(Downstream *downstream);
|
||||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||||
unsigned int status_code);
|
unsigned int status_code);
|
||||||
int send();
|
|
||||||
virtual ClientHandler *get_client_handler() const;
|
virtual ClientHandler *get_client_handler() const;
|
||||||
virtual bufferevent_data_cb get_downstream_readcb();
|
virtual int downstream_read(DownstreamConnection *dconn);
|
||||||
virtual bufferevent_data_cb get_downstream_writecb();
|
virtual int downstream_write(DownstreamConnection *dconn);
|
||||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||||
|
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||||
Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
|
Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
|
||||||
void remove_downstream(Downstream *downstream);
|
void remove_downstream(Downstream *downstream);
|
||||||
Downstream *find_downstream(int32_t stream_id);
|
Downstream *find_downstream(int32_t stream_id);
|
||||||
|
@ -76,7 +77,7 @@ public:
|
||||||
virtual void on_handler_delete();
|
virtual void on_handler_delete();
|
||||||
virtual int on_downstream_reset();
|
virtual int on_downstream_reset();
|
||||||
|
|
||||||
virtual void reset_timeouts();
|
virtual MemchunkPool4K *get_mcpool();
|
||||||
|
|
||||||
bool get_flow_control() const;
|
bool get_flow_control() const;
|
||||||
|
|
||||||
|
@ -85,9 +86,9 @@ public:
|
||||||
void start_downstream(Downstream *downstream);
|
void start_downstream(Downstream *downstream);
|
||||||
void initiate_downstream(std::unique_ptr<Downstream> downstream);
|
void initiate_downstream(std::unique_ptr<Downstream> downstream);
|
||||||
|
|
||||||
nghttp2::util::EvbufferBuffer sendbuf;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// must be put before downstream_queue_
|
||||||
|
MemchunkPool4K mcpool_;
|
||||||
DownstreamQueue downstream_queue_;
|
DownstreamQueue downstream_queue_;
|
||||||
ClientHandler *handler_;
|
ClientHandler *handler_;
|
||||||
spdylay_session *session_;
|
spdylay_session *session_;
|
||||||
|
|
|
@ -36,9 +36,6 @@
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
#include <event2/bufferevent_ssl.h>
|
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
#ifdef HAVE_SPDYLAY
|
#ifdef HAVE_SPDYLAY
|
||||||
|
@ -450,9 +447,7 @@ SSL_CTX *create_ssl_client_context() {
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientHandler *accept_connection(event_base *evbase,
|
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||||
bufferevent_rate_limit_group *rate_limit_group,
|
|
||||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
|
||||||
sockaddr *addr, int addrlen,
|
sockaddr *addr, int addrlen,
|
||||||
WorkerStat *worker_stat,
|
WorkerStat *worker_stat,
|
||||||
DownstreamConnectionPool *dconn_pool) {
|
DownstreamConnectionPool *dconn_pool) {
|
||||||
|
@ -474,7 +469,6 @@ ClientHandler *accept_connection(event_base *evbase,
|
||||||
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
|
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
|
||||||
}
|
}
|
||||||
SSL *ssl = nullptr;
|
SSL *ssl = nullptr;
|
||||||
bufferevent *bev;
|
|
||||||
if (ssl_ctx) {
|
if (ssl_ctx) {
|
||||||
ssl = SSL_new(ssl_ctx);
|
ssl = SSL_new(ssl_ctx);
|
||||||
if (!ssl) {
|
if (!ssl) {
|
||||||
|
@ -490,21 +484,11 @@ ClientHandler *accept_connection(event_base *evbase,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bev = bufferevent_openssl_socket_new(
|
SSL_set_accept_state(ssl);
|
||||||
evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
} else {
|
|
||||||
bev = bufferevent_socket_new(evbase, fd, BEV_OPT_DEFER_CALLBACKS);
|
|
||||||
}
|
|
||||||
if (!bev) {
|
|
||||||
LOG(ERROR) << "bufferevent_socket_new() failed";
|
|
||||||
if (ssl) {
|
|
||||||
SSL_free(ssl);
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClientHandler(bev, rate_limit_group, fd, ssl, host, service,
|
return new ClientHandler(loop, fd, ssl, host, service, worker_stat,
|
||||||
worker_stat, dconn_pool);
|
dconn_pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
#include <event.h>
|
#include <ev.h>
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
@ -47,9 +47,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
||||||
|
|
||||||
SSL_CTX *create_ssl_client_context();
|
SSL_CTX *create_ssl_client_context();
|
||||||
|
|
||||||
ClientHandler *accept_connection(event_base *evbase,
|
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||||
bufferevent_rate_limit_group *rate_limit_group,
|
|
||||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
|
||||||
sockaddr *addr, int addrlen,
|
sockaddr *addr, int addrlen,
|
||||||
WorkerStat *worker_stat,
|
WorkerStat *worker_stat,
|
||||||
DownstreamConnectionPool *dconn_pool);
|
DownstreamConnectionPool *dconn_pool);
|
||||||
|
|
|
@ -38,95 +38,93 @@ using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
ThreadEventReceiver::ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
ThreadEventReceiver::ThreadEventReceiver(SSL_CTX *ssl_ctx,
|
||||||
Http2Session *http2session,
|
Http2Session *http2session,
|
||||||
ConnectBlocker *http1_connect_blocker)
|
ConnectBlocker *http1_connect_blocker)
|
||||||
: evbase_(evbase), ssl_ctx_(ssl_ctx), http2session_(http2session),
|
: ssl_ctx_(ssl_ctx), http2session_(http2session),
|
||||||
http1_connect_blocker_(http1_connect_blocker),
|
http1_connect_blocker_(http1_connect_blocker),
|
||||||
rate_limit_group_(bufferevent_rate_limit_group_new(
|
|
||||||
evbase_, get_config()->worker_rate_limit_cfg)),
|
|
||||||
worker_stat_(util::make_unique<WorkerStat>()) {}
|
worker_stat_(util::make_unique<WorkerStat>()) {}
|
||||||
|
|
||||||
ThreadEventReceiver::~ThreadEventReceiver() {
|
ThreadEventReceiver::~ThreadEventReceiver() {}
|
||||||
bufferevent_rate_limit_group_free(rate_limit_group_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadEventReceiver::on_read(bufferevent *bev) {
|
void ThreadEventReceiver::on_read() {
|
||||||
auto input = bufferevent_get_input(bev);
|
// auto input = bufferevent_get_input(bev);
|
||||||
while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
// while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
||||||
WorkerEvent wev;
|
// WorkerEvent wev;
|
||||||
int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
// int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
||||||
if (nread == -1) {
|
// if (nread == -1) {
|
||||||
TLOG(FATAL, this) << "evbuffer_remove() failed";
|
// TLOG(FATAL, this) << "evbuffer_remove() failed";
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
if (nread != sizeof(wev)) {
|
// if (nread != sizeof(wev)) {
|
||||||
TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
// TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
||||||
<< sizeof(wev) << " Actual:" << nread;
|
// << sizeof(wev) << " Actual:" << nread;
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (wev.type == REOPEN_LOG) {
|
// if (wev.type == REOPEN_LOG) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
// if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
// LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||||
<< ")";
|
// << ")";
|
||||||
}
|
// }
|
||||||
|
|
||||||
reopen_log_files();
|
// reopen_log_files();
|
||||||
|
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (wev.type == GRACEFUL_SHUTDOWN) {
|
// if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||||
LOG(NOTICE) << "Graceful shutdown commencing";
|
// LOG(NOTICE) << "Graceful shutdown commencing";
|
||||||
|
|
||||||
worker_config->graceful_shutdown = true;
|
// worker_config->graceful_shutdown = true;
|
||||||
|
|
||||||
if (worker_stat_->num_connections == 0) {
|
// if (worker_stat_->num_connections == 0) {
|
||||||
event_base_loopbreak(evbase_);
|
// event_base_loopbreak(evbase_);
|
||||||
|
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
|
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
// if (LOG_ENABLED(INFO)) {
|
||||||
TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
// TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||||
<< ", addrlen=" << wev.client_addrlen;
|
// << ", addrlen=" << wev.client_addrlen;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (worker_stat_->num_connections >=
|
// if (worker_stat_->num_connections >=
|
||||||
get_config()->worker_frontend_connections) {
|
// get_config()->worker_frontend_connections) {
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
// if (LOG_ENABLED(INFO)) {
|
||||||
TLOG(INFO, this) << "Too many connections >= "
|
// TLOG(INFO, this) << "Too many connections >= "
|
||||||
<< get_config()->worker_frontend_connections;
|
// << get_config()->worker_frontend_connections;
|
||||||
}
|
// }
|
||||||
|
|
||||||
close(wev.client_fd);
|
// close(wev.client_fd);
|
||||||
|
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
auto evbase = bufferevent_get_base(bev);
|
// auto evbase = bufferevent_get_base(bev);
|
||||||
auto client_handler = ssl::accept_connection(
|
// auto client_handler = ssl::accept_connection(
|
||||||
evbase, rate_limit_group_, ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
|
// evbase, rate_limit_group_, ssl_ctx_, wev.client_fd,
|
||||||
wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
// &wev.client_addr.sa,
|
||||||
if (client_handler) {
|
// wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||||
client_handler->set_http2_session(http2session_);
|
// if (client_handler) {
|
||||||
client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
// client_handler->set_http2_session(http2session_);
|
||||||
|
// client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
||||||
|
|
||||||
if (LOG_ENABLED(INFO)) {
|
// if (LOG_ENABLED(INFO)) {
|
||||||
TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created";
|
// TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << "
|
||||||
}
|
// created";
|
||||||
} else {
|
// }
|
||||||
if (LOG_ENABLED(INFO)) {
|
// } else {
|
||||||
TLOG(ERROR, this) << "ClientHandler creation failed";
|
// if (LOG_ENABLED(INFO)) {
|
||||||
}
|
// TLOG(ERROR, this) << "ClientHandler creation failed";
|
||||||
close(wev.client_fd);
|
// }
|
||||||
}
|
// close(wev.client_fd);
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -31,8 +31,6 @@
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "shrpx_downstream_connection_pool.h"
|
#include "shrpx_downstream_connection_pool.h"
|
||||||
|
|
||||||
|
@ -54,28 +52,26 @@ struct WorkerEvent {
|
||||||
struct {
|
struct {
|
||||||
sockaddr_union client_addr;
|
sockaddr_union client_addr;
|
||||||
size_t client_addrlen;
|
size_t client_addrlen;
|
||||||
evutil_socket_t client_fd;
|
int client_fd;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class ThreadEventReceiver {
|
class ThreadEventReceiver {
|
||||||
public:
|
public:
|
||||||
ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
ThreadEventReceiver(SSL_CTX *ssl_ctx, Http2Session *http2session,
|
||||||
Http2Session *http2session,
|
|
||||||
ConnectBlocker *http1_connect_blocker);
|
ConnectBlocker *http1_connect_blocker);
|
||||||
~ThreadEventReceiver();
|
~ThreadEventReceiver();
|
||||||
void on_read(bufferevent *bev);
|
void on_read();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DownstreamConnectionPool dconn_pool_;
|
DownstreamConnectionPool dconn_pool_;
|
||||||
event_base *evbase_;
|
// event_base *evbase_;
|
||||||
SSL_CTX *ssl_ctx_;
|
SSL_CTX *ssl_ctx_;
|
||||||
// Shared HTTP2 session for each thread. NULL if not client
|
// Shared HTTP2 session for each thread. NULL if not client
|
||||||
// mode. Not deleted by this object.
|
// mode. Not deleted by this object.
|
||||||
Http2Session *http2session_;
|
Http2Session *http2session_;
|
||||||
ConnectBlocker *http1_connect_blocker_;
|
ConnectBlocker *http1_connect_blocker_;
|
||||||
bufferevent_rate_limit_group *rate_limit_group_;
|
|
||||||
std::unique_ptr<WorkerStat> worker_stat_;
|
std::unique_ptr<WorkerStat> worker_stat_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,28 +26,29 @@
|
||||||
#define SHRPX_UPSTREAM_H
|
#define SHRPX_UPSTREAM_H
|
||||||
|
|
||||||
#include "shrpx.h"
|
#include "shrpx.h"
|
||||||
|
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include "shrpx_io_control.h"
|
#include "shrpx_io_control.h"
|
||||||
|
#include "memchunk.h"
|
||||||
|
|
||||||
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
class ClientHandler;
|
class ClientHandler;
|
||||||
class Downstream;
|
class Downstream;
|
||||||
|
class DownstreamConnection;
|
||||||
|
|
||||||
class Upstream {
|
class Upstream {
|
||||||
public:
|
public:
|
||||||
virtual ~Upstream() {}
|
virtual ~Upstream() {}
|
||||||
virtual int on_read() = 0;
|
virtual int on_read() = 0;
|
||||||
virtual int on_write() = 0;
|
virtual int on_write() = 0;
|
||||||
virtual int on_event() = 0;
|
|
||||||
virtual int on_timeout(Downstream *downstream) { return 0; };
|
virtual int on_timeout(Downstream *downstream) { return 0; };
|
||||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||||
unsigned int status_code) = 0;
|
unsigned int status_code) = 0;
|
||||||
virtual bufferevent_data_cb get_downstream_readcb() = 0;
|
virtual int downstream_read(DownstreamConnection *dconn) = 0;
|
||||||
virtual bufferevent_data_cb get_downstream_writecb() = 0;
|
virtual int downstream_write(DownstreamConnection *dconn) = 0;
|
||||||
virtual bufferevent_event_cb get_downstream_eventcb() = 0;
|
virtual int downstream_eof(DownstreamConnection *dconn) = 0;
|
||||||
|
virtual int downstream_error(DownstreamConnection *dconn, int events) = 0;
|
||||||
virtual ClientHandler *get_client_handler() const = 0;
|
virtual ClientHandler *get_client_handler() const = 0;
|
||||||
|
|
||||||
virtual int on_downstream_header_complete(Downstream *downstream) = 0;
|
virtual int on_downstream_header_complete(Downstream *downstream) = 0;
|
||||||
|
@ -64,7 +65,7 @@ public:
|
||||||
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||||
size_t consumed) = 0;
|
size_t consumed) = 0;
|
||||||
|
|
||||||
virtual void reset_timeouts() = 0;
|
virtual MemchunkPool4K *get_mcpool() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -25,96 +25,135 @@
|
||||||
#include "shrpx_worker.h"
|
#include "shrpx_worker.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <event.h>
|
|
||||||
#include <event2/bufferevent.h>
|
|
||||||
|
|
||||||
#include "shrpx_ssl.h"
|
#include "shrpx_ssl.h"
|
||||||
#include "shrpx_thread_event_receiver.h"
|
|
||||||
#include "shrpx_log.h"
|
#include "shrpx_log.h"
|
||||||
|
#include "shrpx_client_handler.h"
|
||||||
#include "shrpx_http2_session.h"
|
#include "shrpx_http2_session.h"
|
||||||
#include "shrpx_worker_config.h"
|
#include "shrpx_worker_config.h"
|
||||||
#include "shrpx_connect_blocker.h"
|
#include "shrpx_connect_blocker.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "libevent_util.h"
|
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
Worker::Worker(const WorkerInfo *info)
|
namespace {
|
||||||
: sv_ssl_ctx_(info->sv_ssl_ctx), cl_ssl_ctx_(info->cl_ssl_ctx),
|
void eventcb(struct ev_loop *loop, ev_async *w, int revents) {
|
||||||
fd_(info->sv[1]) {}
|
auto worker = static_cast<Worker *>(w->data);
|
||||||
|
worker->process_events();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Worker::Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx)
|
||||||
|
: loop_(ev_loop_new(0)), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||||
|
worker_stat_(util::make_unique<WorkerStat>()) {
|
||||||
|
ev_async_init(&w_, eventcb);
|
||||||
|
w_.data = this;
|
||||||
|
ev_async_start(loop_, &w_);
|
||||||
|
|
||||||
|
if (get_config()->downstream_proto == PROTO_HTTP2) {
|
||||||
|
http2session_ = util::make_unique<Http2Session>(loop_, cl_ssl_ctx_);
|
||||||
|
} else {
|
||||||
|
http1_connect_blocker_ = util::make_unique<ConnectBlocker>(loop_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Worker::~Worker() {
|
Worker::~Worker() {
|
||||||
shutdown(fd_, SHUT_WR);
|
ev_async_stop(loop_, &w_);
|
||||||
close(fd_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
void readcb(bufferevent *bev, void *arg) {
|
|
||||||
auto receiver = static_cast<ThreadEventReceiver *>(arg);
|
|
||||||
receiver->on_read(bev);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
void eventcb(bufferevent *bev, short events, void *arg) {
|
|
||||||
if (events & BEV_EVENT_EOF) {
|
|
||||||
LOG(ERROR) << "Connection to main thread lost: eof";
|
|
||||||
}
|
|
||||||
if (events & BEV_EVENT_ERROR) {
|
|
||||||
LOG(ERROR) << "Connection to main thread lost: network error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void Worker::run() {
|
void Worker::run() {
|
||||||
(void)reopen_log_files();
|
fut_ = std::async(std::launch::async, [this] { this->run_loop(); });
|
||||||
|
|
||||||
auto evbase = std::unique_ptr<event_base, decltype(&event_base_free)>(
|
|
||||||
event_base_new(), event_base_free);
|
|
||||||
if (!evbase) {
|
|
||||||
LOG(ERROR) << "event_base_new() failed";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto bev = std::unique_ptr<bufferevent, decltype(&bufferevent_free)>(
|
|
||||||
bufferevent_socket_new(evbase.get(), fd_, BEV_OPT_DEFER_CALLBACKS),
|
|
||||||
bufferevent_free);
|
|
||||||
if (!bev) {
|
|
||||||
LOG(ERROR) << "bufferevent_socket_new() failed";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::unique_ptr<Http2Session> http2session;
|
|
||||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker;
|
|
||||||
if (get_config()->downstream_proto == PROTO_HTTP2) {
|
|
||||||
http2session = util::make_unique<Http2Session>(evbase.get(), cl_ssl_ctx_);
|
|
||||||
if (http2session->init_notification() == -1) {
|
|
||||||
DIE();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
http1_connect_blocker = util::make_unique<ConnectBlocker>();
|
|
||||||
if (http1_connect_blocker->init(evbase.get()) == -1) {
|
|
||||||
DIE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto receiver = util::make_unique<ThreadEventReceiver>(
|
|
||||||
evbase.get(), sv_ssl_ctx_, http2session.get(),
|
|
||||||
http1_connect_blocker.get());
|
|
||||||
|
|
||||||
util::bev_enable_unless(bev.get(), EV_READ);
|
|
||||||
bufferevent_setcb(bev.get(), readcb, nullptr, eventcb, receiver.get());
|
|
||||||
|
|
||||||
event_base_loop(evbase.get(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_threaded_worker(WorkerInfo *info) {
|
void Worker::run_loop() {
|
||||||
Worker worker(info);
|
(void)reopen_log_files();
|
||||||
worker.run();
|
ev_run(loop_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::wait() { fut_.get(); }
|
||||||
|
|
||||||
|
void Worker::send(const WorkerEvent &event) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> g(m_);
|
||||||
|
|
||||||
|
q_.push_back(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
ev_async_send(loop_, &w_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Worker::process_events() {
|
||||||
|
std::deque<WorkerEvent> q;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> g(m_);
|
||||||
|
q.swap(q_);
|
||||||
|
}
|
||||||
|
for (auto &wev : q) {
|
||||||
|
if (wev.type == REOPEN_LOG) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||||
|
<< ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
reopen_log_files();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||||
|
LOG(NOTICE) << "Graceful shutdown commencing";
|
||||||
|
|
||||||
|
worker_config->graceful_shutdown = true;
|
||||||
|
|
||||||
|
if (worker_stat_->num_connections == 0) {
|
||||||
|
ev_break(loop_);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||||
|
<< ", addrlen=" << wev.client_addrlen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (worker_stat_->num_connections >=
|
||||||
|
get_config()->worker_frontend_connections) {
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
WLOG(INFO, this) << "Too many connections >= "
|
||||||
|
<< get_config()->worker_frontend_connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(wev.client_fd);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto client_handler = ssl::accept_connection(
|
||||||
|
loop_, sv_ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
|
||||||
|
wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||||
|
if (!client_handler) {
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
WLOG(ERROR, this) << "ClientHandler creation failed";
|
||||||
|
}
|
||||||
|
close(wev.client_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_handler->set_http2_session(http2session_.get());
|
||||||
|
client_handler->set_http1_connect_blocker(http1_connect_blocker_.get());
|
||||||
|
|
||||||
|
if (LOG_ENABLED(INFO)) {
|
||||||
|
WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created ";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -27,13 +27,26 @@
|
||||||
|
|
||||||
#include "shrpx.h"
|
#include "shrpx.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <deque>
|
||||||
|
#include <thread>
|
||||||
|
#ifndef NOTHREADS
|
||||||
|
#include <future>
|
||||||
|
#endif // NOTHREADS
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
|
||||||
#include "shrpx_listen_handler.h"
|
#include <ev.h>
|
||||||
|
|
||||||
|
#include "shrpx_config.h"
|
||||||
|
#include "shrpx_downstream_connection_pool.h"
|
||||||
|
|
||||||
namespace shrpx {
|
namespace shrpx {
|
||||||
|
|
||||||
|
class Http2Session;
|
||||||
|
class ConnectBlocker;
|
||||||
|
|
||||||
struct WorkerStat {
|
struct WorkerStat {
|
||||||
WorkerStat() : num_connections(0), next_downstream(0) {}
|
WorkerStat() : num_connections(0), next_downstream(0) {}
|
||||||
|
|
||||||
|
@ -44,20 +57,48 @@ struct WorkerStat {
|
||||||
size_t next_downstream;
|
size_t next_downstream;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Worker {
|
enum WorkerEventType {
|
||||||
public:
|
NEW_CONNECTION = 0x01,
|
||||||
Worker(const WorkerInfo *info);
|
REOPEN_LOG = 0x02,
|
||||||
~Worker();
|
GRACEFUL_SHUTDOWN = 0x03,
|
||||||
void run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
SSL_CTX *sv_ssl_ctx_;
|
|
||||||
SSL_CTX *cl_ssl_ctx_;
|
|
||||||
// Channel to the main thread
|
|
||||||
int fd_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void start_threaded_worker(WorkerInfo *info);
|
struct WorkerEvent {
|
||||||
|
WorkerEventType type;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
sockaddr_union client_addr;
|
||||||
|
size_t client_addrlen;
|
||||||
|
int client_fd;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class Worker {
|
||||||
|
public:
|
||||||
|
Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
||||||
|
~Worker();
|
||||||
|
void run();
|
||||||
|
void run_loop();
|
||||||
|
void wait();
|
||||||
|
void process_events();
|
||||||
|
void send(const WorkerEvent &event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifndef NOTHREADS
|
||||||
|
std::future<void> fut_;
|
||||||
|
#endif // NOTHREADS
|
||||||
|
std::mutex m_;
|
||||||
|
std::deque<WorkerEvent> q_;
|
||||||
|
ev_async w_;
|
||||||
|
DownstreamConnectionPool dconn_pool_;
|
||||||
|
struct ev_loop *loop_;
|
||||||
|
SSL_CTX *sv_ssl_ctx_;
|
||||||
|
SSL_CTX *cl_ssl_ctx_;
|
||||||
|
std::unique_ptr<Http2Session> http2session_;
|
||||||
|
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
||||||
|
std::unique_ptr<WorkerStat> worker_stat_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
54
src/util.cc
54
src/util.cc
|
@ -30,6 +30,8 @@
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
@ -827,6 +829,58 @@ std::vector<unsigned char> get_default_alpn() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int make_socket_closeonexec(int fd) {
|
||||||
|
int flags;
|
||||||
|
int rv;
|
||||||
|
while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int make_socket_nonblocking(int fd) {
|
||||||
|
int flags;
|
||||||
|
int rv;
|
||||||
|
while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
|
||||||
|
;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int make_socket_nodelay(int fd) {
|
||||||
|
int val = 1;
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
|
||||||
|
sizeof(val)) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int create_nonblock_socket(int family) {
|
||||||
|
auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||||
|
|
||||||
|
if (fd == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_socket_nodelay(fd);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_socket_connected(int fd) {
|
||||||
|
int error;
|
||||||
|
socklen_t len = sizeof(error);
|
||||||
|
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
|
||||||
|
if (error != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -466,6 +466,14 @@ template <typename Clock, typename Rep> Rep clock_precision() {
|
||||||
return duration.count();
|
return duration.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int make_socket_closeonexec(int fd);
|
||||||
|
int make_socket_nonblocking(int fd);
|
||||||
|
int make_socket_nodelay(int fd);
|
||||||
|
|
||||||
|
int create_nonblock_socket(int family);
|
||||||
|
|
||||||
|
bool check_socket_connected(int fd);
|
||||||
|
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -131,7 +131,7 @@ There are two types of callbacks:
|
||||||
* notification `typedef int (*http_cb) (http_parser*);`
|
* notification `typedef int (*http_cb) (http_parser*);`
|
||||||
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||||
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
||||||
Callbacks: (requests only) on_uri,
|
Callbacks: (requests only) on_url,
|
||||||
(common) on_header_field, on_header_value, on_body;
|
(common) on_header_field, on_header_value, on_body;
|
||||||
|
|
||||||
Callbacks must return 0 on success. Returning a non-zero value indicates
|
Callbacks must return 0 on success. Returning a non-zero value indicates
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* Copyright Fedor Indutny. All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to
|
||||||
|
* deal in the Software without restriction, including without limitation the
|
||||||
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
* sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
* IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
#include "http_parser.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
static const char data[] =
|
||||||
|
"POST /joyent/http-parser HTTP/1.1\r\n"
|
||||||
|
"Host: github.com\r\n"
|
||||||
|
"DNT: 1\r\n"
|
||||||
|
"Accept-Encoding: gzip, deflate, sdch\r\n"
|
||||||
|
"Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n"
|
||||||
|
"User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
"Chrome/39.0.2171.65 Safari/537.36\r\n"
|
||||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
|
||||||
|
"image/webp,*/*;q=0.8\r\n"
|
||||||
|
"Referer: https://github.com/joyent/http-parser\r\n"
|
||||||
|
"Connection: keep-alive\r\n"
|
||||||
|
"Transfer-Encoding: chunked\r\n"
|
||||||
|
"Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n";
|
||||||
|
static const size_t data_len = sizeof(data) - 1;
|
||||||
|
|
||||||
|
static int on_info(http_parser* p) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int on_data(http_parser* p, const char *at, size_t length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static http_parser_settings settings = {
|
||||||
|
.on_message_begin = on_info,
|
||||||
|
.on_headers_complete = on_info,
|
||||||
|
.on_message_complete = on_info,
|
||||||
|
.on_header_field = on_data,
|
||||||
|
.on_header_value = on_data,
|
||||||
|
.on_url = on_data,
|
||||||
|
.on_status = on_data,
|
||||||
|
.on_body = on_data
|
||||||
|
};
|
||||||
|
|
||||||
|
int bench(int iter_count, int silent) {
|
||||||
|
struct http_parser parser;
|
||||||
|
int i;
|
||||||
|
int err;
|
||||||
|
struct timeval start;
|
||||||
|
struct timeval end;
|
||||||
|
float rps;
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
err = gettimeofday(&start, NULL);
|
||||||
|
assert(err == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < iter_count; i++) {
|
||||||
|
size_t parsed;
|
||||||
|
http_parser_init(&parser, HTTP_REQUEST);
|
||||||
|
|
||||||
|
parsed = http_parser_execute(&parser, &settings, data, data_len);
|
||||||
|
assert(parsed == data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
err = gettimeofday(&end, NULL);
|
||||||
|
assert(err == 0);
|
||||||
|
|
||||||
|
fprintf(stdout, "Benchmark result:\n");
|
||||||
|
|
||||||
|
rps = (float) (end.tv_sec - start.tv_sec) +
|
||||||
|
(end.tv_usec - start.tv_usec) * 1e-6f;
|
||||||
|
fprintf(stdout, "Took %f seconds to run\n", rps);
|
||||||
|
|
||||||
|
rps = (float) iter_count / rps;
|
||||||
|
fprintf(stdout, "%f req/sec\n", rps);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
|
||||||
|
for (;;)
|
||||||
|
bench(5000000, 1);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return bench(5000000, 0);
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -136,9 +136,10 @@ enum flags
|
||||||
{ F_CHUNKED = 1 << 0
|
{ F_CHUNKED = 1 << 0
|
||||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||||
, F_CONNECTION_CLOSE = 1 << 2
|
, F_CONNECTION_CLOSE = 1 << 2
|
||||||
, F_TRAILING = 1 << 3
|
, F_CONNECTION_UPGRADE = 1 << 3
|
||||||
, F_UPGRADE = 1 << 4
|
, F_TRAILING = 1 << 4
|
||||||
, F_SKIPBODY = 1 << 5
|
, F_UPGRADE = 1 << 5
|
||||||
|
, F_SKIPBODY = 1 << 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -950,6 +950,42 @@ const struct message requests[] =
|
||||||
,.body= ""
|
,.body= ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CONNECTION_MULTI 35
|
||||||
|
, {.name = "multiple connection header values with folding"
|
||||||
|
,.type= HTTP_REQUEST
|
||||||
|
,.raw= "GET /demo HTTP/1.1\r\n"
|
||||||
|
"Host: example.com\r\n"
|
||||||
|
"Connection: Something,\r\n"
|
||||||
|
" Upgrade, ,Keep-Alive\r\n"
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n"
|
||||||
|
"Sec-WebSocket-Protocol: sample\r\n"
|
||||||
|
"Upgrade: WebSocket\r\n"
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n"
|
||||||
|
"Origin: http://example.com\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Hot diggity dogg"
|
||||||
|
,.should_keep_alive= TRUE
|
||||||
|
,.message_complete_on_eof= FALSE
|
||||||
|
,.http_major= 1
|
||||||
|
,.http_minor= 1
|
||||||
|
,.method= HTTP_GET
|
||||||
|
,.query_string= ""
|
||||||
|
,.fragment= ""
|
||||||
|
,.request_path= "/demo"
|
||||||
|
,.request_url= "/demo"
|
||||||
|
,.num_headers= 7
|
||||||
|
,.upgrade="Hot diggity dogg"
|
||||||
|
,.headers= { { "Host", "example.com" }
|
||||||
|
, { "Connection", "Something, Upgrade, ,Keep-Alive" }
|
||||||
|
, { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" }
|
||||||
|
, { "Sec-WebSocket-Protocol", "sample" }
|
||||||
|
, { "Upgrade", "WebSocket" }
|
||||||
|
, { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" }
|
||||||
|
, { "Origin", "http://example.com" }
|
||||||
|
}
|
||||||
|
,.body= ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
, {.name= NULL } /* sentinel */
|
, {.name= NULL } /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue