Merge branch 'libev'
This commit is contained in:
commit
1474f755cf
|
@ -21,6 +21,7 @@ before_install:
|
|||
libcunit1-dev
|
||||
libssl-dev
|
||||
libxml2-dev
|
||||
libev-dev
|
||||
libevent-dev
|
||||
libjansson-dev
|
||||
libjemalloc-dev
|
||||
|
|
|
@ -71,7 +71,7 @@ To build and run the application programs (``nghttp``, ``nghttpd`` and
|
|||
required:
|
||||
|
||||
* OpenSSL >= 1.0.1
|
||||
* libevent-openssl >= 2.0.8
|
||||
* libev >= 4.15
|
||||
* zlib >= 1.2.3
|
||||
|
||||
ALPN support requires unreleased version OpenSSL >= 1.0.2.
|
||||
|
@ -90,6 +90,10 @@ The HPACK tools require the following package:
|
|||
|
||||
* 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
|
||||
(``nghttpd`` and ``nghttpx``), jemalloc is recommended:
|
||||
|
||||
|
@ -119,6 +123,7 @@ installed:
|
|||
* libcunit1-dev
|
||||
* libssl-dev
|
||||
* libxml2-dev
|
||||
* libev-dev
|
||||
* libevent-dev
|
||||
* libjansson-dev
|
||||
* libjemalloc-dev
|
||||
|
|
23
configure.ac
23
configure.ac
|
@ -275,6 +275,21 @@ fi
|
|||
|
||||
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)
|
||||
PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
|
||||
[have_openssl=yes], [have_openssl=no])
|
||||
|
@ -282,7 +297,7 @@ if test "x${have_openssl}" = "xno"; then
|
|||
AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
|
||||
fi
|
||||
|
||||
# libevent_openssl (for src)
|
||||
# libevent_openssl (for examples)
|
||||
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
||||
PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
|
||||
[have_libevent_openssl=yes], [have_libevent_openssl=no])
|
||||
|
@ -375,12 +390,12 @@ if test "x${request_asio_lib}" = "xyes"; then
|
|||
fi
|
||||
|
||||
# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
|
||||
# and libevent_openssl
|
||||
# and libev
|
||||
enable_app=no
|
||||
if test "x${request_app}" != "xno" &&
|
||||
test "x${have_zlib}" = "xyes" &&
|
||||
test "x${have_openssl}" = "xyes" &&
|
||||
test "x${have_libevent_openssl}" = "xyes"; then
|
||||
test "x${have_libev}" = "xyes"; then
|
||||
enable_app=yes
|
||||
fi
|
||||
|
||||
|
@ -505,6 +520,7 @@ if test "x$cross_compiling" != "xyes"; then
|
|||
fi
|
||||
AC_CHECK_FUNCS([ \
|
||||
_Exit \
|
||||
accept4 \
|
||||
getpwnam \
|
||||
memmove \
|
||||
memset \
|
||||
|
@ -649,6 +665,7 @@ AC_MSG_NOTICE([summary of build options:
|
|||
Libs:
|
||||
OpenSSL: ${have_openssl}
|
||||
Libxml2: ${have_libxml2}
|
||||
Libev: ${have_libev}
|
||||
Libevent(SSL): ${have_libevent_openssl}
|
||||
Spdylay: ${have_spdylay}
|
||||
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 <event2/event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "nghttp2_buf.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "http2.h"
|
||||
#include "ringbuf.h"
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
|
@ -65,8 +55,8 @@ struct Config {
|
|||
std::string private_key_file;
|
||||
std::string cert_file;
|
||||
std::string dh_param_file;
|
||||
timeval stream_read_timeout;
|
||||
timeval stream_write_timeout;
|
||||
ev_tstamp stream_read_timeout;
|
||||
ev_tstamp stream_write_timeout;
|
||||
nghttp2_option *session_option;
|
||||
void *data_ptr;
|
||||
size_t padding;
|
||||
|
@ -88,11 +78,12 @@ class Http2Handler;
|
|||
struct Stream {
|
||||
Headers headers;
|
||||
Http2Handler *handler;
|
||||
event *rtimer;
|
||||
event *wtimer;
|
||||
ev_timer rtimer;
|
||||
ev_timer wtimer;
|
||||
int64_t body_left;
|
||||
int32_t stream_id;
|
||||
int file;
|
||||
int hdidx[http2::HD_MAXIDX];
|
||||
Stream(Http2Handler *handler, int32_t stream_id);
|
||||
~Stream();
|
||||
};
|
||||
|
@ -106,7 +97,6 @@ public:
|
|||
|
||||
void remove_self();
|
||||
int setup_bev();
|
||||
int send();
|
||||
int on_read();
|
||||
int on_write();
|
||||
int on_connect();
|
||||
|
@ -137,14 +127,29 @@ public:
|
|||
void remove_settings_timer();
|
||||
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:
|
||||
ev_io wev_;
|
||||
ev_io rev_;
|
||||
ev_timer settings_timerev_;
|
||||
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
||||
RingBuf<65536> wb_;
|
||||
std::function<int(Http2Handler &)> read_, write_;
|
||||
int64_t session_id_;
|
||||
nghttp2_session *session_;
|
||||
Sessions *sessions_;
|
||||
SSL *ssl_;
|
||||
bufferevent *bev_;
|
||||
event *settings_timerev_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
int fd_;
|
||||
};
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ AM_CPPFLAGS = \
|
|||
-I$(top_srcdir)/third-party \
|
||||
@LIBSPDYLAY_CFLAGS@ \
|
||||
@XML_CPPFLAGS@ \
|
||||
@LIBEVENT_OPENSSL_CFLAGS@ \
|
||||
@LIBEV_CFLAGS@ \
|
||||
@OPENSSL_CFLAGS@ \
|
||||
@JANSSON_CFLAGS@ \
|
||||
@ZLIB_CFLAGS@ \
|
||||
|
@ -45,7 +45,7 @@ AM_LDFLAGS = \
|
|||
@JEMALLOC_LIBS@ \
|
||||
@LIBSPDYLAY_LIBS@ \
|
||||
@XML_LIBS@ \
|
||||
@LIBEVENT_OPENSSL_LIBS@ \
|
||||
@LIBEV_LIBS@ \
|
||||
@OPENSSL_LIBS@ \
|
||||
@JANSSON_LIBS@ \
|
||||
@ZLIB_LIBS@ \
|
||||
|
@ -59,9 +59,9 @@ if ENABLE_APP
|
|||
|
||||
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
|
||||
HELPER_HFILES = util.h libevent_util.h \
|
||||
HELPER_HFILES = util.h \
|
||||
http2.h timegm.h app_helper.h nghttp2_config.h \
|
||||
nghttp2_gzip.h
|
||||
|
||||
|
@ -78,11 +78,12 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
|
|||
|
||||
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
|
||||
ssl.cc ssl.h \
|
||||
HttpServer.cc HttpServer.h
|
||||
HttpServer.cc HttpServer.h \
|
||||
ringbuf.h
|
||||
|
||||
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 \
|
||||
timegm.c timegm.h \
|
||||
ssl.cc ssl.h \
|
||||
|
@ -95,31 +96,32 @@ endif # HAVE_SPDYLAY
|
|||
|
||||
NGHTTPX_SRCS = \
|
||||
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 \
|
||||
ssl.cc ssl.h \
|
||||
shrpx_config.cc shrpx_config.h \
|
||||
shrpx_error.h \
|
||||
shrpx_accept_handler.cc shrpx_accept_handler.h \
|
||||
shrpx_listen_handler.cc shrpx_listen_handler.h \
|
||||
shrpx_client_handler.cc shrpx_client_handler.h \
|
||||
shrpx_upstream.h \
|
||||
shrpx_http2_upstream.cc shrpx_http2_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_connection.cc shrpx_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_session.cc shrpx_http2_session.h \
|
||||
shrpx_downstream_queue.cc shrpx_downstream_queue.h \
|
||||
shrpx_log.cc shrpx_log.h \
|
||||
shrpx_http.cc shrpx_http.h \
|
||||
shrpx_io_control.cc shrpx_io_control.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_config.cc shrpx_worker_config.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
|
||||
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 \
|
||||
util_test.cc util_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}\
|
||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||
nghttpx_unittest_LDFLAGS = ${AM_LDFLAGS} \
|
||||
|
|
589
src/h2load.cc
589
src/h2load.cc
|
@ -42,8 +42,6 @@
|
|||
#include <spdylay/spdylay.h>
|
||||
#endif // HAVE_SPDYLAY
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/conf.h>
|
||||
|
||||
|
@ -70,18 +68,6 @@ Config::~Config() { freeaddrinfo(addrs); }
|
|||
|
||||
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 {
|
||||
void debug(const char *format, ...) {
|
||||
if (config.verbose) {
|
||||
|
@ -94,46 +80,113 @@ void debug(const char *format, ...) {
|
|||
}
|
||||
} // 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) {}
|
||||
|
||||
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)
|
||||
: worker(worker), ssl(nullptr), bev(nullptr), next_addr(config.addrs),
|
||||
reqidx(0), state(CLIENT_IDLE), req_todo(req_todo), req_started(0),
|
||||
req_done(0) {}
|
||||
: worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
|
||||
state(CLIENT_IDLE), req_todo(req_todo), req_started(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(); }
|
||||
|
||||
int Client::do_read() { return readfn(*this); }
|
||||
int Client::do_write() { return writefn(*this); }
|
||||
|
||||
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) {
|
||||
rv = bufferevent_socket_connect(bev, next_addr->ai_addr,
|
||||
next_addr->ai_addrlen);
|
||||
auto addr = next_addr;
|
||||
next_addr = next_addr->ai_next;
|
||||
if (rv == 0) {
|
||||
break;
|
||||
fd = util::create_nonblock_socket(addr->ai_family);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -144,27 +197,21 @@ void Client::fail() {
|
|||
}
|
||||
|
||||
void Client::disconnect() {
|
||||
int fd = -1;
|
||||
streams.clear();
|
||||
session.reset();
|
||||
state = CLIENT_IDLE;
|
||||
ev_io_stop(worker->loop, &wev);
|
||||
ev_io_stop(worker->loop, &rev);
|
||||
if (ssl) {
|
||||
fd = SSL_get_fd(ssl);
|
||||
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
|
||||
SSL_shutdown(ssl);
|
||||
}
|
||||
if (bev) {
|
||||
bufferevent_disable(bev, EV_READ | EV_WRITE);
|
||||
bufferevent_free(bev);
|
||||
bev = nullptr;
|
||||
}
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
ssl = nullptr;
|
||||
}
|
||||
if (fd != -1) {
|
||||
shutdown(fd, SHUT_WR);
|
||||
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() {
|
||||
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();
|
||||
|
||||
auto nreq =
|
||||
|
@ -334,25 +448,235 @@ int Client::on_connect() {
|
|||
for (; nreq > 0; --nreq) {
|
||||
submit_request();
|
||||
}
|
||||
return session->on_write();
|
||||
|
||||
signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Client::on_read() {
|
||||
ssize_t rv = session->on_read();
|
||||
if (rv < 0) {
|
||||
int Client::on_net_error() {
|
||||
if (state == CLIENT_IDLE) {
|
||||
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;
|
||||
}
|
||||
worker->stats.bytes_total += rv;
|
||||
|
||||
return on_write();
|
||||
worker->stats.bytes_total += len;
|
||||
signal_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,
|
||||
Config *config)
|
||||
: stats{0}, evbase(event_base_new()), ssl_ctx(ssl_ctx), config(config),
|
||||
id(id), tls_info_report_done(false) {
|
||||
: stats{0}, loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config), id(id),
|
||||
tls_info_report_done(false) {
|
||||
stats.req_todo = req_todo;
|
||||
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() {
|
||||
for (auto &client : clients) {
|
||||
|
@ -378,145 +707,9 @@ void Worker::run() {
|
|||
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 {
|
||||
void resolve_host() {
|
||||
int rv;
|
||||
|
|
38
src/h2load.h
38
src/h2load.h
|
@ -38,12 +38,14 @@
|
|||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/event.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "http2.h"
|
||||
#include "ringbuf.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -107,7 +109,7 @@ struct Client;
|
|||
struct Worker {
|
||||
std::vector<std::unique_ptr<Client>> clients;
|
||||
Stats stats;
|
||||
event_base *evbase;
|
||||
struct ev_loop *loop;
|
||||
SSL_CTX *ssl_ctx;
|
||||
Config *config;
|
||||
size_t progress_interval;
|
||||
|
@ -128,9 +130,13 @@ struct Stream {
|
|||
struct Client {
|
||||
std::unordered_map<int32_t, Stream> streams;
|
||||
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;
|
||||
SSL *ssl;
|
||||
bufferevent *bev;
|
||||
addrinfo *next_addr;
|
||||
size_t reqidx;
|
||||
ClientState state;
|
||||
|
@ -140,6 +146,10 @@ struct Client {
|
|||
size_t req_started;
|
||||
// The number of requests this client has done so far.
|
||||
size_t req_done;
|
||||
int fd;
|
||||
RingBuf<65536> wb;
|
||||
|
||||
enum { ERR_CONNECT_FAIL = -100 };
|
||||
|
||||
Client(Worker *worker, size_t req_todo);
|
||||
~Client();
|
||||
|
@ -151,13 +161,29 @@ struct Client {
|
|||
void report_progress();
|
||||
void report_tls_info();
|
||||
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_connect();
|
||||
int on_net_error();
|
||||
int noop();
|
||||
|
||||
void on_request(int32_t stream_id);
|
||||
void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen);
|
||||
void on_stream_close(int32_t stream_id, bool success);
|
||||
|
||||
void signal_write();
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
#include "h2load.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
|
@ -86,6 +85,20 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|||
}
|
||||
} // 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() {
|
||||
int rv;
|
||||
|
||||
|
@ -108,6 +121,8 @@ void Http2Session::on_connect() {
|
|||
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
||||
on_header_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
||||
|
||||
nghttp2_session_client_new(&session_, callbacks, client_);
|
||||
|
||||
nghttp2_settings_entry iv[2];
|
||||
|
@ -129,8 +144,13 @@ void Http2Session::on_connect() {
|
|||
extra_connection_window);
|
||||
}
|
||||
|
||||
bufferevent_write(client_->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
|
||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||
auto &wb = client_->wb;
|
||||
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() {
|
||||
|
@ -148,66 +168,35 @@ void Http2Session::submit_request() {
|
|||
client_->on_request(stream_id);
|
||||
}
|
||||
|
||||
ssize_t Http2Session::on_read() {
|
||||
int rv;
|
||||
size_t nread = 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 = nghttp2_session_mem_recv(session_, mem, inputlen);
|
||||
|
||||
if (rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nread += rv;
|
||||
|
||||
if (evbuffer_drain(input, rv) != 0) {
|
||||
return -1;
|
||||
}
|
||||
int Http2Session::on_read(const uint8_t *data, size_t len) {
|
||||
auto rv = nghttp2_session_mem_recv(session_, data, len);
|
||||
if (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 rv;
|
||||
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();
|
||||
auto rv = nghttp2_session_send(session_);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nghttp2_session_want_read(session_) == 0 &&
|
||||
nghttp2_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(output) == 0) {
|
||||
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
virtual ~Http2Session();
|
||||
virtual void on_connect();
|
||||
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 void terminate();
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "nghttp2_config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -40,7 +41,7 @@ public:
|
|||
virtual void submit_request() = 0;
|
||||
// Called when incoming bytes are available. The subclass has to
|
||||
// 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
|
||||
// return -1.
|
||||
virtual int on_write() = 0;
|
||||
|
|
|
@ -94,14 +94,13 @@ namespace {
|
|||
ssize_t send_callback(spdylay_session *session, const uint8_t *data,
|
||||
size_t length, int flags, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto spdy_session = static_cast<SpdySession *>(client->session.get());
|
||||
int rv;
|
||||
auto &wb = client->wb;
|
||||
|
||||
rv = spdy_session->sendbuf.add(data, length);
|
||||
if (rv != 0) {
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
if (wb.wleft() == 0) {
|
||||
return SPDYLAY_ERR_DEFERRED;
|
||||
}
|
||||
return length;
|
||||
|
||||
return wb.write(data, length);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -134,6 +133,8 @@ void SpdySession::on_connect() {
|
|||
(1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE;
|
||||
spdylay_submit_window_update(session_, 0, delta);
|
||||
}
|
||||
|
||||
client_->signal_write();
|
||||
}
|
||||
|
||||
void SpdySession::submit_request() {
|
||||
|
@ -147,55 +148,32 @@ void SpdySession::submit_request() {
|
|||
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
ssize_t SpdySession::on_read() {
|
||||
int rv;
|
||||
size_t nread = 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) {
|
||||
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
||||
auto rv = spdylay_session_mem_recv(session_, data, len);
|
||||
if (rv < 0) {
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (spdylay_session_want_read(session_) == 0 &&
|
||||
spdylay_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {
|
||||
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include <spdylay/spdylay.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -42,13 +41,11 @@ public:
|
|||
virtual ~SpdySession();
|
||||
virtual void on_connect();
|
||||
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 void terminate();
|
||||
void handle_window_update(int32_t stream_id, size_t recvlen);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
Client *client_;
|
||||
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,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
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));
|
||||
}
|
||||
|
||||
const Headers::value_type *get_unique_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) {
|
||||
auto j = i + 1;
|
||||
if (j == std::end(nva) || (*j).name != nv.name) {
|
||||
return &(*i);
|
||||
const Headers::value_type *get_header(const Headers &nva, const char *name) {
|
||||
const Headers::value_type *res = nullptr;
|
||||
for (auto &nv : nva) {
|
||||
if (nv.name == name) {
|
||||
res = &nv;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
return res;
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
|
||||
const Headers &headers) {
|
||||
size_t i, j;
|
||||
for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) {
|
||||
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;
|
||||
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (; i < headers.size(); ++i) {
|
||||
auto &kv = headers[i];
|
||||
if (!kv.name.empty() && kv.name.c_str()[0] != ':') {
|
||||
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
|
||||
switch (lookup_token(kv.name)) {
|
||||
case HD_COOKIE:
|
||||
case HD_CONNECTION:
|
||||
case HD_HTTP2_SETTINGS:
|
||||
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,
|
||||
const Headers &headers) {
|
||||
size_t i, j;
|
||||
for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) {
|
||||
auto &kv = headers[i];
|
||||
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;
|
||||
void build_http1_headers_from_headers(std::string &hdrs,
|
||||
const Headers &headers) {
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (; i < headers.size(); ++i) {
|
||||
auto &kv = headers[i];
|
||||
|
||||
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";
|
||||
switch (lookup_token(kv.name)) {
|
||||
case HD_CONNECTION:
|
||||
case HD_COOKIE:
|
||||
case HD_HTTP2_SETTINGS:
|
||||
case HD_KEEP_ALIVE:
|
||||
case HD_PROXY_CONNECTION:
|
||||
case HD_SERVER:
|
||||
case HD_UPGRADE:
|
||||
case HD_VIA:
|
||||
case HD_X_FORWARDED_FOR:
|
||||
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;
|
||||
}
|
||||
|
||||
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 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,
|
||||
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,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
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,
|
||||
const uint8_t *value, size_t valuelen, bool no_index);
|
||||
|
||||
// Returns the iterator to the entry in |nva| which has name |name|
|
||||
// and the |name| is uinque in the |nva|. 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.
|
||||
// Returns pointer to the entry in |nva| which has name |name|. If
|
||||
// more than one entries which have the name |name|, last occurrence
|
||||
// in |nva| is returned. If no such entry exist, returns nullptr.
|
||||
const Headers::value_type *get_header(const Headers &nva, const char *name);
|
||||
|
||||
// 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
|
||||
// disallowed headers in HTTP/2 spec and headers which require
|
||||
// special handling (i.e. via), are not copied.
|
||||
void copy_norm_headers_to_nva(std::vector<nghttp2_nv> &nva,
|
||||
const Headers &headers);
|
||||
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
|
||||
|
||||
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
|
||||
// |headers|. Certain headers, which requires special handling
|
||||
// (i.e. via and cookie), are not appended.
|
||||
void build_http1_headers_from_norm_headers(std::string &hdrs,
|
||||
const Headers &headers);
|
||||
void build_http1_headers_from_headers(std::string &hdrs,
|
||||
const Headers &headers);
|
||||
|
||||
// Return positive window_size_increment if WINDOW_UPDATE should be
|
||||
// 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.
|
||||
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 nghttp2
|
||||
|
|
|
@ -100,55 +100,14 @@ void test_http2_add_header(void) {
|
|||
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) {
|
||||
auto nva = Headers{{"alpha", "1"},
|
||||
{"bravo", "2"},
|
||||
{"bravo", "3"},
|
||||
{"charlie", "4"},
|
||||
{"delta", "5"},
|
||||
{"echo", "6"}};
|
||||
{"echo", "6"},
|
||||
{"content-length", "7"}};
|
||||
const Headers::value_type *rv;
|
||||
rv = http2::get_header(nva, "delta");
|
||||
CU_ASSERT(rv != nullptr);
|
||||
|
@ -160,6 +119,12 @@ void test_http2_get_header(void) {
|
|||
|
||||
rv = http2::get_header(nva, "foxtrot");
|
||||
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 {
|
||||
|
@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true},
|
|||
{"zulu", "12"}};
|
||||
} // namespace
|
||||
|
||||
void test_http2_copy_norm_headers_to_nva(void) {
|
||||
void test_http2_copy_headers_to_nva(void) {
|
||||
std::vector<nghttp2_nv> nva;
|
||||
http2::copy_norm_headers_to_nva(nva, headers);
|
||||
CU_ASSERT(7 == nva.size());
|
||||
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
|
||||
http2::copy_headers_to_nva(nva, headers);
|
||||
CU_ASSERT(9 == nva.size());
|
||||
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
|
||||
for (size_t i = 0; i < ans.size(); ++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;
|
||||
http2::build_http1_headers_from_norm_headers(hdrs, headers);
|
||||
http2::build_http1_headers_from_headers(hdrs, headers);
|
||||
CU_ASSERT(hdrs == "Alpha: 0\r\n"
|
||||
"Bravo: 1\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: 9\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) {
|
||||
|
@ -270,4 +226,68 @@ void test_http2_parse_http_status_code(void) {
|
|||
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
|
||||
|
|
|
@ -28,14 +28,17 @@
|
|||
namespace shrpx {
|
||||
|
||||
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_copy_norm_headers_to_nva(void);
|
||||
void test_http2_build_http1_headers_from_norm_headers(void);
|
||||
void test_http2_copy_headers_to_nva(void);
|
||||
void test_http2_build_http1_headers_from_headers(void);
|
||||
void test_http2_lws(void);
|
||||
void test_http2_rewrite_location_uri(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
|
||||
|
||||
|
|
|
@ -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 "util_test.h"
|
||||
#include "nghttp2_gzip_test.h"
|
||||
#include "ringbuf_test.h"
|
||||
#include "shrpx_config.h"
|
||||
|
||||
static int init_suite1(void) { return 0; }
|
||||
|
||||
|
@ -51,6 +53,8 @@ int main(int argc, char *argv[]) {
|
|||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
|
||||
shrpx::create_config();
|
||||
|
||||
// initialize the CUnit test registry
|
||||
if (CUE_SUCCESS != CU_initialize_registry())
|
||||
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",
|
||||
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_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_copy_norm_headers_to_nva",
|
||||
shrpx::test_http2_copy_norm_headers_to_nva) ||
|
||||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
|
||||
shrpx::test_http2_build_http1_headers_from_norm_headers) ||
|
||||
!CU_add_test(pSuite, "http2_copy_headers_to_nva",
|
||||
shrpx::test_http2_copy_headers_to_nva) ||
|
||||
!CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
|
||||
shrpx::test_http2_build_http1_headers_from_headers) ||
|
||||
!CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
|
||||
!CU_add_test(pSuite, "http2_rewrite_location_uri",
|
||||
shrpx::test_http2_rewrite_location_uri) ||
|
||||
!CU_add_test(pSuite, "http2_parse_http_status_code",
|
||||
shrpx::test_http2_parse_http_status_code) ||
|
||||
!CU_add_test(pSuite, "downstream_normalize_request_headers",
|
||||
shrpx::test_downstream_normalize_request_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_normalize_response_headers",
|
||||
shrpx::test_downstream_normalize_response_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_get_norm_request_header",
|
||||
shrpx::test_downstream_get_norm_request_header) ||
|
||||
!CU_add_test(pSuite, "downstream_get_norm_response_header",
|
||||
shrpx::test_downstream_get_norm_response_header) ||
|
||||
!CU_add_test(pSuite, "http2_index_header",
|
||||
shrpx::test_http2_index_header) ||
|
||||
!CU_add_test(pSuite, "http2_lookup_token",
|
||||
shrpx::test_http2_lookup_token) ||
|
||||
!CU_add_test(pSuite, "http2_check_http2_pseudo_header",
|
||||
shrpx::test_http2_check_http2_pseudo_header) ||
|
||||
!CU_add_test(pSuite, "http2_http2_header_allowed",
|
||||
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",
|
||||
shrpx::test_downstream_crumble_request_cookie) ||
|
||||
!CU_add_test(pSuite, "downstream_assemble_request_cookie",
|
||||
shrpx::test_downstream_assemble_request_cookie) ||
|
||||
!CU_add_test(
|
||||
pSuite, "downstream_rewrite_norm_location_response_header",
|
||||
shrpx::test_downstream_rewrite_norm_location_response_header) ||
|
||||
!CU_add_test(pSuite, "downstream_rewrite_location_response_header",
|
||||
shrpx::test_downstream_rewrite_location_response_header) ||
|
||||
!CU_add_test(pSuite, "config_parse_config_str_list",
|
||||
shrpx::test_shrpx_config_parse_config_str_list) ||
|
||||
!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_http_date", shrpx::test_util_http_date) ||
|
||||
!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();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
|
331
src/shrpx.cc
331
src/shrpx.cc
|
@ -48,7 +48,7 @@
|
|||
#include <openssl/err.h>
|
||||
#include <openssl/conf.h>
|
||||
|
||||
#include <event2/listener.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
|
@ -57,6 +57,7 @@
|
|||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_accept_handler.h"
|
||||
#include "util.h"
|
||||
#include "app_helper.h"
|
||||
#include "ssl.h"
|
||||
|
@ -82,13 +83,14 @@ const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
|
|||
// binary is listening to.
|
||||
#define ENV_PORT "NGHTTPX_PORT"
|
||||
|
||||
namespace {
|
||||
void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int addrlen,
|
||||
void *arg) {
|
||||
auto handler = static_cast<ListenHandler *>(arg);
|
||||
handler->accept_connection(fd, addr, addrlen);
|
||||
}
|
||||
} // namespace
|
||||
// namespace {
|
||||
// void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int
|
||||
// addrlen,
|
||||
// void *arg) {
|
||||
// auto handler = static_cast<ListenHandler *>(arg);
|
||||
// handler->accept_connection(fd, addr, addrlen);
|
||||
// }
|
||||
// } // namespace
|
||||
|
||||
namespace {
|
||||
bool is_ipv6_numeric_addr(const char *host) {
|
||||
|
@ -145,28 +147,8 @@ int resolve_hostname(sockaddr_union *addr, size_t *addrlen,
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void evlistener_errorcb(evconnlistener *listener, void *ptr) {
|
||||
LOG(ERROR) << "Accepting incoming connection failed";
|
||||
|
||||
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) {
|
||||
std::unique_ptr<AcceptHandler> create_acceptor(ListenHandler *handler,
|
||||
int family) {
|
||||
{
|
||||
auto envfd =
|
||||
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) {
|
||||
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
|
||||
|
@ -219,7 +201,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
return nullptr;
|
||||
}
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
@ -229,7 +212,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
close(fd);
|
||||
continue;
|
||||
}
|
||||
evutil_make_socket_nonblocking(fd);
|
||||
|
||||
#ifdef IPV6_V6ONLY
|
||||
if (family == AF_INET6) {
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
||||
|
@ -239,7 +222,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
}
|
||||
}
|
||||
#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;
|
||||
}
|
||||
close(fd);
|
||||
|
@ -270,7 +254,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
|
||||
LOG(NOTICE) << "Listening on " << host << ", port " << get_config()->port;
|
||||
|
||||
return new_evlistener(handler, fd);
|
||||
return util::make_unique<AcceptHandler>(fd, handler);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -317,8 +301,8 @@ void save_pid() {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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 {
|
||||
void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void exec_binary_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
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);
|
||||
size_t envidx = 0;
|
||||
|
||||
auto evlistener4 = listener_handler->get_evlistener4();
|
||||
if (evlistener4) {
|
||||
auto acceptor4 = listener_handler->get_acceptor4();
|
||||
if (acceptor4) {
|
||||
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());
|
||||
}
|
||||
|
||||
auto evlistener6 = listener_handler->get_evlistener6();
|
||||
if (evlistener6) {
|
||||
auto acceptor6 = listener_handler->get_acceptor6();
|
||||
if (acceptor6) {
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -423,14 +407,15 @@ void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
|
||||
int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
LOG(NOTICE) << "Graceful shutdown signal received";
|
||||
|
||||
worker_config->graceful_shutdown = true;
|
||||
|
||||
listener_handler->disable_evlistener();
|
||||
listener_handler->disable_acceptor();
|
||||
|
||||
// After disabling accepting new connection, disptach incoming
|
||||
// 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->graceful_shutdown_worker();
|
||||
|
||||
// We have accepted all pending connections. Shutdown main event
|
||||
// loop.
|
||||
ev_break(loop);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -449,8 +438,8 @@ std::unique_ptr<std::string> generate_time() {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void refresh_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
auto worker_stat = listener_handler->get_worker_stat();
|
||||
|
||||
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.
|
||||
if (get_config()->num_worker == 1 && worker_config->graceful_shutdown &&
|
||||
(!worker_stat || worker_stat->num_connections == 0)) {
|
||||
event_base_loopbreak(listener_handler->get_evbase());
|
||||
ev_break(loop);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int event_loop() {
|
||||
int rv;
|
||||
|
||||
auto evbase = event_base_new();
|
||||
if (!evbase) {
|
||||
LOG(FATAL) << "event_base_new() failed";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
auto loop = EV_DEFAULT;
|
||||
SSL_CTX *sv_ssl_ctx, *cl_ssl_ctx;
|
||||
|
||||
if (get_config()->client_mode) {
|
||||
|
@ -487,7 +470,8 @@ int event_loop() {
|
|||
: 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 (daemon(0, 0) == -1) {
|
||||
auto error = errno;
|
||||
|
@ -503,22 +487,23 @@ int event_loop() {
|
|||
save_pid();
|
||||
}
|
||||
|
||||
auto evlistener6 = create_evlistener(listener_handler, AF_INET6);
|
||||
auto evlistener4 = create_evlistener(listener_handler, AF_INET);
|
||||
if (!evlistener6 && !evlistener4) {
|
||||
auto acceptor6 = create_acceptor(listener_handler.get(), AF_INET6);
|
||||
auto acceptor4 = create_acceptor(listener_handler.get(), AF_INET);
|
||||
if (!acceptor6 && !acceptor4) {
|
||||
LOG(FATAL) << "Failed to listen on address " << get_config()->host.get()
|
||||
<< ", port " << get_config()->port;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
listener_handler->set_evlistener4(evlistener4);
|
||||
listener_handler->set_evlistener6(evlistener6);
|
||||
listener_handler->set_acceptor4(std::move(acceptor4));
|
||||
listener_handler->set_acceptor6(std::move(acceptor6));
|
||||
|
||||
// ListenHandler loads private key, and we listen on a priveleged port.
|
||||
// After that, we drop the root privileges if needed.
|
||||
drop_privileges();
|
||||
|
||||
#ifndef NOTHREADS
|
||||
int rv;
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
||||
|
@ -545,83 +530,35 @@ int event_loop() {
|
|||
}
|
||||
#endif // !NOTHREADS
|
||||
|
||||
auto reopen_log_signal_event = evsignal_new(
|
||||
evbase, REOPEN_LOG_SIGNAL, reopen_log_signal_cb, listener_handler);
|
||||
ev_signal reopen_log_sig;
|
||||
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) {
|
||||
LOG(ERROR) << "evsignal_new failed";
|
||||
} else {
|
||||
rv = event_add(reopen_log_signal_event, nullptr);
|
||||
if (rv < 0) {
|
||||
LOG(ERROR) << "event_add for reopen_log_signal_event failed";
|
||||
}
|
||||
}
|
||||
ev_signal exec_bin_sig;
|
||||
ev_signal_init(&exec_bin_sig, exec_binary_signal_cb, EXEC_BINARY_SIGNAL);
|
||||
exec_bin_sig.data = listener_handler.get();
|
||||
ev_signal_start(loop, &exec_bin_sig);
|
||||
|
||||
auto exec_binary_signal_event = evsignal_new(
|
||||
evbase, EXEC_BINARY_SIGNAL, exec_binary_signal_cb, listener_handler);
|
||||
rv = event_add(exec_binary_signal_event, nullptr);
|
||||
ev_signal graceful_shutdown_sig;
|
||||
ev_signal_init(&graceful_shutdown_sig, graceful_shutdown_signal_cb,
|
||||
GRACEFUL_SHUTDOWN_SIGNAL);
|
||||
graceful_shutdown_sig.data = listener_handler.get();
|
||||
ev_signal_start(loop, &graceful_shutdown_sig);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(FATAL) << "event_add for exec_binary_signal_event failed";
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
ev_timer refresh_timer;
|
||||
ev_timer_init(&refresh_timer, refresh_cb, 0., 1.);
|
||||
refresh_timer.data = listener_handler.get();
|
||||
ev_timer_again(loop, &refresh_timer);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Entering event loop";
|
||||
}
|
||||
event_base_loop(evbase, 0);
|
||||
|
||||
ev_run(loop, 0);
|
||||
|
||||
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;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -673,26 +610,26 @@ void fill_default_config() {
|
|||
mod_config()->cert_file = nullptr;
|
||||
|
||||
// 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
|
||||
mod_config()->upstream_read_timeout = {180, 0};
|
||||
mod_config()->upstream_read_timeout = 180.;
|
||||
|
||||
// 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
|
||||
mod_config()->downstream_read_timeout = {180, 0};
|
||||
mod_config()->downstream_write_timeout = {30, 0};
|
||||
mod_config()->downstream_read_timeout = 180.;
|
||||
mod_config()->downstream_write_timeout = 30.;
|
||||
|
||||
// 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
|
||||
mod_config()->stream_write_timeout = {0, 0};
|
||||
mod_config()->stream_write_timeout = 0.;
|
||||
|
||||
// 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
|
||||
// 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()->syslog_facility = LOG_DAEMON;
|
||||
// Default accept() backlog
|
||||
mod_config()->backlog = -1;
|
||||
mod_config()->backlog = SOMAXCONN;
|
||||
mod_config()->ciphers = nullptr;
|
||||
mod_config()->http2_proxy = 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_port = 0;
|
||||
mod_config()->downstream_http_proxy_addrlen = 0;
|
||||
mod_config()->rate_limit_cfg = nullptr;
|
||||
mod_config()->read_rate = 0;
|
||||
mod_config()->read_burst = 1 << 30;
|
||||
mod_config()->read_burst = 0;
|
||||
mod_config()->write_rate = 0;
|
||||
mod_config()->write_burst = 0;
|
||||
mod_config()->worker_read_rate = 0;
|
||||
mod_config()->worker_read_burst = 0;
|
||||
mod_config()->worker_write_rate = 0;
|
||||
mod_config()->worker_write_burst = 0;
|
||||
mod_config()->worker_rate_limit_cfg = nullptr;
|
||||
mod_config()->verify_client = false;
|
||||
mod_config()->verify_client_cacert = nullptr;
|
||||
mod_config()->client_private_key_file = nullptr;
|
||||
|
@ -777,19 +712,20 @@ void fill_default_config() {
|
|||
mod_config()->argc = 0;
|
||||
mod_config()->argv = nullptr;
|
||||
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 {
|
||||
size_t get_rate_limit(size_t rate_limit) {
|
||||
if (rate_limit == 0) {
|
||||
return EV_RATE_LIMIT_MAX;
|
||||
} else {
|
||||
return rate_limit;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
// namespace {
|
||||
// size_t get_rate_limit(size_t rate_limit) {
|
||||
// if (rate_limit == 0) {
|
||||
// return EV_RATE_LIMIT_MAX;
|
||||
// } else {
|
||||
// return rate_limit;
|
||||
// }
|
||||
// }
|
||||
// } // namespace
|
||||
|
||||
namespace {
|
||||
void print_version(std::ostream &out) {
|
||||
|
@ -864,10 +800,8 @@ Performance:
|
|||
Default: )" << get_config()->read_rate << R"(
|
||||
--read-burst=<SIZE>
|
||||
Set maximum read burst size on frontend
|
||||
connection. Setting 0 does not work, but it is
|
||||
not a problem because --read-rate=0 will give
|
||||
unlimited read rate regardless of this option
|
||||
value.
|
||||
connection. Setting 0 to this option means read
|
||||
burst size is unlimited.
|
||||
Default: )" << get_config()->read_burst << R"(
|
||||
--write-rate=<RATE>
|
||||
Set maximum average write rate on frontend
|
||||
|
@ -882,22 +816,26 @@ Performance:
|
|||
--worker-read-rate=<RATE>
|
||||
Set maximum average read rate on frontend
|
||||
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"(
|
||||
--worker-read-burst=<SIZE>
|
||||
Set maximum read burst size on frontend
|
||||
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"(
|
||||
--worker-write-rate=<RATE>
|
||||
Set maximum average write rate on frontend
|
||||
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"(
|
||||
--worker-write-burst=<SIZE>
|
||||
Set maximum write burst size on frontend
|
||||
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"(
|
||||
--worker-frontend-connections=<NUM>
|
||||
Set maximum number of simultaneous connections
|
||||
|
@ -906,55 +844,61 @@ Performance:
|
|||
--backend-http1-connections-per-host=<NUM>
|
||||
Set maximum number of backend concurrent HTTP/1
|
||||
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: )"
|
||||
<< 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:
|
||||
--frontend-http2-read-timeout=<SEC>
|
||||
Specify read timeout for HTTP/2 and SPDY frontend
|
||||
connection.
|
||||
Default: )"
|
||||
<< get_config()->http2_upstream_read_timeout.tv_sec << R"(
|
||||
Default: )" << get_config()->http2_upstream_read_timeout
|
||||
<< R"(
|
||||
--frontend-read-timeout=<SEC>
|
||||
Specify read timeout for HTTP/1.1 frontend
|
||||
connection.
|
||||
Default: )" << get_config()->upstream_read_timeout.tv_sec
|
||||
<< R"(
|
||||
Default: )" << get_config()->upstream_read_timeout << R"(
|
||||
--frontend-write-timeout=<SEC>
|
||||
Specify write timeout for all frontend
|
||||
connections.
|
||||
Default: )" << get_config()->upstream_write_timeout.tv_sec
|
||||
<< R"(
|
||||
Default: )" << get_config()->upstream_write_timeout << R"(
|
||||
--stream-read-timeout=<SEC>
|
||||
Specify read timeout for HTTP/2 and SPDY streams.
|
||||
0 means no timeout.
|
||||
Default: )" << get_config()->stream_read_timeout.tv_sec
|
||||
<< R"(
|
||||
Default: )" << get_config()->stream_read_timeout << R"(
|
||||
--stream-write-timeout=<SEC>
|
||||
Specify write timeout for HTTP/2 and SPDY
|
||||
streams. 0 means no timeout.
|
||||
Default: )" << get_config()->stream_write_timeout.tv_sec
|
||||
<< R"(
|
||||
Default: )" << get_config()->stream_write_timeout << R"(
|
||||
--backend-read-timeout=<SEC>
|
||||
Specify read timeout for backend connection.
|
||||
Default: )" << get_config()->downstream_read_timeout.tv_sec
|
||||
<< R"(
|
||||
Default: )" << get_config()->downstream_read_timeout << R"(
|
||||
--backend-write-timeout=<SEC>
|
||||
Specify write timeout for backend connection.
|
||||
Default: )"
|
||||
<< get_config()->downstream_write_timeout.tv_sec << R"(
|
||||
Default: )" << get_config()->downstream_write_timeout
|
||||
<< R"(
|
||||
--backend-keep-alive-timeout=<SEC>
|
||||
Specify keep-alive timeout for backend
|
||||
connection.
|
||||
Default: )"
|
||||
<< get_config()->downstream_idle_read_timeout.tv_sec << R"(
|
||||
Default: )" << get_config()->downstream_idle_read_timeout
|
||||
<< R"(
|
||||
--listener-disable-timeout=<SEC>
|
||||
After accepting connection failed, connection
|
||||
listener is disabled for a given time in seconds.
|
||||
Specifying 0 disables this feature.
|
||||
Default: )"
|
||||
<< get_config()->listener_disable_timeout.tv_sec << R"(
|
||||
Default: )" << get_config()->listener_disable_timeout
|
||||
<< R"(
|
||||
|
||||
SSL/TLS:
|
||||
--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},
|
||||
{"strip-incoming-x-forwarded-for", no_argument, &flag, 65},
|
||||
{"accesslog-format", required_argument, &flag, 66},
|
||||
{"backend-http1-connections-per-frontend", required_argument, &flag,
|
||||
67},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
|
@ -1587,6 +1533,11 @@ int main(int argc, char **argv) {
|
|||
// --accesslog-format
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, optarg);
|
||||
break;
|
||||
case 67:
|
||||
// --backend-http1-connections-per-frontend
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
|
||||
optarg);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1829,17 +1780,17 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
}
|
||||
|
||||
mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new(
|
||||
get_rate_limit(get_config()->read_rate),
|
||||
get_rate_limit(get_config()->read_burst),
|
||||
get_rate_limit(get_config()->write_rate),
|
||||
get_rate_limit(get_config()->write_burst), nullptr);
|
||||
// mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new(
|
||||
// get_rate_limit(get_config()->read_rate),
|
||||
// get_rate_limit(get_config()->read_burst),
|
||||
// get_rate_limit(get_config()->write_rate),
|
||||
// get_rate_limit(get_config()->write_burst), nullptr);
|
||||
|
||||
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_burst),
|
||||
get_rate_limit(get_config()->worker_write_rate),
|
||||
get_rate_limit(get_config()->worker_write_burst), nullptr);
|
||||
// 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_burst),
|
||||
// get_rate_limit(get_config()->worker_write_rate),
|
||||
// get_rate_limit(get_config()->worker_write_burst), nullptr);
|
||||
|
||||
if (get_config()->upstream_frame_debug) {
|
||||
// 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"
|
||||
#endif // HAVE_SPDYLAY
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
void upstream_readcb(bufferevent *bev, void *arg) {
|
||||
auto handler = static_cast<ClientHandler *>(arg);
|
||||
auto upstream = handler->get_upstream();
|
||||
if (upstream) {
|
||||
upstream->reset_timeouts();
|
||||
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto handler = static_cast<ClientHandler *>(w->data);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void upstream_writecb(bufferevent *bev, void *arg) {
|
||||
auto handler = static_cast<ClientHandler *>(arg);
|
||||
auto upstream = handler->get_upstream();
|
||||
if (upstream) {
|
||||
upstream->reset_timeouts();
|
||||
}
|
||||
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||
auto handler = static_cast<ClientHandler *>(w->data);
|
||||
|
||||
handler->update_last_write_time();
|
||||
|
||||
// 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()) {
|
||||
if (handler->do_write() != 0) {
|
||||
delete handler;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!upstream) {
|
||||
return;
|
||||
}
|
||||
int rv = upstream->on_write();
|
||||
if (rv != 0) {
|
||||
delete handler;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void upstream_eventcb(bufferevent *bev, short events, void *arg) {
|
||||
auto handler = static_cast<ClientHandler *>(arg);
|
||||
bool finish = false;
|
||||
if (events & BEV_EVENT_EOF) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, handler) << "EOF";
|
||||
int ClientHandler::read_clear() {
|
||||
ev_timer_again(loop_, &rt_);
|
||||
|
||||
for (;;) {
|
||||
if (rb_.rleft() && on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
finish = true;
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, handler) << "Network error: " << evutil_socket_error_to_string(
|
||||
EVUTIL_SOCKET_ERROR());
|
||||
rb_.reset();
|
||||
struct iovec iov[2];
|
||||
auto iovcnt = rb_.wiovec(iov);
|
||||
iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail());
|
||||
if (iovcnt == 0) {
|
||||
break;
|
||||
}
|
||||
finish = true;
|
||||
}
|
||||
if (events & BEV_EVENT_TIMEOUT) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, handler) << "Time out";
|
||||
}
|
||||
finish = true;
|
||||
}
|
||||
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";
|
||||
|
||||
ssize_t nread;
|
||||
while ((nread = readv(fd_, iov, iovcnt)) == -1 && errno == EINTR)
|
||||
;
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
if (handler->validate_next_proto() != 0) {
|
||||
delete handler;
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
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())) {
|
||||
CLOG(INFO, handler) << "SSL/TLS session reused";
|
||||
|
||||
ssize_t nwrite;
|
||||
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 {
|
||||
void upstream_http2_connhd_readcb(bufferevent *bev, void *arg) {
|
||||
// This callback assumes upstream is Http2Upstream.
|
||||
auto handler = static_cast<ClientHandler *>(arg);
|
||||
if (handler->on_http2_connhd_read() != 0) {
|
||||
delete handler;
|
||||
wlimit_.stopw();
|
||||
ev_timer_stop(loop_, &wt_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ClientHandler::upstream_noop() { return 0; }
|
||||
|
||||
int ClientHandler::upstream_read() {
|
||||
assert(upstream_);
|
||||
if (upstream_->on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void upstream_http1_connhd_readcb(bufferevent *bev, void *arg) {
|
||||
// This callback assumes upstream is HttpsUpstream.
|
||||
auto handler = static_cast<ClientHandler *>(arg);
|
||||
if (handler->on_http1_connhd_read() != 0) {
|
||||
delete handler;
|
||||
int ClientHandler::upstream_write() {
|
||||
assert(upstream_);
|
||||
if (upstream_->on_write() != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ClientHandler::ClientHandler(bufferevent *bev,
|
||||
bufferevent_rate_limit_group *rate_limit_group,
|
||||
int fd, SSL *ssl, const char *ipaddr,
|
||||
const char *port, WorkerStat *worker_stat,
|
||||
if (get_should_close_after_write() && wb_.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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)
|
||||
: ipaddr_(ipaddr), port_(port), dconn_pool_(dconn_pool), bev_(bev),
|
||||
http2session_(nullptr), ssl_(ssl), reneg_shutdown_timerev_(nullptr),
|
||||
: ipaddr_(ipaddr), port_(port),
|
||||
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),
|
||||
left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN), fd_(fd),
|
||||
should_close_after_write_(false), tls_handshake_(false),
|
||||
tls_renegotiation_(false) {
|
||||
int rv;
|
||||
|
||||
++worker_stat->num_connections;
|
||||
|
||||
rv = bufferevent_set_rate_limit(bev_, get_config()->rate_limit_cfg);
|
||||
if (rv == -1) {
|
||||
CLOG(FATAL, this) << "bufferevent_set_rate_limit() failed";
|
||||
}
|
||||
ev_io_init(&wev_, writecb, fd_, EV_WRITE);
|
||||
ev_io_init(&rev_, readcb, fd_, EV_READ);
|
||||
|
||||
rv = bufferevent_add_to_rate_limit_group(bev_, rate_limit_group);
|
||||
if (rv == -1) {
|
||||
CLOG(FATAL, this) << "bufferevent_add_to_rate_limit_group() failed";
|
||||
}
|
||||
wev_.data = this;
|
||||
rev_.data = this;
|
||||
|
||||
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_) {
|
||||
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 {
|
||||
// For non-TLS version, first create HttpsUpstream. It may be
|
||||
// upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
|
||||
// connection.
|
||||
upstream_ = util::make_unique<HttpsUpstream>(this);
|
||||
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;
|
||||
|
||||
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
|
||||
// it and make it loopbreak when output is zero.
|
||||
if (worker_config->graceful_shutdown && worker_stat_->num_connections == 0) {
|
||||
event_base_loopbreak(get_evbase());
|
||||
}
|
||||
|
||||
if (reneg_shutdown_timerev_) {
|
||||
event_free(reneg_shutdown_timerev_);
|
||||
ev_break(loop_);
|
||||
}
|
||||
|
||||
if (ssl_) {
|
||||
|
@ -227,11 +516,6 @@ ClientHandler::~ClientHandler() {
|
|||
SSL_shutdown(ssl_);
|
||||
}
|
||||
|
||||
bufferevent_remove_from_rate_limit_group(bev_);
|
||||
|
||||
util::bev_disable_unless(bev_, EV_READ | EV_WRITE);
|
||||
bufferevent_free(bev_);
|
||||
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
}
|
||||
|
@ -245,21 +529,22 @@ ClientHandler::~ClientHandler() {
|
|||
|
||||
Upstream *ClientHandler::get_upstream() { return upstream_.get(); }
|
||||
|
||||
bufferevent *ClientHandler::get_bev() const { return bev_; }
|
||||
|
||||
event_base *ClientHandler::get_evbase() const {
|
||||
return bufferevent_get_base(bev_);
|
||||
struct ev_loop *ClientHandler::get_loop() const {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
void ClientHandler::set_bev_cb(bufferevent_data_cb readcb,
|
||||
bufferevent_data_cb writecb,
|
||||
bufferevent_event_cb eventcb) {
|
||||
bufferevent_setcb(bev_, readcb, writecb, eventcb, this);
|
||||
void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) {
|
||||
ev_timer_set(&rt_, 0., t);
|
||||
if (ev_is_active(&rt_)) {
|
||||
ev_timer_again(loop_, &rt_);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientHandler::set_upstream_timeouts(const timeval *read_timeout,
|
||||
const timeval *write_timeout) {
|
||||
bufferevent_set_timeouts(bev_, read_timeout, write_timeout);
|
||||
void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
|
||||
ev_timer_set(&wt_, 0., t);
|
||||
if (ev_is_active(&wt_)) {
|
||||
ev_timer_again(loop_, &wt_);
|
||||
}
|
||||
}
|
||||
|
||||
int ClientHandler::validate_next_proto() {
|
||||
|
@ -268,7 +553,8 @@ int ClientHandler::validate_next_proto() {
|
|||
int rv;
|
||||
|
||||
// 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);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (next_proto) {
|
||||
|
@ -284,8 +570,7 @@ int ClientHandler::validate_next_proto() {
|
|||
(next_proto_len == sizeof("h2-16") - 1 &&
|
||||
memcmp("h2-16", next_proto, next_proto_len) == 0)) {
|
||||
|
||||
set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb,
|
||||
upstream_eventcb);
|
||||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||
|
||||
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
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (on_http2_connhd_read() != 0) {
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -331,7 +616,7 @@ int ClientHandler::validate_next_proto() {
|
|||
// At this point, input buffer is already filled with some
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (upstream_->on_read() != 0) {
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -345,7 +630,7 @@ int ClientHandler::validate_next_proto() {
|
|||
// At this point, input buffer is already filled with some
|
||||
// bytes. The read callback is not called until new data
|
||||
// come. So consume input buffer here.
|
||||
if (upstream_->on_read() != 0) {
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -370,7 +655,7 @@ int ClientHandler::validate_next_proto() {
|
|||
// At this point, input buffer is already filled with some bytes.
|
||||
// The read callback is not called until new data come. So consume
|
||||
// input buffer here.
|
||||
if (upstream_->on_read() != 0) {
|
||||
if (on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -382,101 +667,11 @@ int ClientHandler::validate_next_proto() {
|
|||
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_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;
|
||||
}
|
||||
int ClientHandler::on_read() { return on_read_(*this); }
|
||||
int ClientHandler::on_write() { return on_write_(*this); }
|
||||
|
||||
const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; }
|
||||
|
||||
|
@ -519,7 +714,7 @@ ClientHandler::get_downstream_connection() {
|
|||
dconn = util::make_unique<Http2DownstreamConnection>(dconn_pool_,
|
||||
http2session_);
|
||||
} else {
|
||||
dconn = util::make_unique<HttpDownstreamConnection>(dconn_pool_);
|
||||
dconn = util::make_unique<HttpDownstreamConnection>(dconn_pool_, loop_);
|
||||
}
|
||||
dconn->set_client_handler(this);
|
||||
return dconn;
|
||||
|
@ -535,10 +730,6 @@ ClientHandler::get_downstream_connection() {
|
|||
return dconn;
|
||||
}
|
||||
|
||||
size_t ClientHandler::get_outbuf_length() {
|
||||
return evbuffer_get_length(bufferevent_get_output(bev_));
|
||||
}
|
||||
|
||||
SSL *ClientHandler::get_ssl() const { return ssl_; }
|
||||
|
||||
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
|
||||
// just use library default for now.
|
||||
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 rv;
|
||||
auto upstream = util::make_unique<Http2Upstream>(this);
|
||||
if (upstream->upgrade_upstream(http) != 0) {
|
||||
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
|
||||
// support aliasing for h2, but we just use library default for now.
|
||||
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"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
||||
"\r\n";
|
||||
rv = bufferevent_write(bev_, res, sizeof(res) - 1);
|
||||
if (rv != 0) {
|
||||
CLOG(FATAL, this) << "bufferevent_write() faild";
|
||||
return -1;
|
||||
}
|
||||
wb_.write(res, sizeof(res) - 1);
|
||||
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_; }
|
||||
|
||||
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) {
|
||||
if (tls_renegotiation_ == false) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -622,13 +797,7 @@ void ClientHandler::set_tls_renegotiation(bool f) {
|
|||
<< "Start shutdown timer now.";
|
||||
}
|
||||
|
||||
reneg_shutdown_timerev_ = evtimer_new(get_evbase(), shutdown_cb, this);
|
||||
event_priority_set(reneg_shutdown_timerev_, 0);
|
||||
|
||||
timeval timeout = {0, 0};
|
||||
|
||||
// TODO What to do if this failed?
|
||||
evtimer_add(reneg_shutdown_timerev_, &timeout);
|
||||
ev_timer_start(loop_, &reneg_shutdown_timer_);
|
||||
}
|
||||
tls_renegotiation_ = f;
|
||||
}
|
||||
|
@ -645,14 +814,12 @@ ssize_t ClientHandler::get_write_limit() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
timeval tv;
|
||||
if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) {
|
||||
auto now = util::to_time64(tv);
|
||||
if (now - last_write_time_ > 1000000) {
|
||||
// Time out, use small record size
|
||||
warmup_writelen_ = 0;
|
||||
return SHRPX_SMALL_WRITE_LIMIT;
|
||||
}
|
||||
auto t = ev_now(loop_);
|
||||
|
||||
if (t - last_write_time_ > 1.0) {
|
||||
// Time out, use small record size
|
||||
warmup_writelen_ = 0;
|
||||
return SHRPX_SMALL_WRITE_LIMIT;
|
||||
}
|
||||
|
||||
// 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() {
|
||||
timeval tv;
|
||||
if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) {
|
||||
last_write_time_ = util::to_time64(tv);
|
||||
}
|
||||
last_write_time_ = ev_now(loop_);
|
||||
}
|
||||
|
||||
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_; }
|
||||
|
||||
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
|
||||
|
|
|
@ -29,11 +29,15 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "shrpx_rate_limit.h"
|
||||
#include "ringbuf.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class Upstream;
|
||||
|
@ -44,21 +48,41 @@ class ConnectBlocker;
|
|||
class DownstreamConnectionPool;
|
||||
struct WorkerStat;
|
||||
|
||||
typedef RingBuf<65536> UpstreamBuf;
|
||||
|
||||
class ClientHandler {
|
||||
public:
|
||||
ClientHandler(bufferevent *bev,
|
||||
bufferevent_rate_limit_group *rate_limit_group, int fd,
|
||||
SSL *ssl, const char *ipaddr, const char *port,
|
||||
WorkerStat *worker_stat, DownstreamConnectionPool *dconn_pool);
|
||||
ClientHandler(struct ev_loop *loop, int fd, SSL *ssl, const char *ipaddr,
|
||||
const char *port, WorkerStat *worker_stat,
|
||||
DownstreamConnectionPool *dconn_pool);
|
||||
~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_event();
|
||||
bufferevent *get_bev() const;
|
||||
event_base *get_evbase() const;
|
||||
void set_bev_cb(bufferevent_data_cb readcb, bufferevent_data_cb writecb,
|
||||
bufferevent_event_cb eventcb);
|
||||
void set_upstream_timeouts(const timeval *read_timeout,
|
||||
const timeval *write_timeout);
|
||||
int on_write();
|
||||
|
||||
struct ev_loop *get_loop() const;
|
||||
void reset_upstream_read_timeout(ev_tstamp t);
|
||||
void reset_upstream_write_timeout(ev_tstamp t);
|
||||
int validate_next_proto();
|
||||
const std::string &get_ipaddr() const;
|
||||
const std::string &get_port() const;
|
||||
|
@ -69,7 +93,6 @@ public:
|
|||
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
|
||||
void remove_downstream_connection(DownstreamConnection *dconn);
|
||||
std::unique_ptr<DownstreamConnection> get_downstream_connection();
|
||||
size_t get_outbuf_length();
|
||||
SSL *get_ssl() const;
|
||||
void set_http2_session(Http2Session *http2session);
|
||||
Http2Session *get_http2_session() const;
|
||||
|
@ -89,8 +112,6 @@ public:
|
|||
bool get_tls_handshake() const;
|
||||
void set_tls_renegotiation(bool f);
|
||||
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
|
||||
// of this chunk size is control the TLS record size. The actual
|
||||
// SSL_write() call is done under libevent control. In
|
||||
|
@ -116,20 +137,36 @@ public:
|
|||
int64_t body_bytes_sent);
|
||||
WorkerStat *get_worker_stat() const;
|
||||
|
||||
UpstreamBuf *get_wb();
|
||||
UpstreamBuf *get_rb();
|
||||
|
||||
RateLimit *get_rlimit();
|
||||
RateLimit *get_wlimit();
|
||||
|
||||
void signal_write();
|
||||
|
||||
private:
|
||||
ev_io wev_;
|
||||
ev_io rev_;
|
||||
ev_timer wt_;
|
||||
ev_timer rt_;
|
||||
ev_timer reneg_shutdown_timer_;
|
||||
std::unique_ptr<Upstream> upstream_;
|
||||
std::string ipaddr_;
|
||||
std::string port_;
|
||||
// The ALPN identifier negotiated for this connection.
|
||||
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_;
|
||||
bufferevent *bev_;
|
||||
// Shared HTTP2 session for each thread. NULL if backend is not
|
||||
// HTTP2. Not deleted by this object.
|
||||
Http2Session *http2session_;
|
||||
ConnectBlocker *http1_connect_blocker_;
|
||||
SSL *ssl_;
|
||||
event *reneg_shutdown_timerev_;
|
||||
WorkerStat *worker_stat_;
|
||||
int64_t last_write_time_;
|
||||
size_t warmup_writelen_;
|
||||
|
@ -139,6 +176,8 @@ private:
|
|||
bool should_close_after_write_;
|
||||
bool tls_handshake_;
|
||||
bool tls_renegotiation_;
|
||||
UpstreamBuf wb_;
|
||||
UpstreamBuf rb_;
|
||||
};
|
||||
|
||||
} // 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_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";
|
||||
|
||||
namespace {
|
||||
|
@ -198,7 +200,7 @@ FILE *open_file_for_write(const char *filename) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
evutil_make_socket_closeonexec(fileno(f));
|
||||
util::make_socket_closeonexec(fileno(f));
|
||||
|
||||
return f;
|
||||
}
|
||||
|
@ -421,15 +423,14 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
|
|||
}
|
||||
|
||||
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;
|
||||
|
||||
if (parse_uint(&sec, opt, optarg) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dest->tv_sec = sec;
|
||||
dest->tv_usec = 0;
|
||||
*dest = sec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -856,18 +857,22 @@ int parse_config(const char *opt, const char *optarg) {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1027,6 +1032,24 @@ int parse_config(const char *opt, const char *optarg) {
|
|||
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)) {
|
||||
return parse_timeval(&mod_config()->listener_disable_timeout, opt, optarg);
|
||||
}
|
||||
|
|
|
@ -37,9 +37,10 @@
|
|||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include <event.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
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_NO_LOCATION_REWRITE[];
|
||||
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[];
|
||||
|
||||
union sockaddr_union {
|
||||
|
@ -171,15 +173,15 @@ struct Config {
|
|||
std::vector<DownstreamAddr> downstream_addrs;
|
||||
// binary form of http proxy host and port
|
||||
sockaddr_union downstream_http_proxy_addr;
|
||||
timeval http2_upstream_read_timeout;
|
||||
timeval upstream_read_timeout;
|
||||
timeval upstream_write_timeout;
|
||||
timeval downstream_read_timeout;
|
||||
timeval downstream_write_timeout;
|
||||
timeval stream_read_timeout;
|
||||
timeval stream_write_timeout;
|
||||
timeval downstream_idle_read_timeout;
|
||||
timeval listener_disable_timeout;
|
||||
ev_tstamp http2_upstream_read_timeout;
|
||||
ev_tstamp upstream_read_timeout;
|
||||
ev_tstamp upstream_write_timeout;
|
||||
ev_tstamp downstream_read_timeout;
|
||||
ev_tstamp downstream_write_timeout;
|
||||
ev_tstamp stream_read_timeout;
|
||||
ev_tstamp stream_write_timeout;
|
||||
ev_tstamp downstream_idle_read_timeout;
|
||||
ev_tstamp listener_disable_timeout;
|
||||
std::unique_ptr<char[]> host;
|
||||
std::unique_ptr<char[]> private_key_file;
|
||||
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[]> http2_upstream_dump_request_header_file;
|
||||
std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
|
||||
// Rate limit configuration per connection
|
||||
ev_token_bucket_cfg *rate_limit_cfg;
|
||||
// Rate limit configuration per worker (thread)
|
||||
ev_token_bucket_cfg *worker_rate_limit_cfg;
|
||||
// // Rate limit configuration per connection
|
||||
// ev_token_bucket_cfg *rate_limit_cfg;
|
||||
// // Rate limit configuration per worker (thread)
|
||||
// ev_token_bucket_cfg *worker_rate_limit_cfg;
|
||||
// list of supported NPN/ALPN protocol strings in the order of
|
||||
// preference. The each element of this list is a NULL-terminated
|
||||
// string.
|
||||
|
@ -229,6 +231,7 @@ struct Config {
|
|||
size_t http2_upstream_connection_window_bits;
|
||||
size_t http2_downstream_connection_window_bits;
|
||||
size_t downstream_connections_per_host;
|
||||
size_t downstream_connections_per_frontend;
|
||||
// actual size of downstream_http_proxy_addr
|
||||
size_t downstream_http_proxy_addrlen;
|
||||
size_t read_rate;
|
||||
|
|
|
@ -27,55 +27,35 @@
|
|||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const int INITIAL_SLEEP = 2;
|
||||
const ev_tstamp INITIAL_SLEEP = 2.;
|
||||
} // namespace
|
||||
|
||||
ConnectBlocker::ConnectBlocker() : timerev_(nullptr), sleep_(INITIAL_SLEEP) {}
|
||||
|
||||
ConnectBlocker::~ConnectBlocker() {
|
||||
if (timerev_) {
|
||||
event_free(timerev_);
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
LOG(INFO) << "unblock downstream connection";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int ConnectBlocker::init(event_base *evbase) {
|
||||
timerev_ = evtimer_new(evbase, connect_blocker_cb, this);
|
||||
|
||||
if (timerev_ == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
ConnectBlocker::ConnectBlocker(struct ev_loop *loop)
|
||||
: loop_(loop), sleep_(INITIAL_SLEEP) {
|
||||
ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
|
||||
}
|
||||
|
||||
bool ConnectBlocker::blocked() const {
|
||||
return evtimer_pending(timerev_, nullptr);
|
||||
}
|
||||
ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
|
||||
|
||||
bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
|
||||
|
||||
void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
|
||||
|
||||
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_;
|
||||
|
||||
timeval t = {sleep_, 0};
|
||||
|
||||
rv = evtimer_add(timerev_, &t);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(ERROR) << "evtimer_add for ConnectBlocker timerev_ failed";
|
||||
}
|
||||
ev_timer_set(&timer_, sleep_, 0.);
|
||||
ev_timer_start(loop_, &timer_);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -27,16 +27,15 @@
|
|||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class ConnectBlocker {
|
||||
public:
|
||||
ConnectBlocker();
|
||||
ConnectBlocker(struct ev_loop *loop);
|
||||
~ConnectBlocker();
|
||||
|
||||
int init(event_base *evbase);
|
||||
// Returns true if making connection is not allowed.
|
||||
bool blocked() const;
|
||||
// Call this function if connect operation succeeded. This will
|
||||
|
@ -47,8 +46,9 @@ public:
|
|||
void on_failure();
|
||||
|
||||
private:
|
||||
event *timerev_;
|
||||
int sleep_;
|
||||
ev_timer timer_;
|
||||
struct ev_loop *loop_;
|
||||
ev_tstamp sleep_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -38,45 +38,123 @@
|
|||
|
||||
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)
|
||||
: request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
|
||||
upstream_(upstream), response_body_buf_(nullptr),
|
||||
upstream_rtimerev_(nullptr), upstream_wtimerev_(nullptr),
|
||||
downstream_rtimerev_(nullptr), downstream_wtimerev_(nullptr),
|
||||
request_headers_sum_(0), response_headers_sum_(0), request_datalen_(0),
|
||||
response_datalen_(0), stream_id_(stream_id), priority_(priority),
|
||||
downstream_stream_id_(-1),
|
||||
: request_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||
response_buf_(upstream ? upstream->get_mcpool() : nullptr),
|
||||
request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
|
||||
upstream_(upstream), request_headers_sum_(0), response_headers_sum_(0),
|
||||
request_datalen_(0), response_datalen_(0), stream_id_(stream_id),
|
||||
priority_(priority), downstream_stream_id_(-1),
|
||||
response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
|
||||
request_state_(INITIAL), request_major_(1), request_minor_(1),
|
||||
response_state_(INITIAL), response_http_status_(0), response_major_(1),
|
||||
response_minor_(1), upgrade_request_(false), upgraded_(false),
|
||||
http2_upgrade_seen_(false), http2_settings_seen_(false),
|
||||
chunked_request_(false), request_connection_close_(false),
|
||||
request_header_key_prev_(false), request_http2_expect_body_(false),
|
||||
chunked_response_(false), response_connection_close_(false),
|
||||
response_header_key_prev_(false), expect_final_response_(false),
|
||||
request_headers_normalized_(false) {}
|
||||
http2_upgrade_seen_(false), chunked_request_(false),
|
||||
request_connection_close_(false), request_header_key_prev_(false),
|
||||
request_http2_expect_body_(false), chunked_response_(false),
|
||||
response_connection_close_(false), response_header_key_prev_(false),
|
||||
expect_final_response_(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() {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, this) << "Deleting";
|
||||
}
|
||||
if (response_body_buf_) {
|
||||
// Passing NULL to evbuffer_free() causes segmentation fault.
|
||||
evbuffer_free(response_body_buf_);
|
||||
}
|
||||
if (upstream_rtimerev_) {
|
||||
event_free(upstream_rtimerev_);
|
||||
}
|
||||
if (upstream_wtimerev_) {
|
||||
event_free(upstream_wtimerev_);
|
||||
}
|
||||
if (downstream_rtimerev_) {
|
||||
event_free(downstream_rtimerev_);
|
||||
}
|
||||
if (downstream_wtimerev_) {
|
||||
event_free(downstream_wtimerev_);
|
||||
|
||||
// check nullptr for unittest
|
||||
if (upstream_) {
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
|
||||
ev_timer_stop(loop, &upstream_rtimer_);
|
||||
ev_timer_stop(loop, &upstream_wtimer_);
|
||||
ev_timer_stop(loop, &downstream_rtimer_);
|
||||
ev_timer_stop(loop, &downstream_wtimer_);
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, this) << "Deleted";
|
||||
}
|
||||
|
@ -137,35 +215,15 @@ void Downstream::force_resume_read() {
|
|||
}
|
||||
|
||||
namespace {
|
||||
Headers::const_iterator get_norm_header(const 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;
|
||||
const Headers::value_type *get_header_linear(const Headers &headers,
|
||||
const std::string &name) {
|
||||
const Headers::value_type *res = nullptr;
|
||||
for (auto &kv : headers) {
|
||||
if (kv.name == name) {
|
||||
res = &kv;
|
||||
}
|
||||
}
|
||||
return std::end(headers);
|
||||
}
|
||||
} // 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;
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -177,90 +235,70 @@ void Downstream::assemble_request_cookie() {
|
|||
std::string &cookie = assembled_request_cookie_;
|
||||
cookie = "";
|
||||
for (auto &kv : request_headers_) {
|
||||
if (util::strieq("cookie", kv.name.c_str())) {
|
||||
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 (kv.name.size() != 6 || kv.name[5] != 'e' ||
|
||||
!util::streq("cooki", kv.name.c_str(), 5)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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) {
|
||||
cookie.erase(cookie.size() - 2);
|
||||
}
|
||||
}
|
||||
|
||||
void Downstream::crumble_request_cookie() {
|
||||
Headers Downstream::crumble_request_cookie() {
|
||||
Headers cookie_hdrs;
|
||||
for (auto &kv : request_headers_) {
|
||||
if (util::strieq("cookie", kv.name.c_str())) {
|
||||
size_t last = kv.value.size();
|
||||
size_t num = 0;
|
||||
std::string rep_cookie;
|
||||
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
|
||||
!util::streq("cooki", kv.name.c_str(), 5)) {
|
||||
continue;
|
||||
}
|
||||
size_t last = kv.value.size();
|
||||
|
||||
for (size_t j = 0; j < last;) {
|
||||
j = kv.value.find_first_not_of("\t ;", j);
|
||||
if (j == std::string::npos) {
|
||||
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;
|
||||
for (size_t j = 0; j < last;) {
|
||||
j = kv.value.find_first_not_of("\t ;", j);
|
||||
if (j == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
if (num > 0) {
|
||||
kv.value = std::move(rep_cookie);
|
||||
auto first = j;
|
||||
|
||||
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_),
|
||||
std::make_move_iterator(std::begin(cookie_hdrs)),
|
||||
std::make_move_iterator(std::end(cookie_hdrs)));
|
||||
if (request_headers_normalized_) {
|
||||
normalize_request_headers();
|
||||
}
|
||||
return cookie_hdrs;
|
||||
}
|
||||
|
||||
const std::string &Downstream::get_assembled_request_cookie() const {
|
||||
return assembled_request_cookie_;
|
||||
}
|
||||
|
||||
void Downstream::normalize_request_headers() {
|
||||
http2::normalize_headers(request_headers_);
|
||||
request_headers_normalized_ = true;
|
||||
}
|
||||
|
||||
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);
|
||||
void Downstream::index_request_headers() {
|
||||
for (auto &kv : request_headers_) {
|
||||
util::inp_strlower(kv.name);
|
||||
}
|
||||
|
||||
return get_header_linear(request_headers_, name);
|
||||
http2::index_headers(request_hdidx_, request_headers_);
|
||||
}
|
||||
|
||||
bool Downstream::get_request_headers_normalized() const {
|
||||
return request_headers_normalized_;
|
||||
const Headers::value_type *Downstream::get_request_header(int token) const {
|
||||
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) {
|
||||
|
@ -276,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) {
|
|||
item.value = std::move(value);
|
||||
}
|
||||
|
||||
void Downstream::split_add_request_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index) {
|
||||
void Downstream::add_request_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index, int token) {
|
||||
http2::index_header(request_hdidx_, token, request_headers_.size());
|
||||
request_headers_sum_ += namelen + valuelen;
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
return request_headers_sum_;
|
||||
|
@ -399,14 +441,16 @@ void Downstream::set_request_http2_expect_body(bool f) {
|
|||
request_http2_expect_body_ = f;
|
||||
}
|
||||
|
||||
bool Downstream::get_output_buffer_full() {
|
||||
bool Downstream::request_buf_full() {
|
||||
if (dconn_) {
|
||||
return dconn_->get_output_buffer_full();
|
||||
return request_buf_.rleft() >= 16384;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Memchunks4K *Downstream::get_request_buf() { return &request_buf_; }
|
||||
|
||||
// Call this function after this object is attached to
|
||||
// Downstream. Otherwise, the program will crash.
|
||||
int Downstream::push_request_headers() {
|
||||
|
@ -446,19 +490,23 @@ const Headers &Downstream::get_response_headers() const {
|
|||
return response_headers_;
|
||||
}
|
||||
|
||||
void Downstream::normalize_response_headers() {
|
||||
http2::normalize_headers(response_headers_);
|
||||
void Downstream::index_response_headers() {
|
||||
for (auto &kv : response_headers_) {
|
||||
util::inp_strlower(kv.name);
|
||||
}
|
||||
http2::index_headers(response_hdidx_, response_headers_);
|
||||
}
|
||||
|
||||
Headers::const_iterator
|
||||
Downstream::get_norm_response_header(const std::string &name) const {
|
||||
return get_norm_header(response_headers_, name);
|
||||
const Headers::value_type *Downstream::get_response_header(int token) const {
|
||||
return http2::get_header(response_hdidx_, token, response_headers_);
|
||||
}
|
||||
|
||||
void Downstream::rewrite_norm_location_response_header(
|
||||
const std::string &upstream_scheme, uint16_t upstream_port) {
|
||||
auto hd = get_norm_header(response_headers_, "location");
|
||||
if (hd == std::end(response_headers_)) {
|
||||
void
|
||||
Downstream::rewrite_location_response_header(const std::string &upstream_scheme,
|
||||
uint16_t upstream_port) {
|
||||
auto hd =
|
||||
http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_);
|
||||
if (!hd) {
|
||||
return;
|
||||
}
|
||||
http_parser_url u;
|
||||
|
@ -475,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header(
|
|||
upstream_scheme, upstream_port);
|
||||
}
|
||||
if (new_uri.empty()) {
|
||||
auto host = get_norm_request_header("host");
|
||||
if (host == std::end(request_headers_)) {
|
||||
auto host = get_request_header(http2::HD_HOST);
|
||||
if (!host) {
|
||||
return;
|
||||
}
|
||||
new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
|
||||
upstream_scheme, upstream_port);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void Downstream::split_add_response_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value,
|
||||
size_t valuelen, bool no_index) {
|
||||
void Downstream::add_response_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index, int token) {
|
||||
http2::index_header(response_hdidx_, token, response_headers_.size());
|
||||
response_headers_sum_ += namelen + valuelen;
|
||||
http2::add_header(response_headers_, name, namelen, value, valuelen,
|
||||
no_index);
|
||||
|
@ -527,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data,
|
|||
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 {
|
||||
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::init_response_body_buf() {
|
||||
if (!response_body_buf_) {
|
||||
response_body_buf_ = evbuffer_new();
|
||||
if (response_body_buf_ == nullptr) {
|
||||
DIE();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Memchunks4K *Downstream::get_response_buf() { return &response_buf_; }
|
||||
|
||||
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) {
|
||||
response_bodylen_ += amount;
|
||||
|
@ -641,37 +692,31 @@ void Downstream::inspect_http1_request() {
|
|||
upgrade_request_ = true;
|
||||
}
|
||||
|
||||
for (auto &hd : request_headers_) {
|
||||
if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) {
|
||||
// TODO Perform more strict checking for upgrade headers
|
||||
if (!upgrade_request_) {
|
||||
auto idx = request_hdidx_[http2::HD_UPGRADE];
|
||||
if (idx != -1) {
|
||||
upgrade_request_ = true;
|
||||
|
||||
if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(),
|
||||
hd.value.size())) {
|
||||
auto &val = request_headers_[idx].value;
|
||||
// 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;
|
||||
}
|
||||
} 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() {
|
||||
for (auto &hd : response_headers_) {
|
||||
if (!chunked_response_ &&
|
||||
util::strieq(hd.name.c_str(), "transfer-encoding")) {
|
||||
if (util::strifind(hd.value.c_str(), "chunked")) {
|
||||
chunked_response_ = true;
|
||||
}
|
||||
}
|
||||
auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
|
||||
if (idx != -1 &&
|
||||
util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
|
||||
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_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 {
|
||||
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) {
|
||||
|
@ -756,207 +810,130 @@ bool pseudo_header_allowed(const Headers &headers) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
bool Downstream::request_pseudo_header_allowed() const {
|
||||
return pseudo_header_allowed(request_headers_);
|
||||
bool Downstream::request_pseudo_header_allowed(int token) const {
|
||||
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 {
|
||||
return pseudo_header_allowed(response_headers_);
|
||||
bool Downstream::response_pseudo_header_allowed(int token) const {
|
||||
if (!pseudo_header_allowed(response_headers_)) {
|
||||
return false;
|
||||
}
|
||||
return http2::check_http2_response_pseudo_header(response_hdidx_, token);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void upstream_timeoutcb(evutil_socket_t fd, short event, void *arg) {
|
||||
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);
|
||||
}
|
||||
void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); }
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void upstream_rtimeoutcb(evutil_socket_t fd, short event, void *arg) {
|
||||
upstream_timeoutcb(fd, EV_READ, arg);
|
||||
}
|
||||
} // 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) {
|
||||
void try_reset_timer(struct ev_loop *loop, ev_timer *w) {
|
||||
if (!ev_is_active(w)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_add(timer, timeout);
|
||||
ev_timer_again(loop, w);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void try_reset_timer(event *timer, const timeval *timeout) {
|
||||
if (!timer) {
|
||||
void ensure_timer(struct ev_loop *loop, ev_timer *w) {
|
||||
if (ev_is_active(w)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!evtimer_pending(timer, nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_add(timer, timeout);
|
||||
ev_timer_again(loop, w);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void ensure_timer(event *timer, const timeval *timeout) {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evtimer_pending(timer, nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_add(timer, timeout);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void disable_timer(event *timer) {
|
||||
if (!timer) {
|
||||
return;
|
||||
}
|
||||
|
||||
event_del(timer);
|
||||
void disable_timer(struct ev_loop *loop, ev_timer *w) {
|
||||
ev_timer_stop(loop, w);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Downstream::reset_upstream_rtimer() {
|
||||
reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
|
||||
try_reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
|
||||
if (get_config()->stream_read_timeout == 0.) {
|
||||
return;
|
||||
}
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
reset_timer(loop, &upstream_rtimer_);
|
||||
}
|
||||
|
||||
void Downstream::reset_upstream_wtimer() {
|
||||
reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
|
||||
try_reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
disable_timer(upstream_wtimerev_);
|
||||
}
|
||||
|
||||
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);
|
||||
if (get_config()->stream_write_timeout == 0.) {
|
||||
return;
|
||||
}
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
disable_timer(loop, &upstream_wtimer_);
|
||||
}
|
||||
|
||||
void Downstream::reset_downstream_rtimer() {
|
||||
reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
|
||||
try_reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
|
||||
if (get_config()->stream_read_timeout == 0.) {
|
||||
return;
|
||||
}
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
reset_timer(loop, &downstream_rtimer_);
|
||||
}
|
||||
|
||||
void Downstream::reset_downstream_wtimer() {
|
||||
reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
|
||||
try_reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
|
||||
auto loop = upstream_->get_client_handler()->get_loop();
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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; }
|
||||
|
|
|
@ -34,13 +34,13 @@
|
|||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "shrpx_io_control.h"
|
||||
#include "http2.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
|
@ -76,7 +76,7 @@ public:
|
|||
|
||||
// Returns true if output buffer is full. If underlying dconn_ is
|
||||
// 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.
|
||||
void check_upgrade_fulfilled();
|
||||
// Returns true if the request is upgrade.
|
||||
|
@ -95,30 +95,27 @@ public:
|
|||
const std::string &get_http2_settings() const;
|
||||
// downstream request API
|
||||
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();
|
||||
const std::string &get_assembled_request_cookie() const;
|
||||
// Makes key lowercase and sort headers by name using <
|
||||
void normalize_request_headers();
|
||||
// Returns iterator pointing to the request header with the name
|
||||
// |name|. If multiple header have |name| as name, return first
|
||||
// occurrence from the beginning. If no such header is found,
|
||||
// returns std::end(get_request_headers()). This function must be
|
||||
// called after calling normalize_request_headers().
|
||||
Headers::const_iterator
|
||||
get_norm_request_header(const std::string &name) const;
|
||||
// Returns iterator pointing to the request header with the name
|
||||
// |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;
|
||||
// Lower the request header field names and indexes request headers
|
||||
void index_request_headers();
|
||||
// Returns pointer to the request header with the name |name|. If
|
||||
// multiple header have |name| as name, return last occurrence from
|
||||
// the beginning. If no such header is found, returns nullptr.
|
||||
// This function must be called after headers are indexed
|
||||
const Headers::value_type *get_request_header(int token) const;
|
||||
// Returns pointer to the request header with the name |name|. If
|
||||
// no such header is found, returns nullptr.
|
||||
const Headers::value_type *get_request_header(const std::string &name) const;
|
||||
void add_request_header(std::string name, std::string value);
|
||||
void set_last_request_header_value(std::string value);
|
||||
|
||||
void split_add_request_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index);
|
||||
void add_request_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, bool no_index,
|
||||
int token);
|
||||
|
||||
bool get_request_header_key_prev() const;
|
||||
void append_last_request_header_key(const char *data, size_t len);
|
||||
|
@ -161,7 +158,7 @@ public:
|
|||
size_t get_request_datalen() const;
|
||||
void dec_request_datalen(size_t len);
|
||||
void reset_request_datalen();
|
||||
bool request_pseudo_header_allowed() const;
|
||||
bool request_pseudo_header_allowed(int token) const;
|
||||
bool expect_response_body() const;
|
||||
enum {
|
||||
INITIAL,
|
||||
|
@ -174,28 +171,25 @@ public:
|
|||
};
|
||||
void set_request_state(int state);
|
||||
int get_request_state() const;
|
||||
Memchunks4K *get_request_buf();
|
||||
// downstream response API
|
||||
const Headers &get_response_headers() const;
|
||||
// Makes key lowercase and sort headers by name using <
|
||||
void normalize_response_headers();
|
||||
// Returns iterator pointing to the response header with the name
|
||||
// |name|. If multiple header have |name| as name, return first
|
||||
// occurrence from the beginning. If no such header is found,
|
||||
// returns std::end(get_response_headers()). This function must be
|
||||
// called after calling normalize_response_headers().
|
||||
Headers::const_iterator
|
||||
get_norm_response_header(const std::string &name) const;
|
||||
// Rewrites the location response header field. This function must
|
||||
// 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);
|
||||
// Lower the response header field names and indexes response headers
|
||||
void index_response_headers();
|
||||
// Returns pointer to the response header with the name |name|. If
|
||||
// multiple header have |name| as name, return last occurrence from
|
||||
// the beginning. If no such header is found, returns nullptr.
|
||||
// This function must be called after response headers are indexed.
|
||||
const Headers::value_type *get_response_header(int token) const;
|
||||
// Rewrites the location response header field.
|
||||
void rewrite_location_response_header(const std::string &upstream_scheme,
|
||||
uint16_t upstream_port);
|
||||
void add_response_header(std::string name, std::string value);
|
||||
void set_last_response_header_value(std::string value);
|
||||
|
||||
void split_add_response_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index);
|
||||
void add_response_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, bool no_index,
|
||||
int token);
|
||||
|
||||
bool get_response_header_key_prev() const;
|
||||
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_state(int state);
|
||||
int get_response_state() const;
|
||||
int init_response_body_buf();
|
||||
evbuffer *get_response_body_buf();
|
||||
Memchunks4K *get_response_buf();
|
||||
bool response_buf_full();
|
||||
void add_response_bodylen(size_t amount);
|
||||
int64_t get_response_bodylen() const;
|
||||
void add_response_sent_bodylen(size_t amount);
|
||||
|
@ -237,7 +231,7 @@ public:
|
|||
void dec_response_datalen(size_t len);
|
||||
size_t get_response_datalen() const;
|
||||
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
|
||||
// connection.
|
||||
|
@ -252,18 +246,15 @@ public:
|
|||
bool get_rst_stream_after_end_stream() const;
|
||||
void set_rst_stream_after_end_stream(bool f);
|
||||
|
||||
// Initializes upstream timers, but they are not pending.
|
||||
void init_upstream_timer();
|
||||
// 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.
|
||||
// Resets upstream read timer. If it is active, timeout value is
|
||||
// reset. If it is not active, timer will be started.
|
||||
void reset_upstream_rtimer();
|
||||
// Makes upstream write timer pending. If it is already pending,
|
||||
// timeout value is reset. This function also resets read timer if
|
||||
// it is already pending.
|
||||
// Resets upstream write timer. If it is active, timeout value is
|
||||
// reset. If it is not active, timer will be started. This
|
||||
// function also resets read timer if it has been started.
|
||||
void reset_upstream_wtimer();
|
||||
// Makes upstream write timer pending. If it is already pending, do
|
||||
// nothing.
|
||||
// Makes sure that upstream write timer is started. If it has been
|
||||
// started, do nothing. Otherwise, write timer will be started.
|
||||
void ensure_upstream_wtimer();
|
||||
// Disables upstream read timer.
|
||||
void disable_upstream_rtimer();
|
||||
|
@ -272,7 +263,6 @@ public:
|
|||
|
||||
// Downstream timer functions. They works in a similar way just
|
||||
// like the upstream timer function.
|
||||
void init_downstream_timer();
|
||||
void reset_downstream_rtimer();
|
||||
void reset_downstream_wtimer();
|
||||
void ensure_downstream_wtimer();
|
||||
|
@ -282,6 +272,11 @@ public:
|
|||
// Returns true if accesslog can be written for this downstream.
|
||||
bool accesslog_ready() const;
|
||||
|
||||
enum {
|
||||
EVENT_ERROR = 0x1,
|
||||
EVENT_TIMEOUT = 0x2,
|
||||
};
|
||||
|
||||
private:
|
||||
Headers request_headers_;
|
||||
Headers response_headers_;
|
||||
|
@ -292,7 +287,15 @@ private:
|
|||
std::string request_http2_authority_;
|
||||
std::chrono::high_resolution_clock::time_point request_start_time_;
|
||||
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
|
||||
int64_t request_bodylen_;
|
||||
|
@ -303,15 +306,6 @@ private:
|
|||
|
||||
Upstream *upstream_;
|
||||
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 response_headers_sum_;
|
||||
|
@ -337,6 +331,9 @@ private:
|
|||
int response_major_;
|
||||
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
|
||||
// CONNECT)
|
||||
bool upgrade_request_;
|
||||
|
@ -344,7 +341,6 @@ private:
|
|||
bool upgraded_;
|
||||
|
||||
bool http2_upgrade_seen_;
|
||||
bool http2_settings_seen_;
|
||||
|
||||
bool chunked_request_;
|
||||
bool request_connection_close_;
|
||||
|
@ -355,9 +351,6 @@ private:
|
|||
bool response_connection_close_;
|
||||
bool response_header_key_prev_;
|
||||
bool expect_final_response_;
|
||||
|
||||
// true if request_headers_ is normalized
|
||||
bool request_headers_normalized_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -51,7 +51,7 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, size_t consumed) = 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_write() = 0;
|
||||
|
|
|
@ -33,10 +33,11 @@ namespace shrpx {
|
|||
|
||||
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
|
||||
? std::numeric_limits<size_t>::max()
|
||||
: conn_max_per_host) {}
|
||||
: conn_max_per_host),
|
||||
unified_host_(unified_host) {}
|
||||
|
||||
DownstreamQueue::~DownstreamQueue() {}
|
||||
|
||||
|
@ -59,8 +60,19 @@ DownstreamQueue::find_host_entry(const std::string &host) {
|
|||
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) {
|
||||
auto &ent = find_host_entry(downstream->get_request_http2_authority());
|
||||
auto &ent = find_host_entry(make_host_key(downstream.get()));
|
||||
++ent.num_active;
|
||||
|
||||
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) {
|
||||
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();
|
||||
ent.blocked.insert(stream_id);
|
||||
blocked_downstreams_[stream_id] = std::move(downstream);
|
||||
}
|
||||
|
||||
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_)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -119,7 +131,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
|
|||
|
||||
if (kv != std::end(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);
|
||||
--ent.num_active;
|
||||
|
||||
|
@ -148,7 +160,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
|
|||
|
||||
if (kv != std::end(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);
|
||||
ent.blocked.erase(stream_id);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
typedef std::map<std::string, HostEntry> HostEntryMap;
|
||||
|
||||
// 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();
|
||||
void add_pending(std::unique_ptr<Downstream> downstream);
|
||||
void add_failure(std::unique_ptr<Downstream> downstream);
|
||||
|
@ -82,6 +82,8 @@ public:
|
|||
Downstream *find(int32_t stream_id);
|
||||
const DownstreamMap &get_active_downstreams() const;
|
||||
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.
|
||||
size_t conn_max_per_host_;
|
||||
|
@ -98,6 +100,9 @@ private:
|
|||
DownstreamMap active_downstreams_;
|
||||
// Downstream objects, blocked by conn_max_per_host_
|
||||
DownstreamMap blocked_downstreams_;
|
||||
// true if downstream host is treated as the same. Used for reverse
|
||||
// proxying.
|
||||
bool unified_host_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
void test_downstream_normalize_request_headers(void) {
|
||||
void test_downstream_index_request_headers(void) {
|
||||
Downstream d(nullptr, 0, 0);
|
||||
d.add_request_header("1", "0");
|
||||
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(":method", "6");
|
||||
d.add_request_header(":authority", "7");
|
||||
d.normalize_request_headers();
|
||||
d.index_request_headers();
|
||||
|
||||
auto ans = Headers{{":authority", "7"},
|
||||
{":method", "6"},
|
||||
{"1", "0"},
|
||||
auto ans = Headers{{"1", "0"},
|
||||
{"2", "1"},
|
||||
{"alpha", "3"},
|
||||
{"bravo", "5"},
|
||||
{"charlie", "2"},
|
||||
{"delta", "4"}};
|
||||
{"alpha", "3"},
|
||||
{"delta", "4"},
|
||||
{"bravo", "5"},
|
||||
{":method", "6"},
|
||||
{":authority", "7"}};
|
||||
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);
|
||||
d.add_response_header("Charlie", "0");
|
||||
d.add_response_header("Alpha", "1");
|
||||
d.add_response_header("Delta", "2");
|
||||
d.add_response_header("BravO", "3");
|
||||
d.normalize_response_headers();
|
||||
d.index_response_headers();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void test_downstream_get_norm_request_header(void) {
|
||||
void test_downstream_get_request_header(void) {
|
||||
Downstream d(nullptr, 0, 0);
|
||||
d.add_request_header("alpha", "0");
|
||||
d.add_request_header("bravo", "1");
|
||||
d.add_request_header("bravo", "2");
|
||||
d.add_request_header("charlie", "3");
|
||||
d.add_request_header("delta", "4");
|
||||
d.add_request_header("echo", "5");
|
||||
auto i = d.get_norm_request_header("alpha");
|
||||
CU_ASSERT(Header("alpha", "0") == *i);
|
||||
i = d.get_norm_request_header("bravo");
|
||||
CU_ASSERT(Header("bravo", "1") == *i);
|
||||
i = d.get_norm_request_header("delta");
|
||||
CU_ASSERT(Header("delta", "4") == *i);
|
||||
i = d.get_norm_request_header("echo");
|
||||
CU_ASSERT(Header("echo", "5") == *i);
|
||||
i = d.get_norm_request_header("foxtrot");
|
||||
CU_ASSERT(i == std::end(d.get_request_headers()));
|
||||
d.add_request_header(":authority", "1");
|
||||
d.add_request_header("content-length", "2");
|
||||
d.index_request_headers();
|
||||
|
||||
// By token
|
||||
CU_ASSERT(Header(":authority", "1") ==
|
||||
*d.get_request_header(http2::HD__AUTHORITY));
|
||||
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
|
||||
|
||||
// By name
|
||||
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
|
||||
CU_ASSERT(nullptr == d.get_request_header("bravo"));
|
||||
}
|
||||
|
||||
void test_downstream_get_norm_response_header(void) {
|
||||
void test_downstream_get_response_header(void) {
|
||||
Downstream d(nullptr, 0, 0);
|
||||
d.add_response_header("alpha", "0");
|
||||
d.add_response_header("bravo", "1");
|
||||
d.add_response_header("bravo", "2");
|
||||
d.add_response_header("charlie", "3");
|
||||
d.add_response_header("delta", "4");
|
||||
d.add_response_header("echo", "5");
|
||||
auto i = d.get_norm_response_header("alpha");
|
||||
CU_ASSERT(Header("alpha", "0") == *i);
|
||||
i = d.get_norm_response_header("bravo");
|
||||
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()));
|
||||
d.add_response_header(":status", "1");
|
||||
d.add_response_header("content-length", "2");
|
||||
d.index_response_headers();
|
||||
|
||||
// By token
|
||||
CU_ASSERT(Header(":status", "1") ==
|
||||
*d.get_response_header(http2::HD__STATUS));
|
||||
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
|
||||
}
|
||||
|
||||
void test_downstream_crumble_request_cookie(void) {
|
||||
Downstream d(nullptr, 0, 0);
|
||||
d.add_request_header(":method", "get");
|
||||
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", "echo");
|
||||
d.crumble_request_cookie();
|
||||
Headers ans = {{":method", "get"},
|
||||
{":path", "/"},
|
||||
{"cookie", "alpha"},
|
||||
{"cookie", "delta"},
|
||||
{"cookie", "echo"},
|
||||
auto cookies = d.crumble_request_cookie();
|
||||
|
||||
Headers ans = {{"cookie", "alpha"},
|
||||
{"cookie", "bravo"},
|
||||
{"cookie", "charlie"}};
|
||||
CU_ASSERT(ans == d.get_request_headers());
|
||||
{"cookie", "charlie"},
|
||||
{"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) {
|
||||
|
@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) {
|
|||
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);
|
||||
d.add_request_header("host", "localhost:3000");
|
||||
d.add_response_header("location", "http://localhost:3000/");
|
||||
d.rewrite_norm_location_response_header("https", 443);
|
||||
auto location = d.get_norm_response_header("location");
|
||||
d.index_request_headers();
|
||||
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);
|
||||
}
|
||||
{
|
||||
Downstream d(nullptr, 0, 0);
|
||||
d.set_request_http2_authority("localhost");
|
||||
d.add_response_header("location", "http://localhost/");
|
||||
d.rewrite_norm_location_response_header("https", 443);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
void test_downstream_normalize_request_headers(void);
|
||||
void test_downstream_normalize_response_headers(void);
|
||||
void test_downstream_get_norm_request_header(void);
|
||||
void test_downstream_get_norm_response_header(void);
|
||||
void test_downstream_index_request_headers(void);
|
||||
void test_downstream_index_response_headers(void);
|
||||
void test_downstream_get_request_header(void);
|
||||
void test_downstream_get_response_header(void);
|
||||
void test_downstream_crumble_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
|
||||
|
||||
|
|
|
@ -26,10 +26,6 @@
|
|||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "shrpx_client_handler.h"
|
||||
|
@ -50,15 +46,12 @@ namespace shrpx {
|
|||
Http2DownstreamConnection::Http2DownstreamConnection(
|
||||
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
|
||||
: DownstreamConnection(dconn_pool), http2session_(http2session),
|
||||
request_body_buf_(nullptr), sd_(nullptr) {}
|
||||
sd_(nullptr) {}
|
||||
|
||||
Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Deleting";
|
||||
}
|
||||
if (request_body_buf_) {
|
||||
evbuffer_free(request_body_buf_);
|
||||
}
|
||||
if (downstream_) {
|
||||
downstream_->disable_downstream_rtimer();
|
||||
downstream_->disable_downstream_wtimer();
|
||||
|
@ -73,24 +66,22 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
|
|||
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 (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(),
|
||||
downstream_->get_response_datalen());
|
||||
|
||||
downstream_->reset_response_datalen();
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
||||
}
|
||||
if (init_request_body_buf() == -1) {
|
||||
return -1;
|
||||
}
|
||||
http2session_->add_downstream_connection(this);
|
||||
if (http2session_->get_state() == Http2Session::DISCONNECTED) {
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
||||
downstream_ = downstream;
|
||||
|
||||
downstream_->init_downstream_timer();
|
||||
downstream_->reset_downstream_rtimer();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -145,7 +115,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
|
|||
DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
|
||||
}
|
||||
if (submit_rst_stream(downstream) == 0) {
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
||||
if (downstream_->get_downstream_stream_id() != -1) {
|
||||
|
@ -154,7 +124,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
|
|||
|
||||
downstream_->reset_response_datalen();
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
||||
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.
|
||||
return NGHTTP2_ERR_DEFERRED;
|
||||
}
|
||||
auto body = dconn->get_request_body_buf();
|
||||
|
||||
auto nread = evbuffer_remove(body, buf, length);
|
||||
if (nread == -1) {
|
||||
DCLOG(FATAL, dconn) << "evbuffer_remove() failed";
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
auto input = downstream->get_request_buf();
|
||||
auto nread = input->remove(buf, length);
|
||||
auto input_empty = input->rleft() == 0;
|
||||
|
||||
if (nread > 0) {
|
||||
// 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 &&
|
||||
// If connection is upgraded, don't set EOF flag, since HTTP/1
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (evbuffer_get_length(body) > 0) {
|
||||
if (!input_empty) {
|
||||
downstream->reset_downstream_wtimer();
|
||||
} else {
|
||||
downstream->disable_downstream_wtimer();
|
||||
|
@ -266,14 +232,12 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
return 0;
|
||||
}
|
||||
size_t nheader = downstream_->get_request_headers().size();
|
||||
|
||||
Headers cookies;
|
||||
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:
|
||||
// 1. :method
|
||||
// 2. :scheme
|
||||
|
@ -283,10 +247,12 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
// 6. x-forwarded-for (optional)
|
||||
// 7. x-forwarded-proto (optional)
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
nva.reserve(nheader + 7);
|
||||
nva.reserve(nheader + 7 + cookies.size());
|
||||
|
||||
std::string via_value;
|
||||
std::string xff_value;
|
||||
std::string scheme, authority, path, query;
|
||||
|
||||
// To reconstruct HTTP/1 status line and headers, proxy should
|
||||
// preserve host header field. See draft-09 section 8.1.3.1.
|
||||
if (downstream_->get_request_method() == "CONNECT") {
|
||||
|
@ -306,7 +272,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
if (!downstream_->get_request_http2_authority().empty()) {
|
||||
nva.push_back(http2::make_nv_ls(
|
||||
":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)) {
|
||||
DCLOG(INFO, this) << "host header field missing";
|
||||
}
|
||||
|
@ -363,7 +329,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
authority += util::utos(u.port);
|
||||
}
|
||||
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)) {
|
||||
DCLOG(INFO, this) << "host header field missing";
|
||||
}
|
||||
|
@ -374,27 +340,30 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
nva.push_back(
|
||||
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;
|
||||
auto transfer_encoding =
|
||||
downstream_->get_norm_request_header("transfer-encoding");
|
||||
if (transfer_encoding != end_headers &&
|
||||
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
|
||||
if (transfer_encoding &&
|
||||
util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
|
||||
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 (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 +=
|
||||
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
|
||||
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
|
||||
} else if (xff != end_headers &&
|
||||
!get_config()->strip_incoming_x_forwarded_for) {
|
||||
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
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 (via != end_headers) {
|
||||
if (via) {
|
||||
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
||||
}
|
||||
} else {
|
||||
if (via != end_headers) {
|
||||
if (via) {
|
||||
via_value = (*via).value;
|
||||
via_value += ", ";
|
||||
}
|
||||
|
@ -440,7 +409,8 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
}
|
||||
|
||||
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 ||
|
||||
content_length || downstream_->get_request_http2_expect_body()) {
|
||||
|
@ -461,17 +431,15 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
|
||||
downstream_->reset_downstream_wtimer();
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
||||
size_t datalen) {
|
||||
int rv = evbuffer_add(request_body_buf_, data, datalen);
|
||||
if (rv != 0) {
|
||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
int rv;
|
||||
auto output = downstream_->get_request_buf();
|
||||
output->append(data, datalen);
|
||||
if (downstream_->get_downstream_stream_id() != -1) {
|
||||
rv = http2session_->resume_data(this);
|
||||
if (rv != 0) {
|
||||
|
@ -480,7 +448,7 @@ int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
|||
|
||||
downstream_->ensure_downstream_wtimer();
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -495,7 +463,7 @@ int Http2DownstreamConnection::end_upload_data() {
|
|||
|
||||
downstream_->ensure_downstream_wtimer();
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -525,7 +493,7 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
|
|||
|
||||
downstream_->dec_response_datalen(consumed);
|
||||
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -535,10 +503,6 @@ int Http2DownstreamConnection::on_read() { 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) {
|
||||
// It is possible sd->dconn is not NULL. sd is detached when
|
||||
// on_stream_close_callback. Before that, after MSG_COMPLETE is set
|
||||
|
@ -556,18 +520,8 @@ StreamData *Http2DownstreamConnection::detach_stream_data() {
|
|||
sd_ = nullptr;
|
||||
sd->dconn = nullptr;
|
||||
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) {
|
||||
|
@ -584,7 +538,7 @@ int Http2DownstreamConnection::on_priority_change(int32_t pri) {
|
|||
DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
|
||||
return -1;
|
||||
}
|
||||
http2session_->notify();
|
||||
http2session_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,6 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
||||
virtual void force_resume_read() {}
|
||||
|
||||
virtual bool get_output_buffer_full();
|
||||
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_timeout();
|
||||
|
@ -66,9 +64,6 @@ public:
|
|||
|
||||
int send();
|
||||
|
||||
int init_request_body_buf();
|
||||
evbuffer *get_request_body_buf() const;
|
||||
|
||||
void attach_stream_data(StreamData *sd);
|
||||
StreamData *detach_stream_data();
|
||||
|
||||
|
@ -77,7 +72,6 @@ public:
|
|||
|
||||
private:
|
||||
Http2Session *http2session_;
|
||||
evbuffer *request_body_buf_;
|
||||
StreamData *sd_;
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,13 +32,16 @@
|
|||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "ringbuf.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class Http2DownstreamConnection;
|
||||
|
@ -49,10 +52,10 @@ struct StreamData {
|
|||
|
||||
class Http2Session {
|
||||
public:
|
||||
Http2Session(event_base *evbase, SSL_CTX *ssl_ctx);
|
||||
~Http2Session();
|
||||
typedef RingBuf<65536> Buf;
|
||||
|
||||
int init_notification();
|
||||
Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx);
|
||||
~Http2Session();
|
||||
|
||||
int check_cert();
|
||||
|
||||
|
@ -84,32 +87,45 @@ public:
|
|||
|
||||
int on_connect();
|
||||
|
||||
int do_read();
|
||||
int do_write();
|
||||
|
||||
int on_read();
|
||||
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();
|
||||
void notify();
|
||||
int downstream_read_proxy();
|
||||
int downstream_connect_proxy();
|
||||
|
||||
bufferevent *get_bev() const;
|
||||
void unwrap_free_bev();
|
||||
int downstream_read();
|
||||
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;
|
||||
void set_state(int state);
|
||||
|
||||
int start_settings_timer();
|
||||
void start_settings_timer();
|
||||
void stop_settings_timer();
|
||||
|
||||
size_t get_outbuf_length() const;
|
||||
|
||||
SSL *get_ssl() const;
|
||||
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
|
||||
void reset_timeouts();
|
||||
|
||||
// Returns true if request can be issued on downstream connection.
|
||||
bool can_push_request() const;
|
||||
// Initiates the connection checking if downstream connection has
|
||||
|
@ -117,13 +133,15 @@ public:
|
|||
void start_checking_connection();
|
||||
// Resets connection check timer. After timeout, we require
|
||||
// connection checking.
|
||||
int reset_connection_check_timer();
|
||||
void reset_connection_check_timer();
|
||||
// Signals that connection is alive. Internally
|
||||
// reset_connection_check_timer() is called.
|
||||
int connection_alive();
|
||||
void connection_alive();
|
||||
// Change connection check state.
|
||||
void set_connection_check_state(int state);
|
||||
|
||||
bool should_hard_fail() const;
|
||||
|
||||
enum {
|
||||
// Disconnected
|
||||
DISCONNECTED,
|
||||
|
@ -136,7 +154,9 @@ public:
|
|||
// Connecting to downstream and/or performing SSL/TLS handshake
|
||||
CONNECTING,
|
||||
// Connected to downstream
|
||||
CONNECTED
|
||||
CONNECTED,
|
||||
// Connection is started to fail
|
||||
CONNECT_FAILING,
|
||||
};
|
||||
|
||||
static const size_t OUTBUF_MAX_THRES = 64 * 1024;
|
||||
|
@ -151,20 +171,26 @@ public:
|
|||
};
|
||||
|
||||
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<StreamData *> streams_;
|
||||
std::function<int(Http2Session &)> read_, write_;
|
||||
std::function<int(Http2Session &)> on_read_, on_write_;
|
||||
// Used to parse the response from HTTP proxy
|
||||
std::unique_ptr<http_parser> proxy_htp_;
|
||||
event_base *evbase_;
|
||||
struct ev_loop *loop_;
|
||||
// NULL if no TLS is configured
|
||||
SSL_CTX *ssl_ctx_;
|
||||
SSL *ssl_;
|
||||
nghttp2_session *session_;
|
||||
bufferevent *bev_;
|
||||
bufferevent *wrbev_;
|
||||
bufferevent *rdbev_;
|
||||
event *settings_timerev_;
|
||||
event *connection_check_timerev_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
// fd_ is used for proxy connection and no TLS connection. For
|
||||
// direct or TLS connection, it may be -1 even after connection is
|
||||
// established. Use bufferevent_getfd(bev_) to get file descriptor
|
||||
|
@ -172,8 +198,10 @@ private:
|
|||
int fd_;
|
||||
int state_;
|
||||
int connection_check_state_;
|
||||
bool notified_;
|
||||
bool flow_control_;
|
||||
bool write_requested_;
|
||||
Buf wb_;
|
||||
Buf rb_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -45,10 +45,6 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||
uint32_t error_code, void *user_data) {
|
||||
|
@ -127,9 +123,7 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
|||
auto downstream = http->pop_downstream();
|
||||
downstream->reset_upstream(this);
|
||||
downstream->set_stream_id(1);
|
||||
downstream->init_upstream_timer();
|
||||
downstream->reset_upstream_rtimer();
|
||||
downstream->init_response_body_buf();
|
||||
downstream->set_stream_id(1);
|
||||
downstream->set_priority(0);
|
||||
|
||||
|
@ -142,46 +136,12 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) {
|
||||
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::start_settings_timer() {
|
||||
ev_timer_start(handler_->get_loop(), &settings_timer_);
|
||||
}
|
||||
|
||||
void Http2Upstream::stop_settings_timer() {
|
||||
if (settings_timerev_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
event_free(settings_timerev_);
|
||||
settings_timerev_ = nullptr;
|
||||
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -224,17 +184,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (namelen > 0 && name[0] == ':') {
|
||||
if (!downstream->request_pseudo_header_allowed() ||
|
||||
!http2::check_http2_request_pseudo_header(name, namelen)) {
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
|
||||
if (name[0] == ':') {
|
||||
if (!downstream->request_pseudo_header_allowed(token)) {
|
||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
downstream->split_add_request_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX);
|
||||
if (!http2::http2_header_allowed(token)) {
|
||||
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;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -256,9 +221,7 @@ int on_begin_headers_callback(nghttp2_session *session,
|
|||
auto downstream =
|
||||
util::make_unique<Downstream>(upstream, frame->hd.stream_id, 0);
|
||||
|
||||
downstream->init_upstream_timer();
|
||||
downstream->reset_upstream_rtimer();
|
||||
downstream->init_response_body_buf();
|
||||
|
||||
// Although, we deprecated minor version from HTTP/2, we supply
|
||||
// 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;
|
||||
}
|
||||
|
||||
downstream->normalize_request_headers();
|
||||
auto &nva = downstream->get_request_headers();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!http2::check_http2_request_headers(nva)) {
|
||||
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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");
|
||||
auto host = downstream->get_request_header(http2::HD_HOST);
|
||||
auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
|
||||
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||
|
||||
bool is_connect = method && "CONNECT" == method->value;
|
||||
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) {
|
||||
case NGHTTP2_SETTINGS:
|
||||
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
|
||||
upstream->start_settings_timer() != 0) {
|
||||
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
||||
upstream->start_settings_timer();
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_GOAWAY:
|
||||
|
@ -598,19 +552,28 @@ uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void write_notify_cb(evutil_socket_t fd, short what, void *arg) {
|
||||
auto upstream = static_cast<Http2Upstream *>(arg);
|
||||
upstream->perform_send();
|
||||
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto upstream = static_cast<Http2Upstream *>(w->data);
|
||||
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
|
||||
|
||||
Http2Upstream::Http2Upstream(ClientHandler *handler)
|
||||
: downstream_queue_(get_config()->http2_proxy
|
||||
? get_config()->downstream_connections_per_host
|
||||
: 0),
|
||||
handler_(handler), session_(nullptr), settings_timerev_(nullptr),
|
||||
write_notifyev_(nullptr), deferred_(false) {
|
||||
reset_timeouts();
|
||||
: downstream_queue_(
|
||||
get_config()->http2_proxy
|
||||
? 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), data_pending_(nullptr),
|
||||
data_pendinglen_(0), deferred_(false) {
|
||||
|
||||
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() {
|
||||
nghttp2_session_del(session_);
|
||||
if (settings_timerev_) {
|
||||
event_free(settings_timerev_);
|
||||
}
|
||||
if (write_notifyev_) {
|
||||
event_free(write_notifyev_);
|
||||
}
|
||||
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
||||
}
|
||||
|
||||
int Http2Upstream::on_read() {
|
||||
ssize_t rv = 0;
|
||||
auto bev = handler_->get_bev();
|
||||
auto input = bufferevent_get_input(bev);
|
||||
auto rb = handler_->get_rb();
|
||||
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
assert(evbuffer_get_length(input) == 0);
|
||||
|
||||
return send();
|
||||
const void *data;
|
||||
size_t nread;
|
||||
std::tie(data, nread) = rb->get();
|
||||
if (nread == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto mem = evbuffer_pullup(input, inputlen);
|
||||
|
||||
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
|
||||
rv = nghttp2_session_mem_recv(
|
||||
session_, reinterpret_cast<const uint8_t *>(data), nread);
|
||||
if (rv < 0) {
|
||||
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
||||
<< nghttp2_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (evbuffer_drain(input, rv) != 0) {
|
||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
||||
return -1;
|
||||
}
|
||||
rb->drain(nread);
|
||||
}
|
||||
}
|
||||
|
||||
int Http2Upstream::on_write() { return send(); }
|
||||
|
||||
int Http2Upstream::send() {
|
||||
if (write_notifyev_ == nullptr) {
|
||||
auto wb = handler_->get_wb();
|
||||
if (nghttp2_session_want_read(session_) == 0 &&
|
||||
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
event_active(write_notifyev_, 0, 0);
|
||||
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// After this function call, downstream may be deleted.
|
||||
int Http2Upstream::perform_send() {
|
||||
int rv;
|
||||
uint8_t buf[16384];
|
||||
auto bev = handler_->get_bev();
|
||||
auto output = bufferevent_get_output(bev);
|
||||
int Http2Upstream::on_write() {
|
||||
auto wb = handler_->get_wb();
|
||||
|
||||
sendbuf.reset(output, buf, sizeof(buf), handler_->get_write_limit());
|
||||
for (;;) {
|
||||
// Check buffer length and break if it is large enough.
|
||||
if (handler_->get_outbuf_length() > 0) {
|
||||
break;
|
||||
if (data_pending_) {
|
||||
auto n = std::min(wb->wleft(), data_pendinglen_);
|
||||
wb->write(data_pending_, n);
|
||||
if (n < data_pendinglen_) {
|
||||
data_pending_ += n;
|
||||
data_pendinglen_ -= n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
data_pending_ = nullptr;
|
||||
data_pendinglen_ = 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const uint8_t *data;
|
||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||
|
||||
|
@ -778,55 +743,38 @@ int Http2Upstream::perform_send() {
|
|||
if (datalen == 0) {
|
||||
break;
|
||||
}
|
||||
rv = sendbuf.add(data, datalen);
|
||||
if (rv != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
if (deferred_) {
|
||||
break;
|
||||
auto n = wb->write(data, datalen);
|
||||
if (n < static_cast<decltype(n)>(datalen)) {
|
||||
data_pending_ = data + n;
|
||||
data_pendinglen_ = datalen - n;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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 &&
|
||||
nghttp2_session_want_write(session_) == 0 &&
|
||||
handler_->get_outbuf_length() == 0) {
|
||||
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::on_event() { return 0; }
|
||||
|
||||
ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
|
||||
|
||||
namespace {
|
||||
void downstream_readcb(bufferevent *bev, void *ptr) {
|
||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
||||
int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
||||
|
||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||
// If upstream HTTP2 stream was closed, we just close downstream,
|
||||
// because there is no consumer now. Downstream connection is also
|
||||
// closed in this case.
|
||||
upstream->remove_downstream(downstream);
|
||||
remove_downstream(downstream);
|
||||
// downstream was deleted
|
||||
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
// here. Deleting downstream will be taken place at
|
||||
// on_stream_close_callback.
|
||||
upstream->rst_stream(downstream,
|
||||
infer_upstream_rst_stream_error_code(
|
||||
downstream->get_response_rst_stream_error_code()));
|
||||
rst_stream(downstream,
|
||||
infer_upstream_rst_stream_error_code(
|
||||
downstream->get_response_rst_stream_error_code()));
|
||||
downstream->pop_downstream_connection();
|
||||
// dconn was deleted
|
||||
dconn = nullptr;
|
||||
} else {
|
||||
auto rv = downstream->on_read();
|
||||
if (rv == DownstreamConnection::ERR_EOF) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
if (rv != 0) {
|
||||
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;
|
||||
if (rv != DownstreamConnection::ERR_NET) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, dconn) << "HTTP parser failure";
|
||||
}
|
||||
}
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
// Clearly, we have to close downstream connection on http parser
|
||||
// failure.
|
||||
downstream->pop_downstream_connection();
|
||||
// dconn was deleted
|
||||
dconn = nullptr;
|
||||
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||
}
|
||||
}
|
||||
if (upstream->send() != 0) {
|
||||
delete upstream->get_client_handler();
|
||||
return;
|
||||
}
|
||||
|
||||
handler_->signal_write();
|
||||
|
||||
// At this point, downstream may be deleted.
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void downstream_writecb(bufferevent *bev, void *ptr) {
|
||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
dconn->on_write();
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
|
||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
||||
int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
|
||||
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)) {
|
||||
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) {
|
||||
// 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
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
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
|
||||
// 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.
|
||||
int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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) {
|
||||
// Server may indicate the end of the request by EOF
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
|
||||
}
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
// For tunneled connection, MSG_COMPLETE signals
|
||||
// downstream_data_read_callback to send RST_STREAM after
|
||||
// pending response body is sent. This is needed to ensure
|
||||
// that RST_STREAM is sent after all pending data are sent.
|
||||
upstream->on_downstream_body_complete(downstream);
|
||||
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||
// If stream was not closed, then we set MSG_COMPLETE and let
|
||||
// on_stream_close_callback delete downstream.
|
||||
if (upstream->error_reply(downstream, 502) != 0) {
|
||||
delete upstream->get_client_handler();
|
||||
return;
|
||||
}
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
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());
|
||||
if (downstream->get_upgraded()) {
|
||||
on_downstream_body_complete(downstream);
|
||||
} 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) {
|
||||
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);
|
||||
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||
}
|
||||
} else {
|
||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||
if (downstream->get_upgraded()) {
|
||||
upstream->on_downstream_body_complete(downstream);
|
||||
} else {
|
||||
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||
}
|
||||
unsigned int status;
|
||||
if (events & Downstream::EVENT_TIMEOUT) {
|
||||
status = 504;
|
||||
} else {
|
||||
unsigned int status;
|
||||
if (events & BEV_EVENT_TIMEOUT) {
|
||||
status = 504;
|
||||
} else {
|
||||
status = 502;
|
||||
}
|
||||
if (upstream->error_reply(downstream, status) != 0) {
|
||||
delete upstream->get_client_handler();
|
||||
return;
|
||||
}
|
||||
status = 502;
|
||||
}
|
||||
if (error_reply(downstream, status) != 0) {
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
handler_->signal_write();
|
||||
// At this point, downstream may be deleted.
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -1048,7 +960,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
|
|||
void *user_data) {
|
||||
auto downstream = static_cast<Downstream *>(source->ptr);
|
||||
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();
|
||||
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));
|
||||
}
|
||||
|
||||
int nread = evbuffer_remove(body, buf, length);
|
||||
if (nread == -1) {
|
||||
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
auto body_empty = evbuffer_get_length(body) == 0;
|
||||
auto nread = body->remove(buf, length);
|
||||
auto body_empty = body->rleft() == 0;
|
||||
|
||||
if (body_empty &&
|
||||
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
|
||||
// stream.
|
||||
upstream->set_deferred(true);
|
||||
|
||||
return NGHTTP2_ERR_DEFERRED;
|
||||
}
|
||||
|
||||
|
@ -1122,13 +1030,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
|
|||
int rv;
|
||||
auto html = http::create_error_html(status_code);
|
||||
downstream->set_response_http_status(status_code);
|
||||
downstream->init_response_body_buf();
|
||||
auto body = downstream->get_response_body_buf();
|
||||
rv = evbuffer_add(body, html.c_str(), html.size());
|
||||
if (rv == -1) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
auto body = downstream->get_response_buf();
|
||||
body->append(html.c_str(), html.size());
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
nghttp2_data_provider data_prd;
|
||||
|
@ -1154,18 +1057,6 @@ int Http2Upstream::error_reply(Downstream *downstream,
|
|||
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
|
||||
Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) {
|
||||
downstream_queue_.add_pending(std::move(downstream));
|
||||
|
@ -1182,6 +1073,9 @@ void Http2Upstream::remove_downstream(Downstream *downstream) {
|
|||
if (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) {
|
||||
|
@ -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 &&
|
||||
!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);
|
||||
}
|
||||
|
||||
auto end_headers = std::end(downstream->get_response_headers());
|
||||
size_t nheader = downstream->get_response_headers().size();
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
// 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());
|
||||
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 (LOG_ENABLED(INFO)) {
|
||||
|
@ -1243,19 +1135,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
|
||||
} else {
|
||||
auto server = downstream->get_norm_response_header("server");
|
||||
if (server != end_headers) {
|
||||
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||
if (server) {
|
||||
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 (via != end_headers) {
|
||||
if (via) {
|
||||
nva.push_back(http2::make_nv_ls("via", (*via).value));
|
||||
}
|
||||
} else {
|
||||
if (via != end_headers) {
|
||||
if (via) {
|
||||
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,
|
||||
const uint8_t *data, size_t len,
|
||||
bool flush) {
|
||||
auto body = downstream->get_response_body_buf();
|
||||
int rv = evbuffer_add(body, data, len);
|
||||
if (rv != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
auto body = downstream->get_response_buf();
|
||||
body->append(data, len);
|
||||
|
||||
if (flush) {
|
||||
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
||||
|
@ -1317,16 +1205,6 @@ int Http2Upstream::on_downstream_body(Downstream *downstream,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -1358,7 +1236,8 @@ int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
|||
downstream->dec_request_datalen(consumed);
|
||||
}
|
||||
|
||||
return send();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
||||
|
@ -1371,7 +1250,8 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
|||
return -1;
|
||||
}
|
||||
|
||||
return send();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Upstream::consume(int32_t stream_id, size_t len) {
|
||||
|
@ -1414,11 +1294,6 @@ int Http2Upstream::on_timeout(Downstream *downstream) {
|
|||
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() {
|
||||
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
||||
if (ent.second->accesslog_ready()) {
|
||||
|
@ -1453,14 +1328,13 @@ int Http2Upstream::on_downstream_reset() {
|
|||
}
|
||||
}
|
||||
|
||||
rv = send();
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
handler_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Http2Upstream::set_deferred(bool f) { deferred_ = f; }
|
||||
|
||||
MemchunkPool4K *Http2Upstream::get_mcpool() { return &mcpool_; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -29,11 +29,15 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_downstream_queue.h"
|
||||
#include "libevent_util.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -46,16 +50,16 @@ public:
|
|||
virtual ~Http2Upstream();
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_event();
|
||||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
int send();
|
||||
int perform_send();
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
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 remove_downstream(Downstream *downstream);
|
||||
Downstream *find_downstream(int32_t stream_id);
|
||||
|
@ -78,14 +82,14 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
bool get_flow_control() const;
|
||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||
// takes ownership of the |upstream|. This function returns 0 if it
|
||||
// succeeds, or -1.
|
||||
int upgrade_upstream(HttpsUpstream *upstream);
|
||||
int start_settings_timer();
|
||||
void start_settings_timer();
|
||||
void stop_settings_timer();
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
void log_response_headers(Downstream *downstream,
|
||||
|
@ -95,15 +99,16 @@ public:
|
|||
|
||||
void set_deferred(bool f);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
DownstreamQueue downstream_queue_;
|
||||
// must be put before downstream_queue_
|
||||
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
||||
MemchunkPool4K mcpool_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ev_timer settings_timer_;
|
||||
ClientHandler *handler_;
|
||||
nghttp2_session *session_;
|
||||
event *settings_timerev_;
|
||||
event *write_notifyev_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
bool flow_control_;
|
||||
bool deferred_;
|
||||
};
|
||||
|
|
|
@ -36,25 +36,99 @@
|
|||
#include "shrpx_worker.h"
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
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
|
||||
|
||||
HttpDownstreamConnection::HttpDownstreamConnection(
|
||||
DownstreamConnectionPool *dconn_pool)
|
||||
: DownstreamConnection(dconn_pool), bev_(nullptr), ioctrl_(nullptr),
|
||||
response_htp_{0} {}
|
||||
DownstreamConnectionPool *dconn_pool, struct ev_loop *loop)
|
||||
: DownstreamConnection(dconn_pool), rlimit_(loop, &rev_, 0, 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() {
|
||||
if (bev_) {
|
||||
util::bev_disable_unless(bev_, EV_READ | EV_WRITE);
|
||||
bufferevent_free(bev_);
|
||||
ev_timer_stop(loop_, &rt_);
|
||||
ev_timer_stop(loop_, &wt_);
|
||||
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
|
||||
// asynchronously.
|
||||
|
@ -67,25 +141,25 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
if (LOG_ENABLED(INFO)) {
|
||||
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();
|
||||
|
||||
if (connect_blocker->blocked()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto evbase = client_handler_->get_evbase();
|
||||
auto worker_stat = client_handler_->get_worker_stat();
|
||||
auto end = worker_stat->next_downstream;
|
||||
for (;;) {
|
||||
auto i = worker_stat->next_downstream;
|
||||
++worker_stat->next_downstream;
|
||||
worker_stat->next_downstream %= get_config()->downstream_addrs.size();
|
||||
|
||||
auto fd = socket(get_config()->downstream_addrs[i].addr.storage.ss_family,
|
||||
SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
fd_ = util::create_nonblock_socket(
|
||||
get_config()->downstream_addrs[i].addr.storage.ss_family);
|
||||
|
||||
if (fd == -1) {
|
||||
if (fd_ == -1) {
|
||||
auto error = errno;
|
||||
DCLOG(WARN, this) << "socket() failed; errno=" << error;
|
||||
|
||||
|
@ -94,32 +168,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
return SHRPX_ERR_NETWORK;
|
||||
}
|
||||
|
||||
bev_ = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE |
|
||||
BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev_) {
|
||||
int rv;
|
||||
rv = connect(fd_, const_cast<sockaddr *>(
|
||||
&get_config()->downstream_addrs[i].addr.sa),
|
||||
get_config()->downstream_addrs[i].addrlen);
|
||||
if (rv != 0 && errno != EINPROGRESS) {
|
||||
auto error = errno;
|
||||
DCLOG(WARN, this) << "bufferevent_socket_new() failed; errno=" << error;
|
||||
DCLOG(WARN, this) << "connect() failed; errno=" << error;
|
||||
|
||||
connect_blocker->on_failure();
|
||||
close(fd);
|
||||
close(fd_);
|
||||
fd_ = -1;
|
||||
|
||||
return SHRPX_ERR_NETWORK;
|
||||
}
|
||||
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) {
|
||||
if (end == worker_stat->next_downstream) {
|
||||
return SHRPX_ERR_NETWORK;
|
||||
}
|
||||
|
||||
|
@ -133,34 +194,33 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
downstream_ = downstream;
|
||||
|
||||
ioctrl_.set_bev(bev_);
|
||||
|
||||
http_parser_init(&response_htp_, HTTP_RESPONSE);
|
||||
response_htp_.data = downstream_;
|
||||
|
||||
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
|
||||
util::bev_enable_unless(bev_, EV_READ);
|
||||
bufferevent_setcb(bev_, upstream->get_downstream_readcb(),
|
||||
upstream->get_downstream_writecb(),
|
||||
upstream->get_downstream_eventcb(), this);
|
||||
ev_set_cb(&rev_, readcb);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::push_request_headers() {
|
||||
assert(downstream_->get_request_headers_normalized());
|
||||
|
||||
downstream_->assemble_request_cookie();
|
||||
|
||||
auto end_headers = std::end(downstream_->get_request_headers());
|
||||
// Assume that method and request path do not contain \r\n.
|
||||
std::string hdrs = downstream_->get_request_method();
|
||||
hdrs += " ";
|
||||
|
@ -196,14 +256,14 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
hdrs += downstream_->get_request_path();
|
||||
}
|
||||
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()) {
|
||||
hdrs += "Host: ";
|
||||
hdrs += downstream_->get_request_http2_authority();
|
||||
hdrs += "\r\n";
|
||||
}
|
||||
http2::build_http1_headers_from_norm_headers(
|
||||
hdrs, downstream_->get_request_headers());
|
||||
http2::build_http1_headers_from_headers(hdrs,
|
||||
downstream_->get_request_headers());
|
||||
|
||||
if (!downstream_->get_assembled_request_cookie().empty()) {
|
||||
hdrs += "Cookie: ";
|
||||
|
@ -213,7 +273,7 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
|
||||
if (downstream_->get_request_method() != "CONNECT" &&
|
||||
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);
|
||||
hdrs += "Transfer-Encoding: chunked\r\n";
|
||||
|
@ -222,18 +282,17 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
if (downstream_->get_request_connection_close()) {
|
||||
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) {
|
||||
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;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
||||
hdrs += ", ";
|
||||
}
|
||||
hdrs += client_handler_->get_ipaddr();
|
||||
hdrs += "\r\n";
|
||||
} else if (xff != end_headers &&
|
||||
!get_config()->strip_incoming_x_forwarded_for) {
|
||||
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
hdrs += "X-Forwarded-For: ";
|
||||
hdrs += (*xff).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size());
|
||||
|
@ -251,17 +310,16 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
hdrs += "http\r\n";
|
||||
}
|
||||
}
|
||||
auto expect = downstream_->get_norm_request_header("expect");
|
||||
if (expect != end_headers &&
|
||||
!util::strifind((*expect).value.c_str(), "100-continue")) {
|
||||
auto expect = downstream_->get_request_header(http2::HD_EXPECT);
|
||||
if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) {
|
||||
hdrs += "Expect: ";
|
||||
hdrs += (*expect).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size());
|
||||
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 (via != end_headers) {
|
||||
if (via) {
|
||||
hdrs += "Via: ";
|
||||
hdrs += (*via).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||
|
@ -269,7 +327,7 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
}
|
||||
} else {
|
||||
hdrs += "Via: ";
|
||||
if (via != end_headers) {
|
||||
if (via) {
|
||||
hdrs += (*via).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||
hdrs += ", ";
|
||||
|
@ -292,65 +350,55 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
DCLOG(INFO, this) << "HTTP request headers. stream_id="
|
||||
<< downstream_->get_stream_id() << "\n" << hdrp;
|
||||
}
|
||||
auto output = bufferevent_get_output(bev_);
|
||||
int rv;
|
||||
rv = evbuffer_add(output, hdrs.c_str(), hdrs.size());
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
auto output = downstream_->get_request_buf();
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
||||
size_t datalen) {
|
||||
int rv;
|
||||
int chunked = downstream_->get_chunked_request();
|
||||
auto output = bufferevent_get_output(bev_);
|
||||
auto chunked = downstream_->get_chunked_request();
|
||||
auto output = downstream_->get_request_buf();
|
||||
|
||||
if (chunked) {
|
||||
auto chunk_size_hex = util::utox(datalen);
|
||||
chunk_size_hex += "\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;
|
||||
}
|
||||
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||
output->append_cstr("\r\n");
|
||||
}
|
||||
|
||||
rv = evbuffer_add(output, data, datalen);
|
||||
|
||||
if (rv == -1) {
|
||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
output->append(data, datalen);
|
||||
|
||||
if (chunked) {
|
||||
rv = evbuffer_add(output, "\r\n", 2);
|
||||
if (rv == -1) {
|
||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
output->append_cstr("\r\n");
|
||||
}
|
||||
|
||||
signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::end_upload_data() {
|
||||
if (downstream_->get_chunked_request()) {
|
||||
auto output = bufferevent_get_output(bev_);
|
||||
if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) {
|
||||
DCLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
if (!downstream_->get_chunked_request()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto output = downstream_->get_request_buf();
|
||||
output->append_cstr("0\r\n\r\n");
|
||||
|
||||
signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void idle_readcb(bufferevent *bev, void *arg) {
|
||||
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
|
||||
void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
|
||||
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, dconn) << "Idle connection EOF";
|
||||
}
|
||||
auto dconn_pool = dconn->get_dconn_pool();
|
||||
dconn_pool->remove_downstream_connection(dconn);
|
||||
// dconn was deleted
|
||||
|
@ -358,26 +406,10 @@ void idle_readcb(bufferevent *bev, void *arg) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
// Gets called when DownstreamConnection is pooled in ClientHandler.
|
||||
void idle_eventcb(bufferevent *bev, short events, void *arg) {
|
||||
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
// 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";
|
||||
}
|
||||
void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, dconn) << "Idle connection timeout";
|
||||
}
|
||||
auto dconn_pool = dconn->get_dconn_pool();
|
||||
dconn_pool->remove_downstream_connection(dconn);
|
||||
|
@ -391,15 +423,17 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
|
|||
}
|
||||
downstream_ = nullptr;
|
||||
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) {
|
||||
ioctrl_.pause_read(reason);
|
||||
|
@ -407,7 +441,10 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
|
|||
|
||||
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
|
||||
size_t consumed) {
|
||||
ioctrl_.resume_read(reason);
|
||||
if (!downstream_->response_buf_full()) {
|
||||
ioctrl_.resume_read(reason);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -415,11 +452,6 @@ void HttpDownstreamConnection::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 {
|
||||
int htp_msg_begincb(http_parser *htp) {
|
||||
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_minor(htp->http_minor);
|
||||
|
||||
downstream->index_response_headers();
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
// For non-final response code, we just call
|
||||
// on_downstream_header_complete() without changing response
|
||||
|
@ -577,52 +611,62 @@ http_parser_settings htp_hooks = {
|
|||
} // namespace
|
||||
|
||||
int HttpDownstreamConnection::on_read() {
|
||||
reset_timeouts();
|
||||
|
||||
auto input = bufferevent_get_input(bev_);
|
||||
ev_timer_again(loop_, &rt_);
|
||||
uint8_t buf[8192];
|
||||
int rv;
|
||||
|
||||
if (downstream_->get_upgraded()) {
|
||||
// For upgraded connection, just pass data to the upstream.
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
assert(evbuffer_get_length(input) == 0);
|
||||
|
||||
return 0;
|
||||
ssize_t nread;
|
||||
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||
;
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
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_, reinterpret_cast<const uint8_t *>(mem), inputlen, true);
|
||||
rv = downstream_->get_upstream()->on_downstream_body(downstream_, buf,
|
||||
nread, true);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
if (evbuffer_drain(input, inputlen) != 0) {
|
||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
||||
return -1;
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
assert(evbuffer_get_length(input) == 0);
|
||||
return 0;
|
||||
ssize_t nread;
|
||||
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
|
||||
;
|
||||
if (nread == -1) {
|
||||
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 =
|
||||
http_parser_execute(&response_htp_, &htp_hooks,
|
||||
reinterpret_cast<const char *>(mem), inputlen);
|
||||
auto nproc = http_parser_execute(&response_htp_, &htp_hooks,
|
||||
reinterpret_cast<char *>(buf), nread);
|
||||
|
||||
if (evbuffer_drain(input, nread) != 0) {
|
||||
DCLOG(FATAL, this) << "evbuffer_drain() failed";
|
||||
if (nproc != static_cast<size_t>(nread)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "nproc != nread";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -635,29 +679,70 @@ int HttpDownstreamConnection::on_read() {
|
|||
<< 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() {
|
||||
reset_timeouts();
|
||||
ev_timer_again(loop_, &rt_);
|
||||
|
||||
auto upstream = downstream_->get_upstream();
|
||||
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
|
||||
downstream_->get_request_datalen());
|
||||
auto input = downstream_->get_request_buf();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {
|
||||
bufferevent_setcb(bev_, upstream->get_downstream_readcb(),
|
||||
upstream->get_downstream_writecb(),
|
||||
upstream->get_downstream_eventcb(), this);
|
||||
int HttpDownstreamConnection::on_connect() {
|
||||
if (!util::check_socket_connected(fd_)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ev_io_start(loop_, &rev_);
|
||||
ev_set_cb(&wev_, writecb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HttpDownstreamConnection::reset_timeouts() {
|
||||
bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout,
|
||||
&get_config()->downstream_write_timeout);
|
||||
}
|
||||
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
|
||||
|
||||
void HttpDownstreamConnection::signal_write() { ev_io_start(loop_, &wev_); }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -27,9 +27,6 @@
|
|||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "shrpx_downstream_connection.h"
|
||||
|
@ -41,7 +38,8 @@ class DownstreamConnectionPool;
|
|||
|
||||
class HttpDownstreamConnection : public DownstreamConnection {
|
||||
public:
|
||||
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool);
|
||||
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool,
|
||||
struct ev_loop *loop);
|
||||
virtual ~HttpDownstreamConnection();
|
||||
virtual int attach_downstream(Downstream *downstream);
|
||||
virtual void detach_downstream(Downstream *downstream);
|
||||
|
@ -54,22 +52,25 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
||||
virtual void force_resume_read();
|
||||
|
||||
virtual bool get_output_buffer_full();
|
||||
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
|
||||
virtual void on_upstream_change(Upstream *upstream);
|
||||
virtual int on_priority_change(int32_t pri) { return 0; }
|
||||
|
||||
bufferevent *get_bev();
|
||||
|
||||
void reset_timeouts();
|
||||
int on_connect();
|
||||
void signal_write();
|
||||
|
||||
private:
|
||||
bufferevent *bev_;
|
||||
ev_io wev_;
|
||||
ev_io rev_;
|
||||
ev_timer wt_;
|
||||
ev_timer rt_;
|
||||
RateLimit rlimit_;
|
||||
IOControl ioctrl_;
|
||||
http_parser response_htp_;
|
||||
struct ev_loop *loop_;
|
||||
int fd_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_downstream.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "shrpx_http2_downstream_connection.h"
|
||||
//#include "shrpx_http2_downstream_connection.h"
|
||||
#include "shrpx_http.h"
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_error.h"
|
||||
|
@ -43,13 +43,9 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const size_t OUTBUF_MAX_THRES = 16 * 1024;
|
||||
} // namespace
|
||||
|
||||
HttpsUpstream::HttpsUpstream(ClientHandler *handler)
|
||||
: handler_(handler), current_header_length_(0),
|
||||
ioctrl_(handler->get_bev()) {
|
||||
ioctrl_(handler->get_rlimit()) {
|
||||
http_parser_init(&htp_, HTTP_REQUEST);
|
||||
htp_.data = this;
|
||||
}
|
||||
|
@ -153,7 +149,7 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
|
||||
}
|
||||
|
||||
downstream->normalize_request_headers();
|
||||
downstream->index_request_headers();
|
||||
|
||||
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
|
||||
// one http request is fully received.
|
||||
int HttpsUpstream::on_read() {
|
||||
auto bev = handler_->get_bev();
|
||||
auto input = bufferevent_get_input(bev);
|
||||
auto rb = handler_->get_rb();
|
||||
auto downstream = get_downstream();
|
||||
const void *data;
|
||||
size_t datalen;
|
||||
|
||||
// downstream can be nullptr here, because it is initialized in the
|
||||
// callback chain called by http_parser_execute()
|
||||
if (downstream && downstream->get_upgraded()) {
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
std::tie(data, datalen) = rb->get();
|
||||
if (datalen == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto mem = evbuffer_pullup(input, inputlen);
|
||||
|
||||
auto rv = downstream->push_upload_data_chunk(
|
||||
reinterpret_cast<const uint8_t *>(mem), inputlen);
|
||||
reinterpret_cast<const uint8_t *>(data), datalen);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (evbuffer_drain(input, inputlen) != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_drain() failed";
|
||||
return -1;
|
||||
}
|
||||
rb->drain(datalen);
|
||||
|
||||
if (downstream->get_output_buffer_full()) {
|
||||
if (downstream->request_buf_full()) {
|
||||
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);
|
||||
|
||||
|
@ -281,21 +272,15 @@ int HttpsUpstream::on_read() {
|
|||
}
|
||||
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
std::tie(data, datalen) = rb->get();
|
||||
if (datalen == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto mem = evbuffer_pullup(input, inputlen);
|
||||
|
||||
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) {
|
||||
ULOG(FATAL, this) << "evbuffer_drain() failed";
|
||||
return -1;
|
||||
}
|
||||
rb->drain(nread);
|
||||
|
||||
// Well, actually header length + some body bytes
|
||||
current_header_length_ += nread;
|
||||
|
@ -318,6 +303,7 @@ int HttpsUpstream::on_read() {
|
|||
if (error_reply(503) != 0) {
|
||||
return -1;
|
||||
}
|
||||
handler_->signal_write();
|
||||
// Downstream gets deleted after response body is read.
|
||||
return 0;
|
||||
}
|
||||
|
@ -339,6 +325,8 @@ int HttpsUpstream::on_read() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
handler_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -370,13 +358,15 @@ int HttpsUpstream::on_read() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
handler_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// downstream can be NULL here.
|
||||
if (downstream && downstream->get_output_buffer_full()) {
|
||||
if (downstream && downstream->request_buf_full()) {
|
||||
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);
|
||||
|
@ -387,31 +377,45 @@ int HttpsUpstream::on_read() {
|
|||
}
|
||||
|
||||
int HttpsUpstream::on_write() {
|
||||
int rv = 0;
|
||||
auto downstream = get_downstream();
|
||||
if (downstream) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
rv = downstream->resume_read(SHRPX_NO_BUFFER,
|
||||
downstream->get_response_datalen());
|
||||
if (!downstream) {
|
||||
return 0;
|
||||
}
|
||||
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; }
|
||||
|
@ -424,6 +428,10 @@ void HttpsUpstream::pause_read(IOCtrlReason reason) {
|
|||
|
||||
int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||
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)) {
|
||||
// Process remaining data in input buffer here because these bytes
|
||||
// are not notified by readcb until new data arrive.
|
||||
|
@ -434,272 +442,147 @@ int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
|||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void https_downstream_readcb(bufferevent *bev, void *ptr) {
|
||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
||||
int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
|
||||
int rv;
|
||||
|
||||
rv = downstream->on_read();
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_RESET) {
|
||||
delete upstream->get_client_handler();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (downstream) {
|
||||
downstream->add_response_sent_bodylen(html.size());
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
} else {
|
||||
handler_->write_accesslog(1, 1, status_code, html.size());
|
||||
if (rv == DownstreamConnection::ERR_EOF) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bufferevent_data_cb HttpsUpstream::get_downstream_readcb() {
|
||||
return https_downstream_readcb;
|
||||
int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
|
||||
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() {
|
||||
return https_downstream_writecb;
|
||||
int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||
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() {
|
||||
return https_downstream_eventcb;
|
||||
int HttpsUpstream::error_reply(unsigned int status_code) {
|
||||
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) {
|
||||
|
@ -737,15 +620,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
hdrs += " ";
|
||||
hdrs += http2::get_status_string(downstream->get_response_http_status());
|
||||
hdrs += "\r\n";
|
||||
downstream->normalize_response_headers();
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!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);
|
||||
}
|
||||
auto end_headers = std::end(downstream->get_response_headers());
|
||||
http2::build_http1_headers_from_norm_headers(
|
||||
hdrs, downstream->get_response_headers());
|
||||
|
||||
http2::build_http1_headers_from_headers(hdrs,
|
||||
downstream->get_response_headers());
|
||||
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
hdrs += "\r\n";
|
||||
|
@ -754,11 +639,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
log_response_headers(hdrs);
|
||||
}
|
||||
|
||||
auto output = bufferevent_get_output(handler_->get_bev());
|
||||
if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
downstream->clear_response_headers();
|
||||
|
||||
|
@ -779,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
hdrs += "Connection: close\r\n";
|
||||
}
|
||||
|
||||
if (downstream->get_norm_response_header("alt-svc") == end_headers) {
|
||||
// We won't change or alter alt-svc from backend at the moment.
|
||||
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
|
||||
// We won't change or alter alt-svc from backend for now
|
||||
if (!get_config()->altsvcs.empty()) {
|
||||
hdrs += "Alt-Svc: ";
|
||||
|
||||
|
@ -803,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
hdrs += get_config()->server_name;
|
||||
hdrs += "\r\n";
|
||||
} else {
|
||||
auto server = downstream->get_norm_response_header("server");
|
||||
if (server != end_headers) {
|
||||
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||
if (server) {
|
||||
hdrs += "Server: ";
|
||||
hdrs += (*server).value;
|
||||
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 (via != end_headers) {
|
||||
if (via) {
|
||||
hdrs += "Via: ";
|
||||
hdrs += (*via).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||
|
@ -821,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
} else {
|
||||
hdrs += "Via: ";
|
||||
if (via != end_headers) {
|
||||
if (via) {
|
||||
hdrs += (*via).value;
|
||||
http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size());
|
||||
hdrs += ", ";
|
||||
|
@ -844,11 +725,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
log_response_headers(hdrs);
|
||||
}
|
||||
|
||||
auto output = bufferevent_get_output(handler_->get_bev());
|
||||
if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
output->append(hdrs.c_str(), hdrs.size());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -856,45 +733,30 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
int HttpsUpstream::on_downstream_body(Downstream *downstream,
|
||||
const uint8_t *data, size_t len,
|
||||
bool flush) {
|
||||
int rv;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
auto output = bufferevent_get_output(handler_->get_bev());
|
||||
auto output = downstream->get_response_buf();
|
||||
if (downstream->get_chunked_response()) {
|
||||
auto chunk_size_hex = util::utox(len);
|
||||
chunk_size_hex += "\r\n";
|
||||
|
||||
rv = evbuffer_add(output, 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(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||
}
|
||||
output->append(data, len);
|
||||
|
||||
downstream->add_response_sent_bodylen(len);
|
||||
|
||||
if (downstream->get_chunked_response()) {
|
||||
if (evbuffer_add(output, "\r\n", 2) != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
output->append_cstr("\r\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
||||
if (downstream->get_chunked_response()) {
|
||||
auto output = bufferevent_get_output(handler_->get_bev());
|
||||
if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
auto output = downstream->get_response_buf();
|
||||
output->append_cstr("0\r\n\r\n");
|
||||
}
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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;
|
||||
}
|
||||
|
||||
void HttpsUpstream::reset_timeouts() {
|
||||
handler_->set_upstream_timeouts(&get_config()->upstream_read_timeout,
|
||||
&get_config()->upstream_write_timeout);
|
||||
}
|
||||
|
||||
void HttpsUpstream::on_handler_delete() {
|
||||
if (downstream_ && downstream_->accesslog_ready()) {
|
||||
handler_->write_accesslog(downstream_.get());
|
||||
|
@ -956,4 +813,6 @@ int HttpsUpstream::on_downstream_reset() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
MemchunkPool4K *HttpsUpstream::get_mcpool() { return &mcpool_; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -49,9 +52,12 @@ public:
|
|||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
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 delete_downstream();
|
||||
Downstream *get_downstream() const;
|
||||
|
@ -70,7 +76,7 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
void reset_current_header_length();
|
||||
void log_response_headers(const std::string &hdrs) const;
|
||||
|
@ -79,6 +85,8 @@ private:
|
|||
ClientHandler *handler_;
|
||||
http_parser htp_;
|
||||
size_t current_header_length_;
|
||||
// must be put before downstream_
|
||||
MemchunkPool4K mcpool_;
|
||||
std::unique_ptr<Downstream> downstream_;
|
||||
IOControl ioctrl_;
|
||||
};
|
||||
|
|
|
@ -26,42 +26,40 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#include "shrpx_rate_limit.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
IOControl::IOControl(bufferevent *bev) : bev_(bev), rdbits_(0) {}
|
||||
IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {}
|
||||
|
||||
IOControl::~IOControl() {}
|
||||
|
||||
void IOControl::set_bev(bufferevent *bev) { bev_ = bev; }
|
||||
|
||||
void IOControl::pause_read(IOCtrlReason reason) {
|
||||
rdbits_ |= reason;
|
||||
if (bev_) {
|
||||
util::bev_disable_unless(bev_, EV_READ);
|
||||
if (lim_) {
|
||||
lim_->stopw();
|
||||
}
|
||||
}
|
||||
|
||||
bool IOControl::resume_read(IOCtrlReason reason) {
|
||||
rdbits_ &= ~reason;
|
||||
if (rdbits_ == 0) {
|
||||
if (bev_) {
|
||||
util::bev_enable_unless(bev_, EV_READ);
|
||||
if (lim_) {
|
||||
lim_->startw();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IOControl::force_resume_read() {
|
||||
rdbits_ = 0;
|
||||
if (bev_) {
|
||||
util::bev_enable_unless(bev_, EV_READ);
|
||||
if (lim_) {
|
||||
lim_->startw();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,9 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include "shrpx_rate_limit.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -38,9 +39,8 @@ enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 };
|
|||
|
||||
class IOControl {
|
||||
public:
|
||||
IOControl(bufferevent *bev);
|
||||
IOControl(RateLimit *lim);
|
||||
~IOControl();
|
||||
void set_bev(bufferevent *bev);
|
||||
void pause_read(IOCtrlReason reason);
|
||||
// Returns true if read operation is enabled after this call
|
||||
bool resume_read(IOCtrlReason reason);
|
||||
|
@ -48,7 +48,7 @@ public:
|
|||
void force_resume_read();
|
||||
|
||||
private:
|
||||
bufferevent *bev_;
|
||||
RateLimit *lim_;
|
||||
uint32_t rdbits_;
|
||||
};
|
||||
|
||||
|
|
|
@ -29,10 +29,7 @@
|
|||
#include <cerrno>
|
||||
#include <thread>
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_thread_event_receiver.h"
|
||||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
|
@ -40,16 +37,16 @@
|
|||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_connect_blocker.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "shrpx_accept_handler.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
||||
auto h = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
// If we are in graceful shutdown period, we must not enable
|
||||
// evlisteners again.
|
||||
|
@ -57,23 +54,24 @@ void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
|||
return;
|
||||
}
|
||||
|
||||
listener_handler->enable_evlistener();
|
||||
h->enable_acceptor();
|
||||
}
|
||||
} // 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)
|
||||
: evbase_(evbase), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||
rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||
evbase, get_config()->worker_rate_limit_cfg)),
|
||||
evlistener4_(nullptr), evlistener6_(nullptr),
|
||||
evlistener_disable_timerev_(
|
||||
evtimer_new(evbase, evlistener_disable_cb, this)),
|
||||
worker_stat_(util::make_unique<WorkerStat>()), num_worker_shutdown_(0),
|
||||
worker_round_robin_cnt_(0) {}
|
||||
: loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||
// rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||
// evbase, get_config()->worker_rate_limit_cfg)),
|
||||
worker_stat_(util::make_unique<WorkerStat>()),
|
||||
worker_round_robin_cnt_(0) {
|
||||
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||
disable_acceptor_timer_.data = this;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -82,68 +80,19 @@ void ListenHandler::worker_reopen_log_files() {
|
|||
memset(&wev, 0, sizeof(wev));
|
||||
wev.type = REOPEN_LOG;
|
||||
|
||||
for (auto &info : workers_) {
|
||||
bufferevent_write(info->bev, &wev, sizeof(wev));
|
||||
for (auto &worker : workers_) {
|
||||
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) {
|
||||
#ifndef NOTHREADS
|
||||
workers_.resize(0);
|
||||
assert(workers_.size() == 0);
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
int rv;
|
||||
auto info = util::make_unique<WorkerInfo>();
|
||||
rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
|
||||
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));
|
||||
auto worker = util::make_unique<Worker>(sv_ssl_ctx_, cl_ssl_ctx_);
|
||||
worker->run();
|
||||
workers_.push_back(std::move(worker));
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||
|
@ -162,7 +111,7 @@ void ListenHandler::join_worker() {
|
|||
}
|
||||
|
||||
for (auto &worker : workers_) {
|
||||
worker->fut.get();
|
||||
worker->wait();
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Thread #" << n << " joined";
|
||||
}
|
||||
|
@ -185,29 +134,22 @@ void ListenHandler::graceful_shutdown_worker() {
|
|||
LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
|
||||
}
|
||||
|
||||
auto output = bufferevent_get_output(worker->bev);
|
||||
|
||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
}
|
||||
worker->send(wev);
|
||||
}
|
||||
}
|
||||
|
||||
int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
||||
int addrlen) {
|
||||
int ListenHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
||||
}
|
||||
|
||||
evutil_make_socket_closeonexec(fd);
|
||||
|
||||
if (get_config()->num_worker == 1) {
|
||||
|
||||
if (worker_stat_->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "Too many connections >="
|
||||
LLOG(INFO, this) << "Too many connections >="
|
||||
<< get_config()->worker_frontend_connections;
|
||||
}
|
||||
|
||||
|
@ -215,9 +157,8 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
return -1;
|
||||
}
|
||||
|
||||
auto client =
|
||||
ssl::accept_connection(evbase_, rate_limit_group_, sv_ssl_ctx_, fd,
|
||||
addr, addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
auto client = ssl::accept_connection(loop_, sv_ssl_ctx_, fd, addr, addrlen,
|
||||
worker_stat_.get(), &dconn_pool_);
|
||||
if (!client) {
|
||||
LLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
|
||||
|
@ -230,6 +171,7 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t idx = worker_round_robin_cnt_ % workers_.size();
|
||||
++worker_round_robin_cnt_;
|
||||
WorkerEvent wev;
|
||||
|
@ -238,140 +180,77 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
wev.client_fd = fd;
|
||||
memcpy(&wev.client_addr, addr, addrlen);
|
||||
wev.client_addrlen = addrlen;
|
||||
auto output = bufferevent_get_output(workers_[idx]->bev);
|
||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
workers_[idx]->send(wev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
event_base *ListenHandler::get_evbase() const { return evbase_; }
|
||||
|
||||
int ListenHandler::create_http2_session() {
|
||||
int rv;
|
||||
http2session_ = util::make_unique<Http2Session>(evbase_, cl_ssl_ctx_);
|
||||
rv = http2session_->init_notification();
|
||||
return rv;
|
||||
struct ev_loop *ListenHandler::get_loop() const {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
int ListenHandler::create_http1_connect_blocker() {
|
||||
int rv;
|
||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>();
|
||||
void ListenHandler::create_http2_session() {
|
||||
http2session_ = util::make_unique<Http2Session>(loop_, cl_ssl_ctx_);
|
||||
}
|
||||
|
||||
rv = http1_connect_blocker_->init(evbase_);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
void ListenHandler::create_http1_connect_blocker() {
|
||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>(loop_);
|
||||
}
|
||||
|
||||
const WorkerStat *ListenHandler::get_worker_stat() const {
|
||||
return worker_stat_.get();
|
||||
}
|
||||
|
||||
void ListenHandler::set_evlistener4(evconnlistener *evlistener4) {
|
||||
evlistener4_ = evlistener4;
|
||||
void ListenHandler::set_acceptor4(std::unique_ptr<AcceptHandler> h) {
|
||||
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) {
|
||||
evlistener6_ = evlistener6;
|
||||
void ListenHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptor6_ = std::move(h);
|
||||
}
|
||||
|
||||
evconnlistener *ListenHandler::get_evlistener6() const { return evlistener6_; }
|
||||
AcceptHandler *ListenHandler::get_acceptor6() const { return acceptor6_.get(); }
|
||||
|
||||
void ListenHandler::enable_evlistener() {
|
||||
if (evlistener4_) {
|
||||
evconnlistener_enable(evlistener4_);
|
||||
void ListenHandler::enable_acceptor() {
|
||||
if (acceptor4_) {
|
||||
acceptor4_->enable();
|
||||
}
|
||||
|
||||
if (evlistener6_) {
|
||||
evconnlistener_enable(evlistener6_);
|
||||
if (acceptor6_) {
|
||||
acceptor6_->enable();
|
||||
}
|
||||
}
|
||||
|
||||
void ListenHandler::disable_evlistener() {
|
||||
if (evlistener4_) {
|
||||
evconnlistener_disable(evlistener4_);
|
||||
void ListenHandler::disable_acceptor() {
|
||||
if (acceptor4_) {
|
||||
acceptor4_->disable();
|
||||
}
|
||||
|
||||
if (evlistener6_) {
|
||||
evconnlistener_disable(evlistener6_);
|
||||
if (acceptor6_) {
|
||||
acceptor6_->disable();
|
||||
}
|
||||
}
|
||||
|
||||
void ListenHandler::disable_evlistener_temporary(const timeval *timeout) {
|
||||
int rv;
|
||||
|
||||
if (timeout->tv_sec == 0 ||
|
||||
evtimer_pending(evlistener_disable_timerev_, nullptr)) {
|
||||
void ListenHandler::disable_acceptor_temporary(ev_tstamp t) {
|
||||
if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disable_evlistener();
|
||||
disable_acceptor();
|
||||
|
||||
rv = evtimer_add(evlistener_disable_timerev_, timeout);
|
||||
|
||||
if (rv < 0) {
|
||||
LOG(ERROR) << "evtimer_add for evlistener_disable_timerev_ failed";
|
||||
}
|
||||
ev_timer_set(&disable_acceptor_timer_, t, 0.);
|
||||
ev_timer_start(loop_, &disable_acceptor_timer_);
|
||||
}
|
||||
|
||||
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() {
|
||||
perform_accept_pending_connection(this, evlistener4_);
|
||||
perform_accept_pending_connection(this, evlistener6_);
|
||||
}
|
||||
|
||||
void ListenHandler::notify_worker_shutdown() {
|
||||
if (++num_worker_shutdown_ == workers_.size()) {
|
||||
event_base_loopbreak(evbase_);
|
||||
if (acceptor4_) {
|
||||
acceptor4_->accept_connection();
|
||||
}
|
||||
if (acceptor6_) {
|
||||
acceptor6_->accept_connection();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,61 +32,48 @@
|
|||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // NOTHREADS
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/listener.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include "shrpx_downstream_connection_pool.h"
|
||||
|
||||
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 ConnectBlocker;
|
||||
class AcceptHandler;
|
||||
class Worker;
|
||||
struct WorkerStat;
|
||||
|
||||
// TODO should be renamed as ConnectionHandler
|
||||
class ListenHandler {
|
||||
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();
|
||||
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 worker_reopen_log_files();
|
||||
event_base *get_evbase() const;
|
||||
int create_http2_session();
|
||||
int create_http1_connect_blocker();
|
||||
struct ev_loop *get_loop() const;
|
||||
void create_http2_session();
|
||||
void create_http1_connect_blocker();
|
||||
const WorkerStat *get_worker_stat() const;
|
||||
void set_evlistener4(evconnlistener *evlistener4);
|
||||
evconnlistener *get_evlistener4() const;
|
||||
void set_evlistener6(evconnlistener *evlistener6);
|
||||
evconnlistener *get_evlistener6() const;
|
||||
void enable_evlistener();
|
||||
void disable_evlistener();
|
||||
void disable_evlistener_temporary(const timeval *timeout);
|
||||
void set_acceptor4(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor4() const;
|
||||
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor6() const;
|
||||
void enable_acceptor();
|
||||
void disable_acceptor();
|
||||
void disable_acceptor_temporary(ev_tstamp t);
|
||||
void accept_pending_connection();
|
||||
void graceful_shutdown_worker();
|
||||
void join_worker();
|
||||
void notify_worker_shutdown();
|
||||
|
||||
private:
|
||||
DownstreamConnectionPool dconn_pool_;
|
||||
std::vector<std::unique_ptr<WorkerInfo>> workers_;
|
||||
event_base *evbase_;
|
||||
std::vector<std::unique_ptr<Worker>> workers_;
|
||||
struct ev_loop *loop_;
|
||||
// The frontend server SSL_CTX
|
||||
SSL_CTX *sv_ssl_ctx_;
|
||||
// The backend server SSL_CTX
|
||||
|
@ -95,12 +82,11 @@ private:
|
|||
// multi-threaded case, see shrpx_worker.cc.
|
||||
std::unique_ptr<Http2Session> http2session_;
|
||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
||||
bufferevent_rate_limit_group *rate_limit_group_;
|
||||
evconnlistener *evlistener4_;
|
||||
evconnlistener *evlistener6_;
|
||||
event *evlistener_disable_timerev_;
|
||||
// bufferevent_rate_limit_group *rate_limit_group_;
|
||||
std::unique_ptr<AcceptHandler> acceptor4_;
|
||||
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||
ev_timer disable_acceptor_timer_;
|
||||
std::unique_ptr<WorkerStat> worker_stat_;
|
||||
size_t num_worker_shutdown_;
|
||||
unsigned int worker_round_robin_cnt_;
|
||||
};
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
|
|||
case SHRPX_LOGF_HTTP:
|
||||
if (downstream) {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -48,10 +48,9 @@ class Downstream;
|
|||
#define LLOG(SEVERITY, LISTEN) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[LISTEN:" << LISTEN << "] ")
|
||||
|
||||
// ThreadEventReceiver log
|
||||
#define TLOG(SEVERITY, THREAD_RECV) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[THREAD_RECV:" << THREAD_RECV << "]" \
|
||||
" ")
|
||||
// Worker log
|
||||
#define WLOG(SEVERITY, WORKER) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[WORKER:" << WORKER << "] ")
|
||||
|
||||
// ClientHandler log
|
||||
#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 {
|
||||
const size_t OUTBUF_MAX_THRES = 16 * 1024;
|
||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
|
||||
int flags, void *user_data) {
|
||||
int rv;
|
||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto wb = handler->get_wb();
|
||||
|
||||
// Check buffer length and return WOULDBLOCK if it is large enough.
|
||||
if (handler->get_outbuf_length() + upstream->sendbuf.get_buflen() >=
|
||||
OUTBUF_MAX_THRES) {
|
||||
if (wb->wleft() == 0) {
|
||||
return SPDYLAY_ERR_WOULDBLOCK;
|
||||
}
|
||||
|
||||
rv = upstream->sendbuf.add(data, len);
|
||||
if (rv != 0) {
|
||||
ULOG(FATAL, upstream) << "evbuffer_add() failed";
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
return len;
|
||||
auto nread = wb->write(data, len);
|
||||
|
||||
handler->update_warmup_writelen(nread);
|
||||
|
||||
return nread;
|
||||
}
|
||||
} // 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) {
|
||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto bev = handler->get_bev();
|
||||
auto input = bufferevent_get_input(bev);
|
||||
int nread = evbuffer_remove(input, data, len);
|
||||
if (nread == -1) {
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
} else if (nread == 0) {
|
||||
auto rb = handler->get_rb();
|
||||
const void *data;
|
||||
size_t nread;
|
||||
|
||||
std::tie(data, nread) = rb->get();
|
||||
if (nread == 0) {
|
||||
return SPDYLAY_ERR_WOULDBLOCK;
|
||||
} else {
|
||||
return nread;
|
||||
}
|
||||
|
||||
nread = std::min(nread, len);
|
||||
|
||||
memcpy(buf, data, nread);
|
||||
rb->drain(nread);
|
||||
|
||||
return nread;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -155,47 +152,36 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
auto downstream = upstream->add_pending_downstream(
|
||||
frame->syn_stream.stream_id, frame->syn_stream.pri);
|
||||
|
||||
downstream->init_upstream_timer();
|
||||
downstream->reset_upstream_rtimer();
|
||||
downstream->init_response_body_buf();
|
||||
|
||||
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) {
|
||||
if (strcmp(nv[i], ":path") == 0) {
|
||||
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->add_request_header(nv[i], nv[i + 1]);
|
||||
}
|
||||
|
||||
downstream->normalize_request_headers();
|
||||
downstream->index_request_headers();
|
||||
|
||||
bool is_connect = method && strcmp("CONNECT", method) == 0;
|
||||
if (!path || !host || !method || http2::lws(host) || http2::lws(path) ||
|
||||
http2::lws(method) ||
|
||||
(!is_connect && (!scheme || http2::lws(scheme)))) {
|
||||
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||
auto host = downstream->get_request_header(http2::HD__HOST);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
downstream->set_request_method(method);
|
||||
downstream->set_request_method(method->value);
|
||||
if (is_connect) {
|
||||
downstream->set_request_http2_authority(path);
|
||||
downstream->set_request_http2_authority(path->value);
|
||||
} else {
|
||||
downstream->set_request_http2_scheme(scheme);
|
||||
downstream->set_request_http2_authority(host);
|
||||
downstream->set_request_path(path);
|
||||
downstream->set_request_http2_scheme(scheme->value);
|
||||
downstream->set_request_http2_authority(host->value);
|
||||
downstream->set_request_path(path->value);
|
||||
}
|
||||
|
||||
downstream->set_request_start_time(
|
||||
|
@ -404,13 +390,14 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
|
|||
} // namespace
|
||||
|
||||
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
||||
: downstream_queue_(get_config()->http2_proxy
|
||||
? get_config()->downstream_connections_per_host
|
||||
: 0),
|
||||
: downstream_queue_(
|
||||
get_config()->http2_proxy
|
||||
? 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->set_bev_cb(spdy_readcb, 0, spdy_eventcb);
|
||||
reset_timeouts();
|
||||
|
||||
spdylay_session_callbacks callbacks;
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.send_callback = send_callback;
|
||||
|
@ -461,8 +448,10 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
|||
assert(rv == 0);
|
||||
}
|
||||
|
||||
// TODO Maybe call from outside?
|
||||
send();
|
||||
handler_->reset_upstream_read_timeout(
|
||||
get_config()->http2_upstream_read_timeout);
|
||||
|
||||
handler_->signal_write();
|
||||
}
|
||||
|
||||
SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); }
|
||||
|
@ -478,18 +467,15 @@ int SpdyUpstream::on_read() {
|
|||
}
|
||||
return rv;
|
||||
}
|
||||
return send();
|
||||
|
||||
handler_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::on_write() { return send(); }
|
||||
|
||||
// After this function call, downstream may be deleted.
|
||||
int SpdyUpstream::send() {
|
||||
int SpdyUpstream::on_write() {
|
||||
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_);
|
||||
if (rv != 0) {
|
||||
|
@ -498,17 +484,9 @@ int SpdyUpstream::send() {
|
|||
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 &&
|
||||
spdylay_session_want_write(session_) == 0 &&
|
||||
handler_->get_outbuf_length() == 0) {
|
||||
handler_->get_wb()->rleft() == 0) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, this) << "No more read/write for this SPDY session";
|
||||
}
|
||||
|
@ -517,23 +495,19 @@ int SpdyUpstream::send() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::on_event() { return 0; }
|
||||
|
||||
ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; }
|
||||
|
||||
namespace {
|
||||
void spdy_downstream_readcb(bufferevent *bev, void *ptr) {
|
||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
||||
int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
||||
|
||||
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
|
||||
// If upstream SPDY stream was closed, we just close downstream,
|
||||
// because there is no consumer now. Downstream connection is also
|
||||
// closed in this case.
|
||||
upstream->remove_downstream(downstream);
|
||||
remove_downstream(downstream);
|
||||
// downstrea was deleted
|
||||
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
// here. Deleting downstream will be taken place at
|
||||
// on_stream_close_callback.
|
||||
upstream->rst_stream(downstream,
|
||||
infer_upstream_rst_stream_status_code(
|
||||
downstream->get_response_rst_stream_error_code()));
|
||||
rst_stream(downstream,
|
||||
infer_upstream_rst_stream_status_code(
|
||||
downstream->get_response_rst_stream_error_code()));
|
||||
downstream->pop_downstream_connection();
|
||||
dconn = nullptr;
|
||||
} else {
|
||||
auto rv = downstream->on_read();
|
||||
if (rv == DownstreamConnection::ERR_EOF) {
|
||||
return downstream_eof(dconn);
|
||||
}
|
||||
if (rv != 0) {
|
||||
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;
|
||||
if (rv != DownstreamConnection::ERR_NET) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, dconn) << "HTTP parser failure";
|
||||
}
|
||||
}
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
// Clearly, we have to close downstream connection on http parser
|
||||
// failure.
|
||||
downstream->pop_downstream_connection();
|
||||
dconn = nullptr;
|
||||
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
||||
}
|
||||
}
|
||||
if (upstream->send() != 0) {
|
||||
delete upstream->get_client_handler();
|
||||
return;
|
||||
}
|
||||
|
||||
handler_->signal_write();
|
||||
// At this point, downstream may be deleted.
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void spdy_downstream_writecb(bufferevent *bev, void *ptr) {
|
||||
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
dconn->on_write();
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
|
||||
auto dconn = static_cast<DownstreamConnection *>(ptr);
|
||||
int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
|
||||
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, dconn) << "Connection established. stream_id="
|
||||
<< downstream->get_stream_id();
|
||||
}
|
||||
int 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)) {
|
||||
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) {
|
||||
// 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
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
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
|
||||
// will be pooled in on_stream_close_callback.
|
||||
downstream->pop_downstream_connection();
|
||||
dconn = nullptr;
|
||||
// downstream wil be deleted in on_stream_close_callback.
|
||||
int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
||||
auto downstream = dconn->get_downstream();
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
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) {
|
||||
// Server may indicate the end of the request by EOF
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
|
||||
}
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
// For tunneled connection, MSG_COMPLETE signals
|
||||
// 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());
|
||||
if (downstream->get_upgraded()) {
|
||||
on_downstream_body_complete(downstream);
|
||||
} 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) {
|
||||
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);
|
||||
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
||||
}
|
||||
} else {
|
||||
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||
unsigned int status;
|
||||
if (events & Downstream::EVENT_TIMEOUT) {
|
||||
status = 504;
|
||||
} else {
|
||||
unsigned int status;
|
||||
if (events & BEV_EVENT_TIMEOUT) {
|
||||
status = 504;
|
||||
} else {
|
||||
status = 502;
|
||||
}
|
||||
if (upstream->error_reply(downstream, status) != 0) {
|
||||
delete upstream->get_client_handler();
|
||||
return;
|
||||
}
|
||||
status = 502;
|
||||
}
|
||||
if (error_reply(downstream, status) != 0) {
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
handler_->signal_write();
|
||||
// At this point, downstream may be deleted.
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) {
|
||||
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) {
|
||||
auto downstream = static_cast<Downstream *>(source->ptr);
|
||||
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();
|
||||
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));
|
||||
}
|
||||
|
||||
int nread = evbuffer_remove(body, buf, length);
|
||||
if (nread == -1) {
|
||||
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
auto nread = body->remove(buf, length);
|
||||
auto body_empty = body->rleft() == 0;
|
||||
|
||||
if (nread == 0 &&
|
||||
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
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) {
|
||||
downstream->reset_upstream_wtimer();
|
||||
} else {
|
||||
if (body_empty) {
|
||||
downstream->disable_upstream_wtimer();
|
||||
} else {
|
||||
downstream->reset_upstream_wtimer();
|
||||
}
|
||||
|
||||
if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) {
|
||||
|
@ -797,13 +739,8 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
|||
int rv;
|
||||
auto html = http::create_error_html(status_code);
|
||||
downstream->set_response_http_status(status_code);
|
||||
downstream->init_response_body_buf();
|
||||
auto body = downstream->get_response_body_buf();
|
||||
rv = evbuffer_add(body, html.c_str(), html.size());
|
||||
if (rv == -1) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
auto body = downstream->get_response_buf();
|
||||
body->append(html.c_str(), html.size());
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
spdylay_data_provider data_prd;
|
||||
|
@ -830,18 +767,6 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
|||
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,
|
||||
int32_t priority) {
|
||||
auto downstream = util::make_unique<Downstream>(this, stream_id, priority);
|
||||
|
@ -863,6 +788,9 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) {
|
|||
if (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) {
|
||||
|
@ -886,10 +814,10 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||
}
|
||||
downstream->normalize_response_headers();
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!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);
|
||||
}
|
||||
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++] = "HTTP/1.1";
|
||||
for (auto &hd : downstream->get_response_headers()) {
|
||||
if (hd.name.empty() || hd.name.c_str()[0] == ':' ||
|
||||
util::strieq(hd.name.c_str(), "transfer-encoding") ||
|
||||
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();
|
||||
if (hd.name.empty() || hd.name.c_str()[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
nv[hdidx++] = "server";
|
||||
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 (!via_value.empty()) {
|
||||
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||
if (via) {
|
||||
via_value = via->value;
|
||||
via_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,
|
||||
const uint8_t *data, size_t len,
|
||||
bool flush) {
|
||||
auto body = downstream->get_response_body_buf();
|
||||
int rv = evbuffer_add(body, data, len);
|
||||
if (rv != 0) {
|
||||
ULOG(FATAL, this) << "evbuffer_add() failed";
|
||||
return -1;
|
||||
}
|
||||
auto body = downstream->get_response_buf();
|
||||
body->append(data, len);
|
||||
|
||||
if (flush) {
|
||||
spdylay_session_resume_data(session_, downstream->get_stream_id());
|
||||
|
@ -985,16 +918,6 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -1027,7 +950,8 @@ int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
|||
downstream->dec_request_datalen(consumed);
|
||||
}
|
||||
|
||||
return send();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
||||
|
@ -1040,7 +964,8 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
|
|||
return -1;
|
||||
}
|
||||
|
||||
return send();
|
||||
handler_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdyUpstream::consume(int32_t stream_id, size_t len) {
|
||||
|
@ -1068,11 +993,6 @@ int SpdyUpstream::on_timeout(Downstream *downstream) {
|
|||
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() {
|
||||
for (auto &ent : downstream_queue_.get_active_downstreams()) {
|
||||
if (ent.second->accesslog_ready()) {
|
||||
|
@ -1107,12 +1027,11 @@ int SpdyUpstream::on_downstream_reset() {
|
|||
}
|
||||
}
|
||||
|
||||
rv = send();
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
handler_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MemchunkPool4K *SpdyUpstream::get_mcpool() { return &mcpool_; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -29,12 +29,14 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <spdylay/spdylay.h>
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_downstream_queue.h"
|
||||
#include "memchunk.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -46,15 +48,14 @@ public:
|
|||
virtual ~SpdyUpstream();
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_event();
|
||||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
int send();
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
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);
|
||||
void remove_downstream(Downstream *downstream);
|
||||
Downstream *find_downstream(int32_t stream_id);
|
||||
|
@ -76,7 +77,7 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
bool get_flow_control() const;
|
||||
|
||||
|
@ -85,9 +86,9 @@ public:
|
|||
void start_downstream(Downstream *downstream);
|
||||
void initiate_downstream(std::unique_ptr<Downstream> downstream);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
// must be put before downstream_queue_
|
||||
MemchunkPool4K mcpool_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ClientHandler *handler_;
|
||||
spdylay_session *session_;
|
||||
|
|
|
@ -36,9 +36,6 @@
|
|||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#ifdef HAVE_SPDYLAY
|
||||
|
@ -450,9 +447,7 @@ SSL_CTX *create_ssl_client_context() {
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
ClientHandler *accept_connection(event_base *evbase,
|
||||
bufferevent_rate_limit_group *rate_limit_group,
|
||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
||||
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||
sockaddr *addr, int addrlen,
|
||||
WorkerStat *worker_stat,
|
||||
DownstreamConnectionPool *dconn_pool) {
|
||||
|
@ -474,7 +469,6 @@ ClientHandler *accept_connection(event_base *evbase,
|
|||
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
|
||||
}
|
||||
SSL *ssl = nullptr;
|
||||
bufferevent *bev;
|
||||
if (ssl_ctx) {
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl) {
|
||||
|
@ -490,21 +484,11 @@ ClientHandler *accept_connection(event_base *evbase,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bev = bufferevent_openssl_socket_new(
|
||||
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;
|
||||
SSL_set_accept_state(ssl);
|
||||
}
|
||||
|
||||
return new ClientHandler(bev, rate_limit_group, fd, ssl, host, service,
|
||||
worker_stat, dconn_pool);
|
||||
return new ClientHandler(loop, fd, ssl, host, service, worker_stat,
|
||||
dconn_pool);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -47,9 +47,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
|||
|
||||
SSL_CTX *create_ssl_client_context();
|
||||
|
||||
ClientHandler *accept_connection(event_base *evbase,
|
||||
bufferevent_rate_limit_group *rate_limit_group,
|
||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
||||
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||
sockaddr *addr, int addrlen,
|
||||
WorkerStat *worker_stat,
|
||||
DownstreamConnectionPool *dconn_pool);
|
||||
|
|
|
@ -38,95 +38,93 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
ThreadEventReceiver::ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
||||
ThreadEventReceiver::ThreadEventReceiver(SSL_CTX *ssl_ctx,
|
||||
Http2Session *http2session,
|
||||
ConnectBlocker *http1_connect_blocker)
|
||||
: evbase_(evbase), ssl_ctx_(ssl_ctx), http2session_(http2session),
|
||||
: ssl_ctx_(ssl_ctx), http2session_(http2session),
|
||||
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>()) {}
|
||||
|
||||
ThreadEventReceiver::~ThreadEventReceiver() {
|
||||
bufferevent_rate_limit_group_free(rate_limit_group_);
|
||||
}
|
||||
ThreadEventReceiver::~ThreadEventReceiver() {}
|
||||
|
||||
void ThreadEventReceiver::on_read(bufferevent *bev) {
|
||||
auto input = bufferevent_get_input(bev);
|
||||
while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
||||
WorkerEvent wev;
|
||||
int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
||||
if (nread == -1) {
|
||||
TLOG(FATAL, this) << "evbuffer_remove() failed";
|
||||
continue;
|
||||
}
|
||||
if (nread != sizeof(wev)) {
|
||||
TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
||||
<< sizeof(wev) << " Actual:" << nread;
|
||||
continue;
|
||||
}
|
||||
void ThreadEventReceiver::on_read() {
|
||||
// auto input = bufferevent_get_input(bev);
|
||||
// while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
||||
// WorkerEvent wev;
|
||||
// int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
||||
// if (nread == -1) {
|
||||
// TLOG(FATAL, this) << "evbuffer_remove() failed";
|
||||
// continue;
|
||||
// }
|
||||
// if (nread != sizeof(wev)) {
|
||||
// TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
||||
// << sizeof(wev) << " Actual:" << nread;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (wev.type == REOPEN_LOG) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||
<< ")";
|
||||
}
|
||||
// if (wev.type == REOPEN_LOG) {
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||
// << ")";
|
||||
// }
|
||||
|
||||
reopen_log_files();
|
||||
// reopen_log_files();
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||
LOG(NOTICE) << "Graceful shutdown commencing";
|
||||
// if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||
// LOG(NOTICE) << "Graceful shutdown commencing";
|
||||
|
||||
worker_config->graceful_shutdown = true;
|
||||
// worker_config->graceful_shutdown = true;
|
||||
|
||||
if (worker_stat_->num_connections == 0) {
|
||||
event_base_loopbreak(evbase_);
|
||||
// if (worker_stat_->num_connections == 0) {
|
||||
// event_base_loopbreak(evbase_);
|
||||
|
||||
break;
|
||||
}
|
||||
// break;
|
||||
// }
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||
<< ", addrlen=" << wev.client_addrlen;
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||
// << ", addrlen=" << wev.client_addrlen;
|
||||
// }
|
||||
|
||||
if (worker_stat_->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
// if (worker_stat_->num_connections >=
|
||||
// get_config()->worker_frontend_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "Too many connections >= "
|
||||
<< get_config()->worker_frontend_connections;
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "Too many connections >= "
|
||||
// << get_config()->worker_frontend_connections;
|
||||
// }
|
||||
|
||||
close(wev.client_fd);
|
||||
// close(wev.client_fd);
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
auto evbase = bufferevent_get_base(bev);
|
||||
auto client_handler = ssl::accept_connection(
|
||||
evbase, rate_limit_group_, ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
|
||||
wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
if (client_handler) {
|
||||
client_handler->set_http2_session(http2session_);
|
||||
client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
||||
// auto evbase = bufferevent_get_base(bev);
|
||||
// auto client_handler = ssl::accept_connection(
|
||||
// evbase, rate_limit_group_, ssl_ctx_, wev.client_fd,
|
||||
// &wev.client_addr.sa,
|
||||
// wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
// if (client_handler) {
|
||||
// client_handler->set_http2_session(http2session_);
|
||||
// client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created";
|
||||
}
|
||||
} else {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
}
|
||||
close(wev.client_fd);
|
||||
}
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << "
|
||||
// created";
|
||||
// }
|
||||
// } else {
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
// }
|
||||
// close(wev.client_fd);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_downstream_connection_pool.h"
|
||||
|
||||
|
@ -54,28 +52,26 @@ struct WorkerEvent {
|
|||
struct {
|
||||
sockaddr_union client_addr;
|
||||
size_t client_addrlen;
|
||||
evutil_socket_t client_fd;
|
||||
int client_fd;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class ThreadEventReceiver {
|
||||
public:
|
||||
ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
||||
Http2Session *http2session,
|
||||
ThreadEventReceiver(SSL_CTX *ssl_ctx, Http2Session *http2session,
|
||||
ConnectBlocker *http1_connect_blocker);
|
||||
~ThreadEventReceiver();
|
||||
void on_read(bufferevent *bev);
|
||||
void on_read();
|
||||
|
||||
private:
|
||||
DownstreamConnectionPool dconn_pool_;
|
||||
event_base *evbase_;
|
||||
// event_base *evbase_;
|
||||
SSL_CTX *ssl_ctx_;
|
||||
// Shared HTTP2 session for each thread. NULL if not client
|
||||
// mode. Not deleted by this object.
|
||||
Http2Session *http2session_;
|
||||
ConnectBlocker *http1_connect_blocker_;
|
||||
bufferevent_rate_limit_group *rate_limit_group_;
|
||||
std::unique_ptr<WorkerStat> worker_stat_;
|
||||
};
|
||||
|
||||
|
|
|
@ -26,28 +26,29 @@
|
|||
#define SHRPX_UPSTREAM_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_io_control.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class ClientHandler;
|
||||
class Downstream;
|
||||
class DownstreamConnection;
|
||||
|
||||
class Upstream {
|
||||
public:
|
||||
virtual ~Upstream() {}
|
||||
virtual int on_read() = 0;
|
||||
virtual int on_write() = 0;
|
||||
virtual int on_event() = 0;
|
||||
virtual int on_timeout(Downstream *downstream) { return 0; };
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code) = 0;
|
||||
virtual bufferevent_data_cb get_downstream_readcb() = 0;
|
||||
virtual bufferevent_data_cb get_downstream_writecb() = 0;
|
||||
virtual bufferevent_event_cb get_downstream_eventcb() = 0;
|
||||
virtual int downstream_read(DownstreamConnection *dconn) = 0;
|
||||
virtual int downstream_write(DownstreamConnection *dconn) = 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 int on_downstream_header_complete(Downstream *downstream) = 0;
|
||||
|
@ -64,7 +65,7 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||
size_t consumed) = 0;
|
||||
|
||||
virtual void reset_timeouts() = 0;
|
||||
virtual MemchunkPool4K *get_mcpool() = 0;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -25,96 +25,135 @@
|
|||
#include "shrpx_worker.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_thread_event_receiver.h"
|
||||
#include "shrpx_log.h"
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
#include "shrpx_connect_blocker.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
Worker::Worker(const WorkerInfo *info)
|
||||
: sv_ssl_ctx_(info->sv_ssl_ctx), cl_ssl_ctx_(info->cl_ssl_ctx),
|
||||
fd_(info->sv[1]) {}
|
||||
namespace {
|
||||
void eventcb(struct ev_loop *loop, ev_async *w, int revents) {
|
||||
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() {
|
||||
shutdown(fd_, SHUT_WR);
|
||||
close(fd_);
|
||||
ev_async_stop(loop_, &w_);
|
||||
}
|
||||
|
||||
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)reopen_log_files();
|
||||
|
||||
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);
|
||||
fut_ = std::async(std::launch::async, [this] { this->run_loop(); });
|
||||
}
|
||||
|
||||
void start_threaded_worker(WorkerInfo *info) {
|
||||
Worker worker(info);
|
||||
worker.run();
|
||||
void Worker::run_loop() {
|
||||
(void)reopen_log_files();
|
||||
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
|
||||
|
|
|
@ -27,13 +27,26 @@
|
|||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <thread>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // NOTHREADS
|
||||
|
||||
#include <openssl/ssl.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 {
|
||||
|
||||
class Http2Session;
|
||||
class ConnectBlocker;
|
||||
|
||||
struct WorkerStat {
|
||||
WorkerStat() : num_connections(0), next_downstream(0) {}
|
||||
|
||||
|
@ -44,20 +57,48 @@ struct WorkerStat {
|
|||
size_t next_downstream;
|
||||
};
|
||||
|
||||
class Worker {
|
||||
public:
|
||||
Worker(const WorkerInfo *info);
|
||||
~Worker();
|
||||
void run();
|
||||
|
||||
private:
|
||||
SSL_CTX *sv_ssl_ctx_;
|
||||
SSL_CTX *cl_ssl_ctx_;
|
||||
// Channel to the main thread
|
||||
int fd_;
|
||||
enum WorkerEventType {
|
||||
NEW_CONNECTION = 0x01,
|
||||
REOPEN_LOG = 0x02,
|
||||
GRACEFUL_SHUTDOWN = 0x03,
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
|
|
54
src/util.cc
54
src/util.cc
|
@ -30,6 +30,8 @@
|
|||
#include <netdb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
@ -827,6 +829,58 @@ std::vector<unsigned char> get_default_alpn() {
|
|||
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 nghttp2
|
||||
|
|
|
@ -466,6 +466,14 @@ template <typename Clock, typename Rep> Rep clock_precision() {
|
|||
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 nghttp2
|
||||
|
|
|
@ -131,7 +131,7 @@ There are two types of callbacks:
|
|||
* notification `typedef int (*http_cb) (http_parser*);`
|
||||
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||
* 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;
|
||||
|
||||
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_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_TRAILING = 1 << 3
|
||||
, F_UPGRADE = 1 << 4
|
||||
, F_SKIPBODY = 1 << 5
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -950,6 +950,42 @@ const struct message requests[] =
|
|||
,.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 */
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue