Merge branch 'libev'

This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-06 01:33:55 +09:00
commit 1474f755cf
74 changed files with 6897 additions and 4971 deletions

View File

@ -21,6 +21,7 @@ before_install:
libcunit1-dev
libssl-dev
libxml2-dev
libev-dev
libevent-dev
libjansson-dev
libjemalloc-dev

View File

@ -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

View File

@ -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}

97
genheaderfunc.py Executable file
View File

@ -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

View File

@ -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_;
};

View File

@ -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} \

View File

@ -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,16 +80,73 @@ 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() {
while (next_addr) {
auto addr = next_addr;
next_addr = next_addr->ai_next;
fd = util::create_nonblock_socket(addr->ai_family);
if (fd == -1) {
continue;
}
if (config.scheme == "https") {
ssl = SSL_new(worker->ssl_ctx);
@ -113,27 +156,37 @@ int Client::connect() {
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);
SSL_set_fd(ssl, fd);
SSL_set_connect_state(ssl);
}
int rv = -1;
while (next_addr) {
rv = bufferevent_socket_connect(bev, next_addr->ai_addr,
next_addr->ai_addrlen);
next_addr = next_addr->ai_next;
if (rv == 0) {
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;

View File

@ -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

View File

@ -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,
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);
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;
}
nread += rv;
assert(static_cast<size_t>(rv) == len);
if (evbuffer_drain(input, rv) != 0) {
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);
auto rv = nghttp2_session_send(session_);
if (rv != 0) {
return -1;
}
if (datalen < 0) {
return -1;
}
if (datalen == 0) {
break;
}
rv = evbbuf.add(data, datalen);
if (rv != 0) {
return -1;
}
}
rv = evbbuf.flush();
if (rv != 0) {
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;
}

View File

@ -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();

View File

@ -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;

View File

@ -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);
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;
}
nread += rv;
assert(static_cast<size_t>(rv) == len);
if (evbuffer_drain(input, rv) != 0) {
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() {
int rv;
uint8_t buf[16384];
sendbuf.reset(bufferevent_get_output(client_->bev), buf, sizeof(buf));
rv = spdylay_session_send(session_);
if (rv != 0) {
return -1;
}
rv = sendbuf.flush();
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;

View File

@ -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_;

View File

@ -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);
}
}
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);
const Headers::value_type *res = nullptr;
for (auto &nv : nva) {
if (nv.name == name) {
res = &nv;
}
return nullptr;
}
return res;
}
std::string value_to_str(const Headers::value_type *nv) {
@ -371,40 +227,48 @@ 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] != ':') {
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;
}
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));
}
++i;
} else if (rv > 0) {
++j;
} else {
++i;
}
}
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));
}
}
}
void build_http1_headers_from_norm_headers(std::string &hdrs,
void build_http1_headers_from_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] != ':') {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
}
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 += ": ";
@ -412,25 +276,6 @@ void build_http1_headers_from_norm_headers(std::string &hdrs,
sanitize_header_value(hdrs, hdrs.size() - kv.value.size());
hdrs += "\r\n";
}
++i;
} else if (rv > 0) {
++j;
} else {
++i;
}
}
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";
}
}
}
int32_t determine_window_update_transmission(nghttp2_session *session,
@ -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

View File

@ -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,13 +129,12 @@ 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,
void build_http1_headers_from_headers(std::string &hdrs,
const Headers &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be
@ -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

View File

@ -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

View File

@ -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

235
src/memchunk.h Normal file
View File

@ -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

File diff suppressed because it is too large Load Diff

143
src/ringbuf.h Normal file
View File

@ -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

183
src/ringbuf_test.cc Normal file
View File

@ -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

35
src/ringbuf_test.h Normal file
View File

@ -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

View File

@ -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();
}

View File

@ -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

103
src/shrpx_accept_handler.cc Normal file
View File

@ -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

View File

@ -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

View File

@ -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();
}
int rv = handler->on_read();
if (rv != 0) {
delete handler;
}
}
} // namespace
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
namespace {
void upstream_writecb(bufferevent *bev, void *arg) {
auto handler = static_cast<ClientHandler *>(arg);
auto upstream = handler->get_upstream();
if (upstream) {
upstream->reset_timeouts();
}
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()) {
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";
}
finish = true;
}
if (events & BEV_EVENT_ERROR) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "Network error: " << evutil_socket_error_to_string(
EVUTIL_SOCKET_ERROR());
}
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);
}
} // 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) << "SSL/TLS handshake completed";
CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
}
if (handler->validate_next_proto() != 0) {
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 writecb(struct ev_loop *loop, ev_io *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
if (handler->do_write() != 0) {
delete handler;
return;
}
}
} // namespace
int ClientHandler::read_clear() {
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) {
break;
}
ssize_t nread;
while ((nread = readv(fd_, iov, iovcnt)) == -1 && errno == EINTR)
;
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
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;
}
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)) {
if (SSL_session_reused(handler->get_ssl())) {
CLOG(INFO, handler) << "SSL/TLS session reused";
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";
}
}
}
}
}
} // 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;
}
}
} // namespace
read_ = &ClientHandler::read_tls;
write_ = &ClientHandler::write_tls;
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;
}
return 0;
}
} // 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,
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;
}
}
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;
}
int ClientHandler::upstream_write() {
assert(upstream_);
if (upstream_->on_write() != 0) {
return -1;
}
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,15 +814,13 @@ 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) {
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
// checking. Don't know how to treat this.
@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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 Headers::value_type *get_header_linear(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 *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,7 +235,11 @@ 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())) {
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;
@ -186,19 +248,19 @@ void Downstream::assemble_request_cookie() {
}
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())) {
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size();
size_t num = 0;
std::string rep_cookie;
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
@ -212,55 +274,31 @@ void Downstream::crumble_request_cookie() {
j = last;
}
if (num == 0) {
if (first == 0 && j == last) {
break;
}
rep_cookie = kv.value.substr(first, j - first);
} else {
cookie_hdrs.push_back(
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
}
++num;
}
if (num > 0) {
kv.value = std::move(rep_cookie);
}
}
}
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,
void Downstream::add_request_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index) {
bool no_index, int token) {
http2::index_header(request_hdidx_, token, request_headers_.size());
request_headers_sum_ += namelen + valuelen;
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,38 +692,32 @@ 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")) {
}
}
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")) {
auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
if (idx != -1 &&
util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
chunked_response_ = true;
}
}
}
}
void Downstream::reset_response() {
@ -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; }

