src: Use libev for rest of the applications

This commit is contained in:
Tatsuhiro Tsujikawa 2014-12-28 02:59:06 +09:00
parent cd7258a7cd
commit bfac015d61
52 changed files with 4402 additions and 3577 deletions

View File

@ -74,7 +74,6 @@ int make_socket_nonblocking(int fd) {
;
return rv;
}
} // namespace
namespace {
@ -116,6 +115,9 @@ void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
auto hd = stream->handler;
auto config = hd->get_config();
ev_timer_stop(hd->get_loop(), &stream->rtimer);
ev_timer_stop(hd->get_loop(), &stream->wtimer);
if (config->verbose) {
print_session_id(hd->session_id());
print_timer();
@ -546,8 +548,6 @@ int Http2Handler::read_tls() {
}
}
ev_io_stop(sessions_->get_loop(), &wev_);
fin:
return write_(*this);
}
@ -1307,13 +1307,15 @@ void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) {
auto worker = static_cast<Worker *>(w->data);
auto &sessions = worker->sessions;
std::lock_guard<std::mutex> lock(worker->m);
for (auto c : worker->q) {
sessions->accept_connection(c.fd);
std::deque<ClientInfo> q;
{
std::lock_guard<std::mutex> lock(worker->m);
q.swap(worker->q);
}
worker->q.clear();
for (auto c : q) {
sessions->accept_connection(c.fd);
}
}
} // namespace

View File