View File

@ -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,
// 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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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,6 +66,7 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
error_code = NGHTTP2_INTERNAL_ERROR;
}
if (downstream_->get_downstream_stream_id() != -1) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
<< ", stream_id="
@ -80,17 +74,14 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
<< ", error_code=" << error_code;
}
if (submit_rst_stream(downstream_, error_code) == 0) {
http2session_->notify();
}
submit_rst_stream(downstream_, error_code);
if (downstream_->get_downstream_stream_id() != -1) {
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;
}
}
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;
}

View File

@ -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

View File

@ -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

View File

@ -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
: 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();
: 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";
rb->drain(nread);
}
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;
}
}
}
int Http2Upstream::on_write() { return send(); }
int Http2Upstream::send() {
if (write_notifyev_ == nullptr) {
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,7 +782,7 @@ 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,
rst_stream(downstream,
infer_upstream_rst_stream_error_code(
downstream->get_response_rst_stream_error_code()));
downstream->pop_downstream_connection();
@ -842,81 +790,55 @@ void downstream_readcb(bufferevent *bev, void *ptr) {
dconn = nullptr;
} else {
auto rv = downstream->on_read();
if (rv == DownstreamConnection::ERR_EOF) {
return downstream_eof(dconn);
}
if (rv != 0) {
if (rv != DownstreamConnection::ERR_NET) {
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;
}
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
}
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;
}
}
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 (events & BEV_EVENT_EOF) {
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.
upstream->remove_downstream(downstream);
remove_downstream(downstream);
// downstream was deleted
return;
return 0;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
// 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;
@ -924,38 +846,34 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
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";
ULOG(INFO, this) << "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);
// 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 (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
handler_->signal_write();
// At this point, downstream may be deleted.
return;
}
return 0;
}
int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
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 (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
@ -965,14 +883,14 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
remove_downstream(downstream);
// downstream was deleted
return;
return 0;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
// 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;
@ -982,38 +900,32 @@ void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
// 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_NO_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
if (downstream->get_upgraded()) {
upstream->on_downstream_body_complete(downstream);
on_downstream_body_complete(downstream);
} else {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
if (events & Downstream::EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
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;
}
handler_->signal_write();
// At this point, downstream may be deleted.
return;
}
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

View File

@ -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_;
};

View File

@ -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_) {
auto error = errno;
DCLOG(WARN, this) << "bufferevent_socket_new() failed; errno=" << error;
connect_blocker->on_failure();
close(fd);
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),
int rv;
rv = connect(fd_, const_cast<sockaddr *>(
&get_config()->downstream_addrs[i].addr.sa),
get_config()->downstream_addrs[i].addrlen);
if (rv != 0) {
if (rv != 0 && errno != EINPROGRESS) {
auto error = errno;
DCLOG(WARN, this) << "bufferevent_socket_connect() failed; errno="
<< error;
DCLOG(WARN, this) << "connect() failed; errno=" << error;
connect_blocker->on_failure();
bufferevent_free(bev_);
bev_ = nullptr;
close(fd_);
fd_ = -1;
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,27 +406,11 @@ 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) {
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";
}
} else if (events & BEV_EVENT_ERROR) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection network error";
}
}
auto dconn_pool = dconn->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
// dconn was deleted
@ -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) {
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);
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);
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();
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

View File

@ -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

View File

@ -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,9 +377,25 @@ int HttpsUpstream::on_read() {
}
int HttpsUpstream::on_write() {
int rv = 0;
auto downstream = get_downstream();
if (downstream) {
if (!downstream) {
return 0;
}
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) {
@ -408,10 +414,8 @@ int HttpsUpstream::on_write() {
}
}
rv = downstream->resume_read(SHRPX_NO_BUFFER,
return downstream->resume_read(SHRPX_NO_BUFFER,
downstream->get_response_datalen());
}
return rv;
}
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,153 +442,50 @@ 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 -1;
}
return;
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) {
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;
return -1;
}
// 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;
}
return 0;
}
} // 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);
int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
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";
}
@ -590,118 +495,96 @@ void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
DCLOG(INFO, dconn) << "The end of the response body was indicated by "
<< "EOF";
}
upstream->on_downstream_body_complete(downstream);
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;
downstream->pop_downstream_connection();
goto end;
}
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
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;
if (error_reply(502) != 0) {
return -1;
}
downstream->pop_downstream_connection();
goto end;
}
return;
}
// Otherwise, we don't know how to recover from this situation. Just
// drop connection.
return -1;
end:
handler_->signal_write();
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
return 0;
}
int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
if (LOG_ENABLED(INFO)) {
if (events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Network error";
if (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Network error/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
}
if (downstream->get_response_state() == Downstream::INITIAL) {
if (downstream->get_response_state() != Downstream::INITIAL) {
return -1;
}
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
if (events & Downstream::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;
}
}
if (error_reply(status) != 0) {
return -1;
}
downstream->pop_downstream_connection();
handler_->signal_write();
return 0;
}
} // namespace
int HttpsUpstream::error_reply(unsigned int status_code) {
auto html = http::create_error_html(status_code);
auto downstream = get_downstream();
if (downstream) {
if (!downstream) {
attach_downstream(util::make_unique<Downstream>(this, 1, 1));
downstream = get_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";
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()) {
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;
output->append_cstr("Connection: close\r\n");
}
output->append_cstr("\r\n");
output->append(html.c_str(), html.size());
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());
}
return 0;
}
bufferevent_data_cb HttpsUpstream::get_downstream_readcb() {
return https_downstream_readcb;
}
bufferevent_data_cb HttpsUpstream::get_downstream_writecb() {
return https_downstream_writecb;
}
bufferevent_event_cb HttpsUpstream::get_downstream_eventcb() {
return https_downstream_eventcb;
}
void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
assert(!downstream_);
downstream_ = std::move(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

View File

@ -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_;
};

View File

@ -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();
}
}

View File

@ -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_;
};

View File

@ -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();
}
}

View File

@ -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_;
};

View File

@ -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;
}

View File

@ -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) \

98
src/shrpx_rate_limit.cc Normal file
View File

@ -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

55
src/shrpx_rate_limit.h Normal file
View File

@ -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

View File

@ -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->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
: downstream_queue_(
get_config()->http2_proxy
? get_config()->downstream_connections_per_host
: 0),
: 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,126 +515,96 @@ 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,
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 (rv != DownstreamConnection::ERR_NET) {
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;
}
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser
// failure.
downstream->pop_downstream_connection();
dconn = nullptr;
}
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
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 (events & BEV_EVENT_EOF) {
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.
upstream->remove_downstream(downstream);
// downstrea was deleted
remove_downstream(downstream);
// downstream was deleted
return;
return 0;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
// 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)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
ULOG(INFO, this) << "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);
// 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 (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
handler_->signal_write();
// At this point, downstream may be deleted.
return 0;
}
return;
}
int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
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 (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
@ -668,16 +612,18 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
// downstrea was deleted
return;
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.
// 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) {
@ -685,34 +631,32 @@ void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
// 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_NO_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
if (downstream->get_upgraded()) {
on_downstream_body_complete(downstream);
} else {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
if (events & Downstream::EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
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;
}
handler_->signal_write();
// At this point, downstream may be deleted.
return;
}
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 {
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

View File

@ -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_;

View File

@ -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 {

View File

@ -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);

View File

@ -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

View File

@ -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_;
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

111
third-party/http-parser/bench.c vendored Normal file
View File

@ -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

View File

@ -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
};

View File

@ -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 */
};