@ -36,21 +36,12 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/third-party \
@LIBSPDYLAY_CFLAGS@ \
@XML_CPPFLAGS@ \
@LIBEVENT_OPENSSL_CFLAGS@ \
@LIBEV_CFLAGS@ \
@OPENSSL_CFLAGS@ \
@JANSSON_CFLAGS@ \
@ZLIB_CFLAGS@ \
@DEFS@
AM_LDFLAGS = \
@JEMALLOC_LIBS@ \
@LIBSPDYLAY_LIBS@ \
@XML_LIBS@ \
@LIBEVENT_OPENSSL_LIBS@ \
@OPENSSL_LIBS@ \
@JANSSON_LIBS@ \
@ZLIB_LIBS@ \
@APPLDFLAGS@
EVLDFLAGS = \
@JEMALLOC_LIBS@ \
@LIBSPDYLAY_LIBS@ \
@XML_LIBS@ \
@ -83,11 +74,8 @@ endif # HAVE_LIBXML2
nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \
${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \
libevent_util.cc libevent_util.h \
ssl.cc ssl.h
nghttpd_LDFLAGS = ${EVLDFLAGS}
nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
ssl.cc ssl.h \
HttpServer.cc HttpServer.h \
@ -95,7 +83,7 @@ nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
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 \
@ -108,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
@ -142,8 +131,7 @@ noinst_LIBRARIES = libnghttpx.a
libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
nghttpx_SOURCES = shrpx.cc shrpx.h
nghttpx_LDFLAGS =
nghttpx_LDADD = libnghttpx.a ${LDADD} ${AM_LDFLAGS}
nghttpx_LDADD = libnghttpx.a ${LDADD}
if HAVE_CUNIT
check_PROGRAMS += nghttpx-unittest

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,46 +80,103 @@ 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);
if (client->do_write() != 0) {
client->fail();
}
}
} // namespace
Client::Client(Worker *worker, size_t req_todo)
: worker(worker), ssl(nullptr), bev(nullptr), next_addr(config.addrs),
reqidx(0), state(CLIENT_IDLE), req_todo(req_todo), req_started(0),
req_done(0) {}
: worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0),
fd(-1) {
ev_io_init(&wev, writecb, 0, EV_WRITE);
ev_io_init(&rev, readcb, 0, EV_READ);
wev.data = this;
rev.data = this;
}
Client::~Client() { disconnect(); }
int Client::do_read() { return readfn(*this); }
int Client::do_write() { return writefn(*this); }
int Client::connect() {
if (config.scheme == "https") {
ssl = SSL_new(worker->ssl_ctx);
auto config = worker->config;
if (!util::numeric_host(config->host.c_str())) {
SSL_set_tlsext_host_name(ssl, config->host.c_str());
}
bev = bufferevent_openssl_socket_new(worker->evbase, -1, ssl,
BUFFEREVENT_SSL_CONNECTING,
BEV_OPT_DEFER_CALLBACKS);
} else {
bev = bufferevent_socket_new(worker->evbase, -1, BEV_OPT_DEFER_CALLBACKS);
}
int rv = -1;
while (next_addr) {
rv = bufferevent_socket_connect(bev, next_addr->ai_addr,
next_addr->ai_addrlen);
auto addr = next_addr;
next_addr = next_addr->ai_next;
if (rv == 0) {
break;
fd = util::create_nonblock_socket(addr->ai_family);
if (fd == -1) {
continue;
}
if (config.scheme == "https") {
ssl = SSL_new(worker->ssl_ctx);
auto config = worker->config;
if (!util::numeric_host(config->host.c_str())) {
SSL_set_tlsext_host_name(ssl, config->host.c_str());
}
SSL_set_fd(ssl, fd);
SSL_set_connect_state(ssl);
}
auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
if (rv != 0 && errno != EINPROGRESS) {
if (ssl) {
SSL_free(ssl);
ssl = nullptr;
}
close(fd);
fd = -1;
continue;
}
break;
}
if (rv != 0) {
if (fd == -1) {
return -1;
}
bufferevent_enable(bev, EV_READ);
bufferevent_setcb(bev, readcb, writecb, eventcb, this);
writefn = &Client::connected;
on_readfn = &Client::on_read;
on_writefn = &Client::on_write;
ev_io_set(&rev, fd, EV_READ);
ev_io_set(&wev, fd, EV_WRITE);
ev_io_start(worker->loop, &wev);
return 0;
}
@ -144,27 +187,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 +362,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 +438,232 @@ 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();
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() {
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 +680,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 +694,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,8 @@ 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;
Client(Worker *worker, size_t req_todo);
~Client();
@ -151,13 +159,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,
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
auto &wb = client_->wb;
assert(wb.wleft() >= NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
client_->signal_write();
}
void Http2Session::submit_request() {
@ -148,66 +168,35 @@ void Http2Session::submit_request() {
client_->on_request(stream_id);
}
ssize_t Http2Session::on_read() {
int rv;
size_t nread = 0;
auto input = bufferevent_get_input(client_->bev);
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return nread;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
if (rv < 0) {
return -1;
}
nread += rv;
if (evbuffer_drain(input, rv) != 0) {
return -1;
}
int Http2Session::on_read(const uint8_t *data, size_t len) {
auto rv = nghttp2_session_mem_recv(session_, data, len);
if (rv < 0) {
return -1;
}
assert(static_cast<size_t>(rv) == len);
if (nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
return -1;
}
client_->signal_write();
return 0;
}
int Http2Session::on_write() {
int rv;
uint8_t buf[16384];
auto output = bufferevent_get_output(client_->bev);
util::EvbufferBuffer evbbuf(output, buf, sizeof(buf));
for (;;) {
const uint8_t *data;
auto datalen = nghttp2_session_mem_send(session_, &data);
if (datalen < 0) {
return -1;
}
if (datalen == 0) {
break;
}
rv = evbbuf.add(data, datalen);
if (rv != 0) {
return -1;
}
}
rv = evbbuf.flush();
auto rv = nghttp2_session_send(session_);
if (rv != 0) {
return -1;
}
if (nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 &&
evbuffer_get_length(output) == 0) {
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
return -1;
}
return 0;
}

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);
if (rv < 0) {
return -1;
}
nread += rv;
if (evbuffer_drain(input, rv) != 0) {
return -1;
}
}
}
int SpdySession::on_write() {
int rv;
uint8_t buf[16384];
sendbuf.reset(bufferevent_get_output(client_->bev), buf, sizeof(buf));
rv = spdylay_session_send(session_);
if (rv != 0) {
int SpdySession::on_read(const uint8_t *data, size_t len) {
auto rv = spdylay_session_mem_recv(session_, data, len);
if (rv < 0) {
return -1;
}
rv = sendbuf.flush();
assert(static_cast<size_t>(rv) == len);
if (spdylay_session_want_read(session_) == 0 &&
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
return -1;
}
client_->signal_write();
return 0;
}
int SpdySession::on_write() {
auto rv = spdylay_session_send(session_);
if (rv != 0) {
return -1;
}
if (spdylay_session_want_read(session_) == 0 &&
spdylay_session_want_write(session_) == 0 &&
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
return -1;
}
return 0;

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

232
src/memchunk.h Normal file
View File

@ -0,0 +1,232 @@
/*
* 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() {
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

View File

@ -25,6 +25,8 @@
#ifndef RINGBUF_H
#define RINGBUF_H
#include "nghttp2_config.h"
#include <sys/uio.h>
#include <cstring>
@ -53,6 +55,11 @@ template <size_t N> struct RingBuf {
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());
@ -116,6 +123,21 @@ template <size_t N> struct RingBuf {
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

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,19 @@ 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()->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 +799,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
@ -914,47 +847,42 @@ 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
@ -1829,17 +1757,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

View File

@ -0,0 +1,94 @@
/*
* 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);
auto cfd =
accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
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;
}
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,445 @@
#include "shrpx_spdy_upstream.h"
#endif // HAVE_SPDYLAY
#include "util.h"
#include "libevent_util.h"
using namespace nghttp2;
namespace shrpx {
namespace {
void upstream_readcb(bufferevent *bev, void *arg) {
auto handler = static_cast<ClientHandler *>(arg);
auto upstream = handler->get_upstream();
if (upstream) {
upstream->reset_timeouts();
void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "Time out";
}
int rv = handler->on_read();
if (rv != 0) {
delete handler;
}
} // namespace
namespace {
void shutdowncb(struct ev_loop *loop, ev_timer *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
}
delete handler;
}
} // namespace
namespace {
void readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
if (handler->do_read() != 0) {
delete handler;
return;
}
}
} // namespace
namespace {
void upstream_writecb(bufferevent *bev, void *arg) {
auto handler = static_cast<ClientHandler *>(arg);
auto upstream = handler->get_upstream();
if (upstream) {
upstream->reset_timeouts();
}
void writecb(struct ev_loop *loop, ev_io *w, int revents) {
auto handler = static_cast<ClientHandler *>(w->data);
handler->update_last_write_time();
// We actually depend on write low-water mark == 0.
if (handler->get_outbuf_length() > 0) {
// Possibly because of deferred callback, we may get this callback
// when the output buffer is not empty.
return;
}
if (handler->get_should_close_after_write()) {
if (handler->do_write() != 0) {
delete handler;
return;
}
if (!upstream) {
return;
}
int rv = upstream->on_write();
if (rv != 0) {
delete handler;
}
}
} // namespace
namespace {
void upstream_eventcb(bufferevent *bev, short events, void *arg) {
auto handler = static_cast<ClientHandler *>(arg);
bool finish = false;
if (events & BEV_EVENT_EOF) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "EOF";
int ClientHandler::read_clear() {
ev_timer_again(loop_, &rt_);
for (;;) {
if (rb_.rleft() && on_read() != 0) {
return -1;
}
finish = true;
}
if (events & BEV_EVENT_ERROR) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "Network error: " << evutil_socket_error_to_string(
EVUTIL_SOCKET_ERROR());
rb_.reset();
struct iovec iov[2];
auto iovcnt = rb_.wiovec(iov);
iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail());
if (iovcnt == 0) {
break;
}
finish = true;
}
if (events & BEV_EVENT_TIMEOUT) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "Time out";
}
finish = true;
}
if (finish) {
delete handler;
} else {
if (events & BEV_EVENT_CONNECTED) {
handler->set_tls_handshake(true);
if (LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "SSL/TLS handshake completed";
ssize_t nread;
while ((nread = readv(fd_, iov, iovcnt)) == -1 && errno == EINTR)
;
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
if (handler->validate_next_proto() != 0) {
delete handler;
return;
return -1;
}
if (nread == 0) {
return -1;
}
rb_.write(nread);
rlimit_.drain(nread);
}
return 0;
}
int ClientHandler::write_clear() {
ev_timer_again(loop_, &rt_);
for (;;) {
if (wb_.rleft() > 0) {
struct iovec iov[2];
auto iovcnt = wb_.riovec(iov);
iovcnt = limit_iovec(iov, iovcnt, wlimit_.avail());
if (iovcnt == 0) {
return 0;
}
if (LOG_ENABLED(INFO)) {
if (SSL_session_reused(handler->get_ssl())) {
CLOG(INFO, handler) << "SSL/TLS session reused";
ssize_t nwrite;
while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR)
;
if (nwrite == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
wlimit_.startw();
ev_timer_again(loop_, &wt_);
return 0;
}
return -1;
}
wb_.drain(nwrite);
wlimit_.drain(nwrite);
continue;
}
if (on_write() != 0) {
return -1;
}
if (wb_.rleft() == 0) {
wb_.reset();
break;
}
}
wlimit_.stopw();
ev_timer_stop(loop_, &wt_);
return 0;
}
int ClientHandler::tls_handshake() {
ev_timer_again(loop_, &rt_);
auto rv = SSL_do_handshake(ssl_);
if (rv == 0) {
return -1;
}
if (rv < 0) {
auto err = SSL_get_error(ssl_, rv);
switch (err) {
case SSL_ERROR_WANT_READ:
wlimit_.stopw();
ev_timer_stop(loop_, &wt_);
return 0;
case SSL_ERROR_WANT_WRITE:
wlimit_.startw();
ev_timer_again(loop_, &wt_);
return 0;
default:
return -1;
}
}
wlimit_.stopw();
ev_timer_stop(loop_, &wt_);
set_tls_handshake(true);
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "SSL/TLS handshake completed";
}
if (validate_next_proto() != 0) {
return -1;
}
if (LOG_ENABLED(INFO)) {
if (SSL_session_reused(ssl_)) {
CLOG(INFO, this) << "SSL/TLS session reused";
}
}
read_ = &ClientHandler::read_tls;
write_ = &ClientHandler::write_tls;
return 0;
}
int ClientHandler::read_tls() {
ev_timer_again(loop_, &rt_);
for (;;) {
if (rb_.rleft() && on_read() != 0) {
return -1;
}
rb_.reset();
struct iovec iov[2];
auto iovcnt = rb_.wiovec(iov);
iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail());
if (iovcnt == 0) {
return 0;
}
auto rv = SSL_read(ssl_, iov[0].iov_base, iov[0].iov_len);
if (rv == 0) {
return -1;
}
if (rv < 0) {
auto err = SSL_get_error(ssl_, rv);
switch (err) {
case SSL_ERROR_WANT_READ:
goto fin;
case SSL_ERROR_WANT_WRITE:
wlimit_.startw();
ev_timer_again(loop_, &wt_);
goto fin;
default:
return -1;
}
}
rb_.write(rv);
rlimit_.drain(rv);
}
fin:
return 0;
}
int ClientHandler::write_tls() {
ev_timer_again(loop_, &rt_);
for (;;) {
if (wb_.rleft() > 0) {
const void *p;
size_t len;
std::tie(p, len) = wb_.get();
len = std::min(len, wlimit_.avail());
if (len == 0) {
return 0;
}
auto limit = get_write_limit();
if (limit != -1) {
len = std::min(len, static_cast<size_t>(limit));
}
auto rv = SSL_write(ssl_, p, len);
if (rv == 0) {
return -1;
}
if (rv < 0) {
auto err = SSL_get_error(ssl_, rv);
switch (err) {
case SSL_ERROR_WANT_READ:
wlimit_.stopw();
ev_timer_stop(loop_, &wt_);
return 0;
case SSL_ERROR_WANT_WRITE:
wlimit_.startw();
ev_timer_again(loop_, &wt_);
return 0;
default:
return -1;
}
}
wb_.drain(rv);
wlimit_.drain(rv);
update_warmup_writelen(rv);
update_last_write_time();
continue;
}
if (on_write() != 0) {
return -1;
}
if (wb_.rleft() == 0) {
break;
}
}
}
} // namespace
namespace {
void upstream_http2_connhd_readcb(bufferevent *bev, void *arg) {
// This callback assumes upstream is Http2Upstream.
auto handler = static_cast<ClientHandler *>(arg);
if (handler->on_http2_connhd_read() != 0) {
delete handler;
wlimit_.stopw();
ev_timer_stop(loop_, &wt_);
return 0;
}
int ClientHandler::upstream_noop() { return 0; }
int ClientHandler::upstream_read() {
assert(upstream_);
if (upstream_->on_read() != 0) {
return -1;
}
return 0;
}
} // namespace
namespace {
void upstream_http1_connhd_readcb(bufferevent *bev, void *arg) {
// This callback assumes upstream is HttpsUpstream.
auto handler = static_cast<ClientHandler *>(arg);
if (handler->on_http1_connhd_read() != 0) {
delete handler;
int ClientHandler::upstream_write() {
assert(upstream_);
if (upstream_->on_write() != 0) {
return -1;
}
}
} // namespace
ClientHandler::ClientHandler(bufferevent *bev,
bufferevent_rate_limit_group *rate_limit_group,
int fd, SSL *ssl, const char *ipaddr,
const char *port, WorkerStat *worker_stat,
if (get_should_close_after_write() && wb_.rleft() == 0) {
return -1;
}
return 0;
}
int ClientHandler::upstream_http2_connhd_read() {
struct iovec iov[2];
auto iovcnt = rb_.riovec(iov);
for (int i = 0; i < iovcnt; ++i) {
auto nread =
std::min(left_connhd_len_, static_cast<size_t>(iov[i].iov_len));
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
iov[i].iov_base, nread) != 0) {
// There is no downgrade path here. Just drop the connection.
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "invalid client connection header";
}
return -1;
}
left_connhd_len_ -= nread;
rb_.drain(nread);
if (left_connhd_len_ == 0) {
on_read_ = &ClientHandler::upstream_read;
// Run on_read to process data left in buffer since they are not
// notified further
if (on_read() != 0) {
return -1;
}
return 0;
}
}
return 0;
}
int ClientHandler::upstream_http1_connhd_read() {
struct iovec iov[2];
auto iovcnt = rb_.riovec(iov);
for (int i = 0; i < iovcnt; ++i) {
auto nread =
std::min(left_connhd_len_, static_cast<size_t>(iov[i].iov_len));
if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
iov[i].iov_base, nread) != 0) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "This is HTTP/1.1 connection, "
<< "but may be upgraded to HTTP/2 later.";
}
// Reset header length for later HTTP/2 upgrade
left_connhd_len_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
on_read_ = &ClientHandler::upstream_read;
on_write_ = &ClientHandler::upstream_write;
if (on_read() != 0) {
return -1;
}
return 0;
}
left_connhd_len_ -= nread;
rb_.drain(nread);
if (left_connhd_len_ == 0) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "direct HTTP/2 connection";
}
direct_http2_upgrade();
on_read_ = &ClientHandler::upstream_read;
on_write_ = &ClientHandler::upstream_write;
// Run on_read to process data left in buffer since they are not
// notified further
if (on_read() != 0) {
return -1;
}
return 0;
}
}
return 0;
}
ClientHandler::ClientHandler(struct ev_loop *loop, int fd, SSL *ssl,
const char *ipaddr, const char *port,
WorkerStat *worker_stat,
DownstreamConnectionPool *dconn_pool)
: ipaddr_(ipaddr), port_(port), dconn_pool_(dconn_pool), bev_(bev),
http2session_(nullptr), ssl_(ssl), reneg_shutdown_timerev_(nullptr),
: ipaddr_(ipaddr), port_(port),
wlimit_(loop, &wev_, get_config()->write_rate, get_config()->write_burst),
rlimit_(loop, &rev_, get_config()->read_rate, get_config()->read_burst),
loop_(loop), dconn_pool_(dconn_pool), http2session_(nullptr), ssl_(ssl),
worker_stat_(worker_stat), last_write_time_(0), warmup_writelen_(0),
left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN), fd_(fd),
should_close_after_write_(false), tls_handshake_(false),
tls_renegotiation_(false) {
int rv;
++worker_stat->num_connections;
rv = bufferevent_set_rate_limit(bev_, get_config()->rate_limit_cfg);
if (rv == -1) {
CLOG(FATAL, this) << "bufferevent_set_rate_limit() failed";
}
ev_io_init(&wev_, writecb, fd_, EV_WRITE);
ev_io_init(&rev_, readcb, fd_, EV_READ);
rv = bufferevent_add_to_rate_limit_group(bev_, rate_limit_group);
if (rv == -1) {
CLOG(FATAL, this) << "bufferevent_add_to_rate_limit_group() failed";
}
wev_.data = this;
rev_.data = this;
ev_timer_init(&wt_, timeoutcb, 0., get_config()->upstream_write_timeout);
ev_timer_init(&rt_, timeoutcb, 0., get_config()->upstream_read_timeout);
wt_.data = this;
rt_.data = this;
ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.);
reneg_shutdown_timer_.data = this;
rlimit_.startw();
ev_timer_again(loop_, &rt_);
util::bev_enable_unless(bev_, EV_READ | EV_WRITE);
bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK);
set_upstream_timeouts(&get_config()->upstream_read_timeout,
&get_config()->upstream_write_timeout);
if (ssl_) {
SSL_set_app_data(ssl_, reinterpret_cast<char *>(this));
set_bev_cb(nullptr, upstream_writecb, upstream_eventcb);
read_ = write_ = &ClientHandler::tls_handshake;
on_read_ = &ClientHandler::upstream_noop;
on_write_ = &ClientHandler::upstream_write;
} else {
// For non-TLS version, first create HttpsUpstream. It may be
// upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
// connection.
upstream_ = util::make_unique<HttpsUpstream>(this);
alpn_ = "http/1.1";
set_bev_cb(upstream_http1_connhd_readcb, nullptr, upstream_eventcb);
read_ = &ClientHandler::read_clear;
write_ = &ClientHandler::write_clear;
on_read_ = &ClientHandler::upstream_http1_connhd_read;
on_write_ = &ClientHandler::upstream_noop;
}
}
@ -211,14 +495,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 +515,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 +528,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 +552,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 +569,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 +587,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 +615,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 +629,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 +654,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 +666,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 +713,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 +729,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 +751,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 +765,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 +789,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 +796,7 @@ void ClientHandler::set_tls_renegotiation(bool f) {
<< "Start shutdown timer now.";
}
reneg_shutdown_timerev_ = evtimer_new(get_evbase(), shutdown_cb, this);
event_priority_set(reneg_shutdown_timerev_, 0);
timeval timeout = {0, 0};
// TODO What to do if this failed?
evtimer_add(reneg_shutdown_timerev_, &timeout);
ev_timer_start(loop_, &reneg_shutdown_timer_);
}
tls_renegotiation_ = f;
}
@ -645,14 +813,12 @@ ssize_t ClientHandler::get_write_limit() {
return -1;
}
timeval tv;
if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) {
auto now = util::to_time64(tv);
if (now - last_write_time_ > 1000000) {
// Time out, use small record size
warmup_writelen_ = 0;
return SHRPX_SMALL_WRITE_LIMIT;
}
auto t = ev_now(loop_);
if (t - last_write_time_ > 1.0) {
// Time out, use small record size
warmup_writelen_ = 0;
return SHRPX_SMALL_WRITE_LIMIT;
}
// If event_base_gettimeofday_cached() failed, we just skip timer
@ -672,10 +838,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 +884,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

@ -198,7 +198,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 +421,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;
}

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 {
@ -171,15 +172,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 +200,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.

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,11 +38,76 @@
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
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_buf_(upstream->get_mcpool()),
response_buf_(upstream->get_mcpool()), 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),
@ -55,28 +120,34 @@ Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
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) {}
request_headers_normalized_(false) {
ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
get_config()->stream_read_timeout);
ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0.,
get_config()->stream_write_timeout);
ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0.,
get_config()->stream_read_timeout);
ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
get_config()->stream_write_timeout);
upstream_rtimer_.data = this;
upstream_wtimer_.data = this;
downstream_rtimer_.data = this;
downstream_wtimer_.data = this;
}
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_);
}
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";
}
@ -399,14 +470,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() {
@ -585,17 +658,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;
@ -764,199 +835,120 @@ bool Downstream::response_pseudo_header_allowed() const {
return pseudo_header_allowed(response_headers_);
}
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);
}
} // 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();
// 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_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);
}
// if (get_config()->stream_write_timeout.tv_sec > 0) {
// upstream_wtimerev_ = init_timer(evbase, upstream_wtimeoutcb, this);
// }
}
namespace {
void reset_timer(event *timer, const timeval *timeout) {
if (!timer) {
return;
}
// namespace {
// void reset_timer(event *timer, const timeval *timeout) {
// if (!timer) {
// return;
// }
event_add(timer, timeout);
}
} // namespace
// event_add(timer, timeout);
// }
// } // namespace
namespace {
void try_reset_timer(event *timer, const timeval *timeout) {
if (!timer) {
return;
}
// namespace {
// void try_reset_timer(event *timer, const timeval *timeout) {
// if (!timer) {
// return;
// }
if (!evtimer_pending(timer, nullptr)) {
return;
}
// if (!evtimer_pending(timer, nullptr)) {
// return;
// }
event_add(timer, timeout);
}
} // namespace
// event_add(timer, timeout);
// }
// } // namespace
namespace {
void ensure_timer(event *timer, const timeval *timeout) {
if (!timer) {
return;
}
// namespace {
// void ensure_timer(event *timer, const timeval *timeout) {
// if (!timer) {
// return;
// }
if (evtimer_pending(timer, nullptr)) {
return;
}
// if (evtimer_pending(timer, nullptr)) {
// return;
// }
event_add(timer, timeout);
}
} // namespace
// event_add(timer, timeout);
// }
// } // namespace
namespace {
void disable_timer(event *timer) {
if (!timer) {
return;
}
// namespace {
// void disable_timer(event *timer) {
// if (!timer) {
// return;
// }
event_del(timer);
}
} // namespace
// event_del(timer);
// }
// } // 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);
// reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
// try_reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
}
void Downstream::reset_upstream_wtimer() {
reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
try_reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
// reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
// try_reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout);
}
void Downstream::ensure_upstream_wtimer() {
ensure_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
// ensure_timer(upstream_wtimerev_, &get_config()->stream_write_timeout);
}
void Downstream::disable_upstream_rtimer() {
disable_timer(upstream_rtimerev_);
// disable_timer(upstream_rtimerev_);
}
void Downstream::disable_upstream_wtimer() {
disable_timer(upstream_wtimerev_);
// 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();
// 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_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.tv_sec > 0) {
// downstream_wtimerev_ = init_timer(evbase, downstream_wtimeoutcb, this);
// }
}
void Downstream::reset_downstream_rtimer() {
reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
try_reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
// reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
// try_reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
}
void Downstream::reset_downstream_wtimer() {
reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
try_reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
// reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
// try_reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout);
}
void Downstream::ensure_downstream_wtimer() {
ensure_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
// ensure_timer(downstream_wtimerev_, &get_config()->stream_write_timeout);
}
void Downstream::disable_downstream_rtimer() {
disable_timer(downstream_rtimerev_);
// disable_timer(downstream_rtimerev_);
}
void Downstream::disable_downstream_wtimer() {
disable_timer(downstream_wtimerev_);
// disable_timer(downstream_wtimerev_);
}
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.
@ -174,6 +174,7 @@ 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 <
@ -218,8 +219,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);
@ -282,6 +283,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_;
@ -294,6 +300,15 @@ private:
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_;
// the length of response body
@ -303,15 +318,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_;
@ -358,6 +364,8 @@ private:
// true if request_headers_ is normalized
bool request_headers_normalized_;
bool use_timer_;
};
} // 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

@ -26,10 +26,6 @@
#include <unistd.h>
#include <openssl/err.h>
#include <event2/bufferevent_ssl.h>
#include "http-parser/http_parser.h"
#include "shrpx_client_handler.h"
@ -50,15 +46,12 @@ namespace shrpx {
Http2DownstreamConnection::Http2DownstreamConnection(
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
: DownstreamConnection(dconn_pool), http2session_(http2session),
request_body_buf_(nullptr), sd_(nullptr) {}
sd_(nullptr) {}
Http2DownstreamConnection::~Http2DownstreamConnection() {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Deleting";
}
if (request_body_buf_) {
evbuffer_free(request_body_buf_);
}
if (downstream_) {
downstream_->disable_downstream_rtimer();
downstream_->disable_downstream_wtimer();
@ -73,24 +66,22 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
error_code = NGHTTP2_INTERNAL_ERROR;
}
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
<< ", stream_id="
<< downstream_->get_downstream_stream_id()
<< ", error_code=" << error_code;
}
if (submit_rst_stream(downstream_, error_code) == 0) {
http2session_->notify();
}
if (downstream_->get_downstream_stream_id() != -1) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
<< ", stream_id="
<< downstream_->get_downstream_stream_id()
<< ", error_code=" << error_code;
}
submit_rst_stream(downstream_, error_code);
http2session_->consume(downstream_->get_downstream_stream_id(),
downstream_->get_response_datalen());
downstream_->reset_response_datalen();
http2session_->notify();
http2session_->signal_write();
}
}
http2session_->remove_downstream_connection(this);
@ -104,33 +95,13 @@ 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;
@ -145,7 +116,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 +125,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
downstream_->reset_response_datalen();
http2session_->notify();
http2session_->signal_write();
}
downstream->disable_downstream_rtimer();
@ -201,13 +172,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 +192,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 +204,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();
@ -461,17 +428,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 +445,7 @@ int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
downstream_->ensure_downstream_wtimer();
http2session_->notify();
http2session_->signal_write();
}
return 0;
}
@ -495,7 +460,7 @@ int Http2DownstreamConnection::end_upload_data() {
downstream_->ensure_downstream_wtimer();
http2session_->notify();
http2session_->signal_write();
}
return 0;
}
@ -525,7 +490,7 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
downstream_->dec_response_datalen(consumed);
http2session_->notify();
http2session_->signal_write();
}
return 0;
@ -535,10 +500,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 +517,8 @@ StreamData *Http2DownstreamConnection::detach_stream_data() {
sd_ = nullptr;
sd->dconn = nullptr;
return sd;
} else {
return nullptr;
}
}
bool Http2DownstreamConnection::get_output_buffer_full() {
if (request_body_buf_) {
return evbuffer_get_length(request_body_buf_) >=
Http2Session::OUTBUF_MAX_THRES;
} else {
return false;
}
return nullptr;
}
int Http2DownstreamConnection::on_priority_change(int32_t pri) {
@ -584,7 +535,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) {
@ -129,7 +125,6 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
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 +137,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 {
@ -258,7 +219,6 @@ int on_begin_headers_callback(nghttp2_session *session,
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.
@ -540,10 +500,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,9 +556,15 @@ 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
@ -608,9 +572,8 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
: downstream_queue_(get_config()->http2_proxy
? get_config()->downstream_connections_per_host
: 0),
handler_(handler), session_(nullptr), settings_timerev_(nullptr),
write_notifyev_(nullptr), deferred_(false) {
reset_timeouts();
handler_(handler), session_(nullptr), data_pending_(nullptr),
data_pendinglen_(0), deferred_(false) {
int rv;
@ -699,74 +662,76 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
}
}
write_notifyev_ = evtimer_new(handler_->get_evbase(), write_notify_cb, this);
// We wait for SETTINGS ACK at least 10 seconds.
ev_timer_init(&settings_timer_, settings_timeout_cb, 10., 0.);
settings_timer_.data = this;
handler_->reset_upstream_read_timeout(
get_config()->http2_upstream_read_timeout);
handler_->signal_write();
}
Http2Upstream::~Http2Upstream() {
nghttp2_session_del(session_);
if (settings_timerev_) {
event_free(settings_timerev_);
}
if (write_notifyev_) {
event_free(write_notifyev_);
}
ev_timer_stop(handler_->get_loop(), &settings_timer_);
}
int Http2Upstream::on_read() {
ssize_t rv = 0;
auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev);
auto rb = handler_->get_rb();
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return send();
const void *data;
size_t nread;
std::tie(data, nread) = rb->get();
if (nread == 0) {
break;
}
auto mem = evbuffer_pullup(input, inputlen);
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
rv = nghttp2_session_mem_recv(
session_, reinterpret_cast<const uint8_t *>(data), nread);
if (rv < 0) {
ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
if (evbuffer_drain(input, rv) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
rb->drain(nread);
}
}
int Http2Upstream::on_write() { return send(); }
int Http2Upstream::send() {
if (write_notifyev_ == nullptr) {
auto wb = handler_->get_wb();
if (nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
}
return -1;
}
event_active(write_notifyev_, 0, 0);
handler_->signal_write();
return 0;
}
// After this function call, downstream may be deleted.
int Http2Upstream::perform_send() {
int rv;
uint8_t buf[16384];
auto bev = handler_->get_bev();
auto output = bufferevent_get_output(bev);
int Http2Upstream::on_write() {
auto wb = handler_->get_wb();
sendbuf.reset(output, buf, sizeof(buf), handler_->get_write_limit());
for (;;) {
// Check buffer length and break if it is large enough.
if (handler_->get_outbuf_length() > 0) {
break;
if (data_pending_) {
auto n = std::min(wb->wleft(), data_pendinglen_);
wb->write(data_pending_, n);
if (n < data_pendinglen_) {
data_pendinglen_ += n;
data_pendinglen_ -= n;
return 0;
}
data_pending_ = nullptr;
data_pendinglen_ = 0;
}
for (;;) {
const uint8_t *data;
auto datalen = nghttp2_session_mem_send(session_, &data);
@ -778,55 +743,38 @@ int Http2Upstream::perform_send() {
if (datalen == 0) {
break;
}
rv = sendbuf.add(data, datalen);
if (rv != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
if (deferred_) {
break;
auto n = wb->write(data, datalen);
if (n < static_cast<decltype(n)>(datalen)) {
data_pending_ = data + n;
data_pendinglen_ = datalen - n;
return 0;
}
}
deferred_ = false;
rv = sendbuf.flush();
if (rv != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
handler_->update_warmup_writelen(sendbuf.get_writelen());
if (nghttp2_session_want_read(session_) == 0 &&
nghttp2_session_want_write(session_) == 0 &&
handler_->get_outbuf_length() == 0) {
nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
}
return -1;
}
return 0;
}
int Http2Upstream::on_event() { return 0; }
ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
namespace {
void downstream_readcb(bufferevent *bev, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If upstream HTTP2 stream was closed, we just close downstream,
// because there is no consumer now. Downstream connection is also
// closed in this case.
upstream->remove_downstream(downstream);
remove_downstream(downstream);
// downstream was deleted
return;
return 0;
}
if (downstream->get_response_state() == Downstream::MSG_RESET) {
@ -834,186 +782,150 @@ void downstream_readcb(bufferevent *bev, void *ptr) {
// RST_STREAM to the upstream and delete downstream connection
// here. Deleting downstream will be taken place at
// on_stream_close_callback.
upstream->rst_stream(downstream,
infer_upstream_rst_stream_error_code(
downstream->get_response_rst_stream_error_code()));
rst_stream(downstream,
infer_upstream_rst_stream_error_code(
downstream->get_response_rst_stream_error_code()));
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
} else {
auto rv = downstream->on_read();
if (rv == DownstreamConnection::ERR_EOF) {
return downstream_eof(dconn);
}
if (rv != 0) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure";
}
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If response was completed, then don't issue RST_STREAM
if (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
if (rv != DownstreamConnection::ERR_NET) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure";
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser
// failure.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
handler_->signal_write();
// At this point, downstream may be deleted.
}
} // namespace
namespace {
void downstream_writecb(bufferevent *bev, void *ptr) {
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
return;
return 0;
}
int Http2Upstream::downstream_write(DownstreamConnection *dconn) {
int rv;
rv = dconn->on_write();
if (rv == DownstreamConnection::ERR_NET) {
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
auto dconn = static_cast<DownstreamConnection *>(ptr);
dconn->on_write();
if (rv != 0) {
return -1;
}
return 0;
}
} // namespace
namespace {
void downstream_eventcb(bufferevent *bev, short events, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
if (events & BEV_EVENT_CONNECTED) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Connection established. stream_id="
<< downstream->get_stream_id();
}
auto fd = bufferevent_getfd(bev);
int val = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
sizeof(val)) == -1) {
DCLOG(WARN, dconn) << "Setting option TCP_NODELAY failed: errno="
<< errno;
}
return;
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If stream was closed already, we don't need to send reply at
// the first place. We can delete downstream.
remove_downstream(downstream);
// downstream was deleted
return 0;
}
if (events & BEV_EVENT_EOF) {
// Delete downstream connection. If we don't delete it here, it will
// be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
ULOG(INFO, this) << "Downstream body was ended by EOF";
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If stream was closed already, we don't need to send reply at
// the first place. We can delete downstream.
upstream->remove_downstream(downstream);
// downstream was deleted
downstream->set_response_state(Downstream::MSG_COMPLETE);
return;
// For tunneled connection, MSG_COMPLETE signals
// downstream_data_read_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that RST_STREAM
// is sent after all pending data are sent.
on_downstream_body_complete(downstream);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
handler_->signal_write();
// At this point, downstream may be deleted.
return 0;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
if (LOG_ENABLED(INFO)) {
if (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
if (downstream->get_upgraded()) {
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
remove_downstream(downstream);
// downstream was deleted
return 0;
}
// Delete downstream connection. If we don't delete it here, it will
// be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if (downstream->get_upgraded()) {
rst_stream(downstream, NGHTTP2_NO_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// For tunneled connection, MSG_COMPLETE signals
// downstream_data_read_callback to send RST_STREAM after
// pending response body is sent. This is needed to ensure
// that RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if (LOG_ENABLED(INFO)) {
if (events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: "
<< evutil_socket_error_to_string(
EVUTIL_SOCKET_ERROR());
if (downstream->get_upgraded()) {
on_downstream_body_complete(downstream);
} else {
DCLOG(INFO, dconn) << "Timeout";
}
if (downstream->get_upgraded()) {
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
// downstream was deleted
return;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if (downstream->get_upgraded()) {
upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
if (downstream->get_upgraded()) {
upstream->on_downstream_body_complete(downstream);
} else {
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
unsigned int status;
if (events & Downstream::EVENT_TIMEOUT) {
status = 504;
} else {
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
status = 502;
}
if (error_reply(downstream, status) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
handler_->signal_write();
// At this point, downstream may be deleted.
return 0;
}
} // namespace
int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
if (LOG_ENABLED(INFO)) {
@ -1048,7 +960,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
void *user_data) {
auto downstream = static_cast<Downstream *>(source->ptr);
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
auto body = downstream->get_response_body_buf();
auto body = downstream->get_response_buf();
auto handler = upstream->get_client_handler();
assert(body);
@ -1062,13 +974,8 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
length = std::min(length, static_cast<size_t>(limit - 9));
}
int nread = evbuffer_remove(body, buf, length);
if (nread == -1) {
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
auto body_empty = evbuffer_get_length(body) == 0;
auto nread = body->remove(buf, length);
auto body_empty = body->rleft() == 0;
if (body_empty &&
downstream->get_response_state() == Downstream::MSG_COMPLETE) {
@ -1106,6 +1013,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
// a chance to read another incoming data from backend to this
// stream.
upstream->set_deferred(true);
return NGHTTP2_ERR_DEFERRED;
}
@ -1122,13 +1030,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
int rv;
auto html = http::create_error_html(status_code);
downstream->set_response_http_status(status_code);
downstream->init_response_body_buf();
auto body = downstream->get_response_body_buf();
rv = evbuffer_add(body, html.c_str(), html.size());
if (rv == -1) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
auto body = downstream->get_response_buf();
body->append(html.c_str(), html.size());
downstream->set_response_state(Downstream::MSG_COMPLETE);
nghttp2_data_provider data_prd;
@ -1154,18 +1057,6 @@ int Http2Upstream::error_reply(Downstream *downstream,
return 0;
}
bufferevent_data_cb Http2Upstream::get_downstream_readcb() {
return downstream_readcb;
}
bufferevent_data_cb Http2Upstream::get_downstream_writecb() {
return downstream_writecb;
}
bufferevent_event_cb Http2Upstream::get_downstream_eventcb() {
return downstream_eventcb;
}
void
Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) {
downstream_queue_.add_pending(std::move(downstream));
@ -1182,6 +1073,9 @@ void Http2Upstream::remove_downstream(Downstream *downstream) {
if (next_downstream) {
initiate_downstream(std::move(next_downstream));
}
mcpool_.shrink((downstream_queue_.get_active_downstreams().size() + 1) *
65536);
}
Downstream *Http2Upstream::find_downstream(int32_t stream_id) {
@ -1304,12 +1198,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 +1207,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 +1238,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 +1252,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 +1296,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 +1330,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,93 @@
#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);
dconn->on_connect();
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 +135,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 +162,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
return SHRPX_ERR_NETWORK;
}
bev_ = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE |
BEV_OPT_DEFER_CALLBACKS);
if (!bev_) {
int rv;
rv = connect(fd_, const_cast<sockaddr *>(
&get_config()->downstream_addrs[i].addr.sa),
get_config()->downstream_addrs[i].addrlen);
if (rv != 0 && errno != EINPROGRESS) {
auto error = errno;
DCLOG(WARN, this) << "bufferevent_socket_new() failed; errno=" << error;
DCLOG(WARN, this) << "connect() failed; errno=" << error;
connect_blocker->on_failure();
close(fd);
close(fd_);
fd_ = -1;
return SHRPX_ERR_NETWORK;
}
int rv = bufferevent_socket_connect(
bev_,
// TODO maybe not thread-safe?
const_cast<sockaddr *>(&get_config()->downstream_addrs[i].addr.sa),
get_config()->downstream_addrs[i].addrlen);
if (rv != 0) {
auto error = errno;
DCLOG(WARN, this) << "bufferevent_socket_connect() failed; errno="
<< error;
connect_blocker->on_failure();
bufferevent_free(bev_);
bev_ = nullptr;
if (i == worker_stat->next_downstream) {
if (end == worker_stat->next_downstream) {
return SHRPX_ERR_NETWORK;
}
@ -133,24 +188,26 @@ 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;
}
@ -292,65 +349,55 @@ int HttpDownstreamConnection::push_request_headers() {
DCLOG(INFO, this) << "HTTP request headers. stream_id="
<< downstream_->get_stream_id() << "\n" << hdrp;
}
auto output = bufferevent_get_output(bev_);
int rv;
rv = evbuffer_add(output, hdrs.c_str(), hdrs.size());
if (rv != 0) {
return -1;
}
auto output = downstream_->get_request_buf();
output->append(hdrs.c_str(), hdrs.size());
signal_write();
return 0;
}
int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
size_t datalen) {
int rv;
int chunked = downstream_->get_chunked_request();
auto output = bufferevent_get_output(bev_);
auto chunked = downstream_->get_chunked_request();
auto output = downstream_->get_request_buf();
if (chunked) {
auto chunk_size_hex = util::utox(datalen);
chunk_size_hex += "\r\n";
rv = evbuffer_add(output, chunk_size_hex.c_str(), chunk_size_hex.size());
if (rv == -1) {
DCLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
output->append_cstr("\r\n");
}
rv = evbuffer_add(output, data, datalen);
if (rv == -1) {
DCLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
output->append(data, datalen);
if (chunked) {
rv = evbuffer_add(output, "\r\n", 2);
if (rv == -1) {
DCLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
output->append_cstr("\r\n");
}
signal_write();
return 0;
}
int HttpDownstreamConnection::end_upload_data() {
if (downstream_->get_chunked_request()) {
auto output = bufferevent_get_output(bev_);
if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) {
DCLOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
if (!downstream_->get_chunked_request()) {
return 0;
}
auto output = downstream_->get_request_buf();
output->append_cstr("0\r\n\r\n");
signal_write();
return 0;
}
namespace {
void idle_readcb(bufferevent *bev, void *arg) {
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection EOF";
}
auto dconn_pool = dconn->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
// dconn was deleted
@ -358,26 +405,10 @@ void idle_readcb(bufferevent *bev, void *arg) {
} // namespace
namespace {
// Gets called when DownstreamConnection is pooled in ClientHandler.
void idle_eventcb(bufferevent *bev, short events, void *arg) {
auto dconn = static_cast<HttpDownstreamConnection *>(arg);
if (events & BEV_EVENT_CONNECTED) {
// Downstream was detached before connection established?
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection connected?";
}
} else if (events & BEV_EVENT_EOF) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection EOF";
}
} else if (events & BEV_EVENT_TIMEOUT) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection timeout";
}
} else if (events & BEV_EVENT_ERROR) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection network error";
}
void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
auto dconn = static_cast<HttpDownstreamConnection *>(w->data);
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection timeout";
}
auto dconn_pool = dconn->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
@ -391,15 +422,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 +440,10 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
size_t consumed) {
ioctrl_.resume_read(reason);
if (!downstream_->response_buf_full()) {
ioctrl_.resume_read(reason);
}
return 0;
}
@ -415,11 +451,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);
@ -577,52 +608,62 @@ http_parser_settings htp_hooks = {
} // namespace
int HttpDownstreamConnection::on_read() {
reset_timeouts();
auto input = bufferevent_get_input(bev_);
ev_timer_again(loop_, &rt_);
uint8_t buf[8192];
int rv;
if (downstream_->get_upgraded()) {
// For upgraded connection, just pass data to the upstream.
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
ssize_t nread;
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
;
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return DownstreamConnection::ERR_NET;
}
auto mem = evbuffer_pullup(input, inputlen);
if (nread == 0) {
return DownstreamConnection::ERR_EOF;
}
int rv;
rv = downstream_->get_upstream()->on_downstream_body(
downstream_, reinterpret_cast<const uint8_t *>(mem), inputlen, true);
rv = downstream_->get_upstream()->on_downstream_body(downstream_, buf,
nread, true);
if (rv != 0) {
return rv;
}
if (evbuffer_drain(input, inputlen) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
if (downstream_->response_buf_full()) {
downstream_->pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
}
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
assert(evbuffer_get_length(input) == 0);
return 0;
ssize_t nread;
while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR)
;
if (nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
return DownstreamConnection::ERR_NET;
}
auto mem = evbuffer_pullup(input, inputlen);
if (nread == 0) {
return DownstreamConnection::ERR_EOF;
}
auto nread =
http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<const char *>(mem), inputlen);
auto nproc = http_parser_execute(&response_htp_, &htp_hooks,
reinterpret_cast<char *>(buf), nread);
if (evbuffer_drain(input, nread) != 0) {
DCLOG(FATAL, this) << "evbuffer_drain() failed";
if (nproc != nread) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "nproc != nread";
}
return -1;
}
@ -635,29 +676,64 @@ int HttpDownstreamConnection::on_read() {
<< http_errno_description(htperr);
}
return SHRPX_ERR_HTTP_PARSE;
return -1;
}
if (downstream_->response_buf_full()) {
downstream_->pause_read(SHRPX_NO_BUFFER);
return 0;
}
}
}
int HttpDownstreamConnection::on_write() {
reset_timeouts();
ev_timer_again(loop_, &rt_);
auto upstream = downstream_->get_upstream();
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
downstream_->get_request_datalen());
auto input = downstream_->get_request_buf();
while (input->rleft() > 0) {
struct iovec iov[2];
auto iovcnt = input->riovec(iov, util::array_size(iov));
ssize_t nwrite;
while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR)
;
if (nwrite == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
ev_io_start(loop_, &wev_);
ev_timer_again(loop_, &wt_);
goto end;
}
return DownstreamConnection::ERR_NET;
}
input->drain(nwrite);
}
if (input->rleft() == 0) {
ev_io_stop(loop_, &wev_);
ev_timer_stop(loop_, &wt_);
} else {
ev_io_start(loop_, &wev_);
ev_timer_again(loop_, &wt_);
}
end:
if (input->rleft() == 0) {
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
downstream_->get_request_datalen());
}
return 0;
}
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {
bufferevent_setcb(bev_, upstream->get_downstream_readcb(),
upstream->get_downstream_writecb(),
upstream->get_downstream_eventcb(), this);
void HttpDownstreamConnection::on_connect() {
ev_io_start(loop_, &rev_);
ev_set_cb(&wev_, writecb);
}
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();
void 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;
}
@ -241,37 +237,32 @@ http_parser_settings htp_hooks = {
// on_read() does not consume all available data in input buffer if
// one http request is fully received.
int HttpsUpstream::on_read() {
auto bev = handler_->get_bev();
auto input = bufferevent_get_input(bev);
auto rb = handler_->get_rb();
auto downstream = get_downstream();
const void *data;
size_t datalen;
// downstream can be nullptr here, because it is initialized in the
// callback chain called by http_parser_execute()
if (downstream && downstream->get_upgraded()) {
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
std::tie(data, datalen) = rb->get();
if (datalen == 0) {
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
auto rv = downstream->push_upload_data_chunk(
reinterpret_cast<const uint8_t *>(mem), inputlen);
reinterpret_cast<const uint8_t *>(data), datalen);
if (rv != 0) {
return -1;
}
if (evbuffer_drain(input, inputlen) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
rb->drain(datalen);
if (downstream->get_output_buffer_full()) {
if (downstream->request_buf_full()) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
ULOG(INFO, this) << "Downstream request buf is full";
}
pause_read(SHRPX_NO_BUFFER);
@ -281,21 +272,15 @@ int HttpsUpstream::on_read() {
}
for (;;) {
auto inputlen = evbuffer_get_contiguous_space(input);
if (inputlen == 0) {
std::tie(data, datalen) = rb->get();
if (datalen == 0) {
return 0;
}
auto mem = evbuffer_pullup(input, inputlen);
auto nread = http_parser_execute(
&htp_, &htp_hooks, reinterpret_cast<const char *>(mem), inputlen);
&htp_, &htp_hooks, reinterpret_cast<const char *>(data), datalen);
if (evbuffer_drain(input, nread) != 0) {
ULOG(FATAL, this) << "evbuffer_drain() failed";
return -1;
}
rb->drain(nread);
// Well, actually header length + some body bytes
current_header_length_ += nread;
@ -318,6 +303,7 @@ int HttpsUpstream::on_read() {
if (error_reply(503) != 0) {
return -1;
}
handler_->signal_write();
// Downstream gets deleted after response body is read.
return 0;
}
@ -339,6 +325,8 @@ int HttpsUpstream::on_read() {
return -1;
}
handler_->signal_write();
return 0;
}
@ -370,13 +358,15 @@ int HttpsUpstream::on_read() {
return -1;
}
handler_->signal_write();
return 0;
}
// downstream can be NULL here.
if (downstream && downstream->get_output_buffer_full()) {
if (downstream && downstream->request_buf_full()) {
if (LOG_ENABLED(INFO)) {
ULOG(INFO, this) << "Downstream output buffer is full";
ULOG(INFO, this) << "Downstream request buffer is full";
}
pause_read(SHRPX_NO_BUFFER);
@ -387,31 +377,45 @@ int HttpsUpstream::on_read() {
}
int HttpsUpstream::on_write() {
int rv = 0;
auto downstream = get_downstream();
if (downstream) {
// We need to postpone detachment until all data are sent so that
// we can notify nghttp2 library all data consumed.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
if (downstream->get_response_connection_close()) {
// Connection close
downstream->pop_downstream_connection();
// dconn was deleted
} else {
// Keep-alive
downstream->detach_downstream_connection();
}
// We need this if response ends before request.
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
delete_downstream();
return resume_read(SHRPX_MSG_BLOCK, nullptr, 0);
}
}
rv = downstream->resume_read(SHRPX_NO_BUFFER,
downstream->get_response_datalen());
if (!downstream) {
return 0;
}
return rv;
auto wb = handler_->get_wb();
struct iovec iov[2];
auto iovcnt = wb->wiovec(iov);
if (iovcnt == 0) {
return 0;
}
auto output = downstream->get_response_buf();
for (int i = 0; i < iovcnt; ++i) {
auto n = output->remove(iov[i].iov_base, iov[i].iov_len);
wb->write(n);
}
if (wb->rleft() > 0) {
return 0;
}
// We need to postpone detachment until all data are sent so that
// we can notify nghttp2 library all data consumed.
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
if (downstream->get_response_connection_close()) {
// Connection close
downstream->pop_downstream_connection();
// dconn was deleted
} else {
// Keep-alive
downstream->detach_downstream_connection();
}
// We need this if response ends before request.
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
delete_downstream();
return resume_read(SHRPX_MSG_BLOCK, nullptr, 0);
}
}
return downstream->resume_read(SHRPX_NO_BUFFER,
downstream->get_response_datalen());
}
int HttpsUpstream::on_event() { return 0; }
@ -424,6 +428,10 @@ void HttpsUpstream::pause_read(IOCtrlReason reason) {
int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
size_t consumed) {
// downstream could be nullptr if reason is SHRPX_MSG_BLOCK.
if (downstream && downstream->request_buf_full()) {
return 0;
}
if (ioctrl_.resume_read(reason)) {
// Process remaining data in input buffer here because these bytes
// are not notified by readcb until new data arrive.
@ -434,272 +442,147 @@ int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
return 0;
}
namespace {
void https_downstream_readcb(bufferevent *bev, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
int rv;
rv = downstream->on_read();
if (downstream->get_response_state() == Downstream::MSG_RESET) {
delete upstream->get_client_handler();
return;
}
if (rv != 0) {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// We already sent HTTP response headers to upstream
// client. Just close the upstream connection.
delete upstream->get_client_handler();
return;
}
// We did not sent any HTTP response, so sent error
// response. Cannot reuse downstream connection in this case.
if (upstream->error_reply(502) != 0) {
delete upstream->get_client_handler();
return;
}
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
upstream->delete_downstream();
// Process next HTTP request
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
return;
}
}
return;
}
auto handler = upstream->get_client_handler();
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
if (handler->get_outbuf_length() >= OUTBUF_MAX_THRES) {
downstream->pause_read(SHRPX_NO_BUFFER);
}
return;
}
// If pending data exist, we defer detachment to correctly notify
// the all consumed data to nghttp2 library.
if (handler->get_outbuf_length() == 0) {
if (downstream->get_response_connection_close()) {
// Connection close
downstream->pop_downstream_connection();
dconn = nullptr;
} else {
// Keep-alive
downstream->detach_downstream_connection();
}
}
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
if (handler->get_should_close_after_write() &&
handler->get_outbuf_length() == 0) {
// If all upstream response body has already written out to
// the peer, we cannot use writecb for ClientHandler. In
// this case, we just delete handler here.
delete handler;
return;
}
upstream->delete_downstream();
// Process next HTTP request
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
return;
}
return;
}
if (downstream->get_upgraded()) {
// This path is effectively only taken for HTTP2 downstream
// because only HTTP2 downstream sets response_state to
// MSG_COMPLETE and this function. For HTTP downstream, EOF
// from tunnel connection is handled on
// https_downstream_eventcb.
//
// Tunneled connection always indicates connection close.
if (handler->get_outbuf_length() == 0) {
// For tunneled connection, if there is no pending data,
// delete handler because on_write will not be called.
delete handler;
return;
}
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Tunneled connection has pending data";
}
return;
}
// Delete handler here if we have no pending write.
if (handler->get_should_close_after_write() &&
handler->get_outbuf_length() == 0) {
delete handler;
}
}
} // namespace
namespace {
void https_downstream_writecb(bufferevent *bev, void *ptr) {
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
return;
}
auto dconn = static_cast<DownstreamConnection *>(ptr);
auto downstream = dconn->get_downstream();
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
// May return -1
upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
}
} // namespace
namespace {
void https_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
auto downstream = dconn->get_downstream();
auto upstream = static_cast<HttpsUpstream *>(downstream->get_upstream());
if (events & BEV_EVENT_CONNECTED) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Connection established";
}
return;
}
if (events & BEV_EVENT_EOF) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF";
}
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "The end of the response body was indicated by "
<< "EOF";
}
upstream->on_downstream_body_complete(downstream);
downstream->set_response_state(Downstream::MSG_COMPLETE);
auto handler = upstream->get_client_handler();
if (handler->get_should_close_after_write() &&
handler->get_outbuf_length() == 0) {
// If all upstream response body has already written out to
// the peer, we cannot use writecb for ClientHandler. In this
// case, we just delete handler here.
delete handler;
return;
}
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// error
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Treated as error";
}
if (upstream->error_reply(502) != 0) {
delete upstream->get_client_handler();
return;
}
}
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
upstream->delete_downstream();
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
return;
}
}
return;
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if (LOG_ENABLED(INFO)) {
if (events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Network error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
}
if (downstream->get_response_state() == Downstream::INITIAL) {
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (upstream->error_reply(status) != 0) {
delete upstream->get_client_handler();
return;
}
}
if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
upstream->delete_downstream();
if (upstream->resume_read(SHRPX_MSG_BLOCK, nullptr, 0) == -1) {
return;
}
}
}
}
} // namespace
int HttpsUpstream::error_reply(unsigned int status_code) {
auto html = http::create_error_html(status_code);
auto downstream = get_downstream();
if (downstream) {
downstream->set_response_http_status(status_code);
}
std::string header;
header.reserve(512);
header += "HTTP/1.1 ";
header += http2::get_status_string(status_code);
header += "\r\nServer: ";
header += get_config()->server_name;
header += "\r\nContent-Length: ";
header += util::utos(html.size());
header += "\r\nContent-Type: text/html; charset=UTF-8\r\n";
if (get_client_handler()->get_should_close_after_write()) {
header += "Connection: close\r\n";
}
header += "\r\n";
auto output = bufferevent_get_output(handler_->get_bev());
if (evbuffer_add(output, header.c_str(), header.size()) != 0 ||
evbuffer_add(output, html.c_str(), html.size()) != 0) {
ULOG(FATAL, this) << "evbuffer_add() failed";
return -1;
}
if (downstream) {
downstream->add_response_sent_bodylen(html.size());
downstream->set_response_state(Downstream::MSG_COMPLETE);
} else {
handler_->write_accesslog(1, 1, status_code, html.size());
if (rv == DownstreamConnection::ERR_EOF) {
return downstream_eof(dconn);
}
if (rv == DownstreamConnection::ERR_NET) {
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
if (rv < 0) {
return -1;
}
handler_->signal_write();
return 0;
}
int HttpsUpstream::downstream_write(DownstreamConnection *dconn) {
int rv;
rv = dconn->on_write();
if (rv == DownstreamConnection::ERR_NET) {
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
if (rv != 0) {
return -1;
}
return 0;
}
bufferevent_data_cb HttpsUpstream::get_downstream_readcb() {
return https_downstream_readcb;
int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF";
}
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "The end of the response body was indicated by "
<< "EOF";
}
on_downstream_body_complete(downstream);
downstream->set_response_state(Downstream::MSG_COMPLETE);
downstream->pop_downstream_connection();
goto end;
}
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// error
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Treated as error";
}
if (error_reply(502) != 0) {
return -1;
}
downstream->pop_downstream_connection();
goto end;
}
// Otherwise, we don't know how to recover from this situation. Just
// drop connection.
return -1;
end:
handler_->signal_write();
return 0;
}
bufferevent_data_cb HttpsUpstream::get_downstream_writecb() {
return https_downstream_writecb;
int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
if (LOG_ENABLED(INFO)) {
if (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Network error/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
}
if (downstream->get_response_state() != Downstream::INITIAL) {
return -1;
}
unsigned int status;
if (events & Downstream::EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (error_reply(status) != 0) {
return -1;
}
downstream->pop_downstream_connection();
handler_->signal_write();
return 0;
}
bufferevent_event_cb HttpsUpstream::get_downstream_eventcb() {
return https_downstream_eventcb;
int HttpsUpstream::error_reply(unsigned int status_code) {
auto html = http::create_error_html(status_code);
auto downstream = get_downstream();
if (!downstream) {
attach_downstream(util::make_unique<Downstream>(this, 1, 1));
downstream = get_downstream();
}
downstream->set_response_http_status(status_code);
auto output = downstream->get_response_buf();
output->append_cstr("HTTP/1.1 ");
auto status_str = http2::get_status_string(status_code);
output->append(status_str.c_str(), status_str.size());
output->append_cstr("\r\nServer: ");
output->append(get_config()->server_name, strlen(get_config()->server_name));
output->append_cstr("\r\nContent-Length: ");
auto cl = util::utos(html.size());
output->append(cl.c_str(), cl.size());
output->append_cstr("\r\nContent-Type: text/html; charset=UTF-8\r\n");
if (get_client_handler()->get_should_close_after_write()) {
output->append_cstr("Connection: close\r\n");
}
output->append_cstr("\r\n");
output->append(html.c_str(), html.size());
downstream->add_response_sent_bodylen(html.size());
downstream->set_response_state(Downstream::MSG_COMPLETE);
return 0;
}
void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
@ -747,6 +630,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
http2::build_http1_headers_from_norm_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();
@ -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,42 @@
#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::set_lim(RateLimit *lim) { lim_ = lim; }
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,9 @@ 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 set_lim(RateLimit *lim);
void pause_read(IOCtrlReason reason);
// Returns true if read operation is enabled after this call
bool resume_read(IOCtrlReason reason);
@ -48,7 +49,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

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

96
src/shrpx_rate_limit.cc Normal file
View File

@ -0,0 +1,96 @@
/*
* 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"
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 SSIZE_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
@ -157,7 +154,6 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
downstream->init_upstream_timer();
downstream->reset_upstream_rtimer();
downstream->init_response_body_buf();
auto nv = frame->syn_stream.nv;
const char *path = nullptr;
@ -408,9 +404,6 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
? get_config()->downstream_connections_per_host
: 0),
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 +454,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 +473,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 +490,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 +501,19 @@ int SpdyUpstream::send() {
return 0;
}
int SpdyUpstream::on_event() { return 0; }
ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; }
namespace {
void spdy_downstream_readcb(bufferevent *bev, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If upstream SPDY stream was closed, we just close downstream,
// because there is no consumer now. Downstream connection is also
// closed in this case.
upstream->remove_downstream(downstream);
remove_downstream(downstream);
// downstrea was deleted
return;
return 0;
}
if (downstream->get_response_state() == Downstream::MSG_RESET) {
@ -541,178 +521,148 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr) {
// RST_STREAM to the upstream and delete downstream connection
// here. Deleting downstream will be taken place at
// on_stream_close_callback.
upstream->rst_stream(downstream,
infer_upstream_rst_stream_status_code(
downstream->get_response_rst_stream_error_code()));
rst_stream(downstream,
infer_upstream_rst_stream_status_code(
downstream->get_response_rst_stream_error_code()));
downstream->pop_downstream_connection();
dconn = nullptr;
} else {
auto rv = downstream->on_read();
if (rv == DownstreamConnection::ERR_EOF) {
return downstream_eof(dconn);
}
if (rv != 0) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure";
}
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If response was completed, then don't issue RST_STREAM
if (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
if (rv != DownstreamConnection::ERR_NET) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "HTTP parser failure";
}
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// Clearly, we have to close downstream connection on http parser
// failure.
downstream->pop_downstream_connection();
dconn = nullptr;
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
handler_->signal_write();
// At this point, downstream may be deleted.
}
} // namespace
namespace {
void spdy_downstream_writecb(bufferevent *bev, void *ptr) {
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
return;
return 0;
}
int SpdyUpstream::downstream_write(DownstreamConnection *dconn) {
int rv;
rv = dconn->on_write();
if (rv == DownstreamConnection::ERR_NET) {
return downstream_error(dconn, Downstream::EVENT_ERROR);
}
auto dconn = static_cast<DownstreamConnection *>(ptr);
dconn->on_write();
if (rv != 0) {
return -1;
}
return 0;
}
} // namespace
namespace {
void spdy_downstream_eventcb(bufferevent *bev, short events, void *ptr) {
auto dconn = static_cast<DownstreamConnection *>(ptr);
int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) {
auto downstream = dconn->get_downstream();
auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
if (events & BEV_EVENT_CONNECTED) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Connection established. stream_id="
<< downstream->get_stream_id();
}
int fd = bufferevent_getfd(bev);
int val = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
sizeof(val)) == -1) {
DCLOG(WARN, dconn) << "Setting option TCP_NODELAY failed: errno="
<< errno;
}
return;
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If stream was closed already, we don't need to send reply at
// the first place. We can delete downstream.
remove_downstream(downstream);
// downstream was deleted
return 0;
}
if (events & BEV_EVENT_EOF) {
// Delete downstream connection. If we don't delete it here, it will
// be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
ULOG(INFO, this) << "Downstream body was ended by EOF";
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
// If stream was closed already, we don't need to send reply at
// the first place. We can delete downstream.
upstream->remove_downstream(downstream);
// downstrea was deleted
downstream->set_response_state(Downstream::MSG_COMPLETE);
return;
// For tunneled connection, MSG_COMPLETE signals
// downstream_data_read_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that RST_STREAM
// is sent after all pending data are sent.
on_downstream_body_complete(downstream);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if (error_reply(downstream, 502) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
handler_->signal_write();
// At this point, downstream may be deleted.
return 0;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
dconn = nullptr;
// downstream wil be deleted in on_stream_close_callback.
int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) {
auto downstream = dconn->get_downstream();
if (LOG_ENABLED(INFO)) {
if (events & Downstream::EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network/general error";
} else {
DCLOG(INFO, dconn) << "Timeout";
}
if (downstream->get_upgraded()) {
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
remove_downstream(downstream);
// downstream was deleted
return 0;
}
// Delete downstream connection. If we don't delete it here, it will
// be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
// dconn was deleted
dconn = nullptr;
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if (downstream->get_upgraded()) {
rst_stream(downstream, NGHTTP2_NO_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
// Server may indicate the end of the request by EOF
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Downstream body was ended by EOF";
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
// For tunneled connection, MSG_COMPLETE signals
// spdy_data_read_callback to send RST_STREAM after pending
// response body is sent. This is needed to ensure that
// RST_STREAM is sent after all pending data are sent.
upstream->on_downstream_body_complete(downstream);
} else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
// If stream was not closed, then we set MSG_COMPLETE and let
// on_stream_close_callback delete downstream.
if (upstream->error_reply(downstream, 502) != 0) {
delete upstream->get_client_handler();
return;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) {
if (LOG_ENABLED(INFO)) {
if (events & BEV_EVENT_ERROR) {
DCLOG(INFO, dconn) << "Downstream network error: "
<< evutil_socket_error_to_string(
EVUTIL_SOCKET_ERROR());
if (downstream->get_upgraded()) {
on_downstream_body_complete(downstream);
} else {
DCLOG(INFO, dconn) << "Timeout";
}
if (downstream->get_upgraded()) {
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
}
}
if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
upstream->remove_downstream(downstream);
// downstrea was deleted
return;
}
// Delete downstream connection. If we don't delete it here, it
// will be pooled in on_stream_close_callback.
downstream->pop_downstream_connection();
dconn = nullptr;
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
// For SSL tunneling, we issue RST_STREAM. For other types of
// stream, we don't have to do anything since response was
// complete.
if (downstream->get_upgraded()) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
} else {
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
unsigned int status;
if (events & Downstream::EVENT_TIMEOUT) {
status = 504;
} else {
unsigned int status;
if (events & BEV_EVENT_TIMEOUT) {
status = 504;
} else {
status = 502;
}
if (upstream->error_reply(downstream, status) != 0) {
delete upstream->get_client_handler();
return;
}
status = 502;
}
if (error_reply(downstream, status) != 0) {
return -1;
}
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
if (upstream->send() != 0) {
delete upstream->get_client_handler();
return;
}
// At this point, downstream may be deleted.
return;
downstream->set_response_state(Downstream::MSG_COMPLETE);
}
handler_->signal_write();
// At this point, downstream may be deleted.
return 0;
}
} // namespace
int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) {
if (LOG_ENABLED(INFO)) {
@ -735,7 +685,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,7 +699,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);
auto nread = body->remove(buf, length);
auto body_empty = body->rleft() == 0;
if (nread == -1) {
ULOG(FATAL, upstream) << "evbuffer_remove() failed";
return SPDYLAY_ERR_CALLBACK_FAILURE;
@ -770,10 +722,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 +749,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 +777,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 +798,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) {
@ -972,12 +910,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 +919,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 +951,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 +965,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 +994,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 +1028,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,48 @@ 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_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