src: Use libev for rest of the applications
This commit is contained in:
parent
cd7258a7cd
commit
bfac015d61
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
574
src/h2load.cc
574
src/h2load.cc
|
@ -42,8 +42,6 @@
|
|||
#include <spdylay/spdylay.h>
|
||||
#endif // HAVE_SPDYLAY
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/conf.h>
|
||||
|
||||
|
@ -70,18 +68,6 @@ Config::~Config() { freeaddrinfo(addrs); }
|
|||
|
||||
Config config;
|
||||
|
||||
namespace {
|
||||
void eventcb(bufferevent *bev, short events, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void readcb(bufferevent *bev, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void writecb(bufferevent *bev, void *ptr);
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void debug(const char *format, ...) {
|
||||
if (config.verbose) {
|
||||
|
@ -94,46 +80,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;
|
||||
|
|
36
src/h2load.h
36
src/h2load.h
|
@ -38,12 +38,14 @@
|
|||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/event.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "http2.h"
|
||||
#include "ringbuf.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -107,7 +109,7 @@ struct Client;
|
|||
struct Worker {
|
||||
std::vector<std::unique_ptr<Client>> clients;
|
||||
Stats stats;
|
||||
event_base *evbase;
|
||||
struct ev_loop *loop;
|
||||
SSL_CTX *ssl_ctx;
|
||||
Config *config;
|
||||
size_t progress_interval;
|
||||
|
@ -128,9 +130,13 @@ struct Stream {
|
|||
struct Client {
|
||||
std::unordered_map<int32_t, Stream> streams;
|
||||
std::unique_ptr<Session> session;
|
||||
ev_io wev;
|
||||
ev_io rev;
|
||||
std::function<int(Client &)> readfn, writefn;
|
||||
std::function<int(Client &, const uint8_t *, size_t)> on_readfn;
|
||||
std::function<int(Client &)> on_writefn;
|
||||
Worker *worker;
|
||||
SSL *ssl;
|
||||
bufferevent *bev;
|
||||
addrinfo *next_addr;
|
||||
size_t reqidx;
|
||||
ClientState state;
|
||||
|
@ -140,6 +146,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
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
#include "h2load.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
|
@ -86,6 +85,20 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
||||
size_t length, int flags, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto &wb = client->wb;
|
||||
|
||||
if (wb.wleft() == 0) {
|
||||
return NGHTTP2_ERR_WOULDBLOCK;
|
||||
}
|
||||
|
||||
return wb.write(data, length);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http2Session::on_connect() {
|
||||
int rv;
|
||||
|
||||
|
@ -108,6 +121,8 @@ void Http2Session::on_connect() {
|
|||
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
||||
on_header_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
||||
|
||||
nghttp2_session_client_new(&session_, callbacks, client_);
|
||||
|
||||
nghttp2_settings_entry iv[2];
|
||||
|
@ -129,8 +144,13 @@ void Http2Session::on_connect() {
|
|||
extra_connection_window);
|
||||
}
|
||||
|
||||
bufferevent_write(client_->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
|
||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||
auto &wb = client_->wb;
|
||||
assert(wb.wleft() >= NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||
|
||||
wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
|
||||
NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
|
||||
|
||||
client_->signal_write();
|
||||
}
|
||||
|
||||
void Http2Session::submit_request() {
|
||||
|
@ -148,66 +168,35 @@ void Http2Session::submit_request() {
|
|||
client_->on_request(stream_id);
|
||||
}
|
||||
|
||||
ssize_t Http2Session::on_read() {
|
||||
int rv;
|
||||
size_t nread = 0;
|
||||
|
||||
auto input = bufferevent_get_input(client_->bev);
|
||||
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
assert(evbuffer_get_length(input) == 0);
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
auto mem = evbuffer_pullup(input, inputlen);
|
||||
|
||||
rv = nghttp2_session_mem_recv(session_, mem, inputlen);
|
||||
|
||||
if (rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nread += rv;
|
||||
|
||||
if (evbuffer_drain(input, rv) != 0) {
|
||||
return -1;
|
||||
}
|
||||
int Http2Session::on_read(const uint8_t *data, size_t len) {
|
||||
auto rv = nghttp2_session_mem_recv(session_, data, len);
|
||||
if (rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(static_cast<size_t>(rv) == len);
|
||||
|
||||
if (nghttp2_session_want_read(session_) == 0 &&
|
||||
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
client_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2Session::on_write() {
|
||||
int rv;
|
||||
uint8_t buf[16384];
|
||||
auto output = bufferevent_get_output(client_->bev);
|
||||
util::EvbufferBuffer evbbuf(output, buf, sizeof(buf));
|
||||
for (;;) {
|
||||
const uint8_t *data;
|
||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||
|
||||
if (datalen < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (datalen == 0) {
|
||||
break;
|
||||
}
|
||||
rv = evbbuf.add(data, datalen);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
rv = evbbuf.flush();
|
||||
auto rv = nghttp2_session_send(session_);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nghttp2_session_want_read(session_) == 0 &&
|
||||
nghttp2_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(output) == 0) {
|
||||
nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
virtual ~Http2Session();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual ssize_t on_read();
|
||||
virtual int on_read(const uint8_t *data, size_t len);
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "nghttp2_config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -40,7 +41,7 @@ public:
|
|||
virtual void submit_request() = 0;
|
||||
// Called when incoming bytes are available. The subclass has to
|
||||
// return the number of bytes read.
|
||||
virtual ssize_t on_read() = 0;
|
||||
virtual int on_read(const uint8_t *data, size_t len) = 0;
|
||||
// Called when write is available. Returns 0 on success, otherwise
|
||||
// return -1.
|
||||
virtual int on_write() = 0;
|
||||
|
|
|
@ -94,14 +94,13 @@ namespace {
|
|||
ssize_t send_callback(spdylay_session *session, const uint8_t *data,
|
||||
size_t length, int flags, void *user_data) {
|
||||
auto client = static_cast<Client *>(user_data);
|
||||
auto spdy_session = static_cast<SpdySession *>(client->session.get());
|
||||
int rv;
|
||||
auto &wb = client->wb;
|
||||
|
||||
rv = spdy_session->sendbuf.add(data, length);
|
||||
if (rv != 0) {
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
if (wb.wleft() == 0) {
|
||||
return SPDYLAY_ERR_DEFERRED;
|
||||
}
|
||||
return length;
|
||||
|
||||
return wb.write(data, length);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -134,6 +133,8 @@ void SpdySession::on_connect() {
|
|||
(1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE;
|
||||
spdylay_submit_window_update(session_, 0, delta);
|
||||
}
|
||||
|
||||
client_->signal_write();
|
||||
}
|
||||
|
||||
void SpdySession::submit_request() {
|
||||
|
@ -147,55 +148,32 @@ void SpdySession::submit_request() {
|
|||
spdylay_submit_request(session_, 0, nv.data(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
ssize_t SpdySession::on_read() {
|
||||
int rv;
|
||||
size_t nread = 0;
|
||||
auto input = bufferevent_get_input(client_->bev);
|
||||
|
||||
for (;;) {
|
||||
auto inputlen = evbuffer_get_contiguous_space(input);
|
||||
|
||||
if (inputlen == 0) {
|
||||
assert(evbuffer_get_length(input) == 0);
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
auto mem = evbuffer_pullup(input, inputlen);
|
||||
|
||||
rv = spdylay_session_mem_recv(session_, mem, inputlen);
|
||||
|
||||
if (rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nread += rv;
|
||||
|
||||
if (evbuffer_drain(input, rv) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SpdySession::on_write() {
|
||||
int rv;
|
||||
uint8_t buf[16384];
|
||||
|
||||
sendbuf.reset(bufferevent_get_output(client_->bev), buf, sizeof(buf));
|
||||
|
||||
rv = spdylay_session_send(session_);
|
||||
if (rv != 0) {
|
||||
int SpdySession::on_read(const uint8_t *data, size_t len) {
|
||||
auto rv = spdylay_session_mem_recv(session_, data, len);
|
||||
if (rv < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = sendbuf.flush();
|
||||
assert(static_cast<size_t>(rv) == len);
|
||||
|
||||
if (spdylay_session_want_read(session_) == 0 &&
|
||||
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
client_->signal_write();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SpdySession::on_write() {
|
||||
auto rv = spdylay_session_send(session_);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (spdylay_session_want_read(session_) == 0 &&
|
||||
spdylay_session_want_write(session_) == 0 &&
|
||||
evbuffer_get_length(bufferevent_get_output(client_->bev)) == 0) {
|
||||
spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include <spdylay/spdylay.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
|
@ -42,13 +41,11 @@ public:
|
|||
virtual ~SpdySession();
|
||||
virtual void on_connect();
|
||||
virtual void submit_request();
|
||||
virtual ssize_t on_read();
|
||||
virtual int on_read(const uint8_t *data, size_t len);
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
void handle_window_update(int32_t stream_id, size_t recvlen);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
Client *client_;
|
||||
spdylay_session *session_;
|
||||
|
|
|
@ -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
|
905
src/nghttp.cc
905
src/nghttp.cc
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
298
src/shrpx.cc
298
src/shrpx.cc
|
@ -48,7 +48,7 @@
|
|||
#include <openssl/err.h>
|
||||
#include <openssl/conf.h>
|
||||
|
||||
#include <event2/listener.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
|
@ -57,6 +57,7 @@
|
|||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_accept_handler.h"
|
||||
#include "util.h"
|
||||
#include "app_helper.h"
|
||||
#include "ssl.h"
|
||||
|
@ -82,13 +83,14 @@ const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
|
|||
// binary is listening to.
|
||||
#define ENV_PORT "NGHTTPX_PORT"
|
||||
|
||||
namespace {
|
||||
void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int addrlen,
|
||||
void *arg) {
|
||||
auto handler = static_cast<ListenHandler *>(arg);
|
||||
handler->accept_connection(fd, addr, addrlen);
|
||||
}
|
||||
} // namespace
|
||||
// namespace {
|
||||
// void ssl_acceptcb(evconnlistener *listener, int fd, sockaddr *addr, int
|
||||
// addrlen,
|
||||
// void *arg) {
|
||||
// auto handler = static_cast<ListenHandler *>(arg);
|
||||
// handler->accept_connection(fd, addr, addrlen);
|
||||
// }
|
||||
// } // namespace
|
||||
|
||||
namespace {
|
||||
bool is_ipv6_numeric_addr(const char *host) {
|
||||
|
@ -145,28 +147,8 @@ int resolve_hostname(sockaddr_union *addr, size_t *addrlen,
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void evlistener_errorcb(evconnlistener *listener, void *ptr) {
|
||||
LOG(ERROR) << "Accepting incoming connection failed";
|
||||
|
||||
auto listener_handler = static_cast<ListenHandler *>(ptr);
|
||||
|
||||
listener_handler->disable_evlistener_temporary(
|
||||
&get_config()->listener_disable_timeout);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
evconnlistener *new_evlistener(ListenHandler *handler, int fd) {
|
||||
auto evlistener = evconnlistener_new(
|
||||
handler->get_evbase(), ssl_acceptcb, handler,
|
||||
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, get_config()->backlog, fd);
|
||||
evconnlistener_set_error_cb(evlistener, evlistener_errorcb);
|
||||
return evlistener;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
||||
std::unique_ptr<AcceptHandler> create_acceptor(ListenHandler *handler,
|
||||
int family) {
|
||||
{
|
||||
auto envfd =
|
||||
getenv(family == AF_INET ? ENV_LISTENER4_FD : ENV_LISTENER6_FD);
|
||||
|
@ -182,7 +164,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
if (port == get_config()->port) {
|
||||
LOG(NOTICE) << "Listening on port " << get_config()->port;
|
||||
|
||||
return new_evlistener(handler, fd);
|
||||
return util::make_unique<AcceptHandler>(fd, handler);
|
||||
}
|
||||
|
||||
LOG(WARN) << "Port was changed between old binary (" << port
|
||||
|
@ -219,7 +201,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
return nullptr;
|
||||
}
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
fd =
|
||||
socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
|
||||
if (fd == -1) {
|
||||
continue;
|
||||
}
|
||||
|
@ -229,7 +212,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
close(fd);
|
||||
continue;
|
||||
}
|
||||
evutil_make_socket_nonblocking(fd);
|
||||
|
||||
#ifdef IPV6_V6ONLY
|
||||
if (family == AF_INET6) {
|
||||
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
|
||||
|
@ -239,7 +222,8 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
}
|
||||
}
|
||||
#endif // IPV6_V6ONLY
|
||||
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
|
||||
if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 &&
|
||||
listen(fd, get_config()->backlog) == 0) {
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
|
@ -270,7 +254,7 @@ evconnlistener *create_evlistener(ListenHandler *handler, int family) {
|
|||
|
||||
LOG(NOTICE) << "Listening on " << host << ", port " << get_config()->port;
|
||||
|
||||
return new_evlistener(handler, fd);
|
||||
return util::make_unique<AcceptHandler>(fd, handler);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -317,8 +301,8 @@ void save_pid() {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config << ")";
|
||||
|
@ -333,8 +317,8 @@ void reopen_log_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void exec_binary_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
LOG(NOTICE) << "Executing new binary";
|
||||
|
||||
|
@ -373,17 +357,17 @@ void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
auto envp = util::make_unique<char *[]>(envlen + 3 + 1);
|
||||
size_t envidx = 0;
|
||||
|
||||
auto evlistener4 = listener_handler->get_evlistener4();
|
||||
if (evlistener4) {
|
||||
auto acceptor4 = listener_handler->get_acceptor4();
|
||||
if (acceptor4) {
|
||||
std::string fd4 = ENV_LISTENER4_FD "=";
|
||||
fd4 += util::utos(evconnlistener_get_fd(evlistener4));
|
||||
fd4 += util::utos(acceptor4->get_fd());
|
||||
envp[envidx++] = strdup(fd4.c_str());
|
||||
}
|
||||
|
||||
auto evlistener6 = listener_handler->get_evlistener6();
|
||||
if (evlistener6) {
|
||||
auto acceptor6 = listener_handler->get_acceptor6();
|
||||
if (acceptor6) {
|
||||
std::string fd6 = ENV_LISTENER6_FD "=";
|
||||
fd6 += util::utos(evconnlistener_get_fd(evlistener6));
|
||||
fd6 += util::utos(acceptor6->get_fd());
|
||||
envp[envidx++] = strdup(fd6.c_str());
|
||||
}
|
||||
|
||||
|
@ -423,14 +407,15 @@ void exec_binary_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
|
||||
int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
LOG(NOTICE) << "Graceful shutdown signal received";
|
||||
|
||||
worker_config->graceful_shutdown = true;
|
||||
|
||||
listener_handler->disable_evlistener();
|
||||
listener_handler->disable_acceptor();
|
||||
|
||||
// After disabling accepting new connection, disptach incoming
|
||||
// connection in backlog.
|
||||
|
@ -438,6 +423,10 @@ void graceful_shutdown_signal_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
listener_handler->accept_pending_connection();
|
||||
|
||||
listener_handler->graceful_shutdown_worker();
|
||||
|
||||
// We have accepted all pending connections. Shutdown main event
|
||||
// loop.
|
||||
ev_break(loop);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -449,8 +438,8 @@ std::unique_ptr<std::string> generate_time() {
|
|||
} // namespace
|
||||
|
||||
namespace {
|
||||
void refresh_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(w->data);
|
||||
auto worker_stat = listener_handler->get_worker_stat();
|
||||
|
||||
mod_config()->cached_time = generate_time();
|
||||
|
@ -458,20 +447,14 @@ void refresh_cb(evutil_socket_t sig, short events, void *arg) {
|
|||
// wait for event notification to workers to finish.
|
||||
if (get_config()->num_worker == 1 && worker_config->graceful_shutdown &&
|
||||
(!worker_stat || worker_stat->num_connections == 0)) {
|
||||
event_base_loopbreak(listener_handler->get_evbase());
|
||||
ev_break(loop);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int event_loop() {
|
||||
int rv;
|
||||
|
||||
auto evbase = event_base_new();
|
||||
if (!evbase) {
|
||||
LOG(FATAL) << "event_base_new() failed";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
auto loop = EV_DEFAULT;
|
||||
SSL_CTX *sv_ssl_ctx, *cl_ssl_ctx;
|
||||
|
||||
if (get_config()->client_mode) {
|
||||
|
@ -487,7 +470,8 @@ int event_loop() {
|
|||
: nullptr;
|
||||
}
|
||||
|
||||
auto listener_handler = new ListenHandler(evbase, sv_ssl_ctx, cl_ssl_ctx);
|
||||
auto listener_handler =
|
||||
util::make_unique<ListenHandler>(loop, sv_ssl_ctx, cl_ssl_ctx);
|
||||
if (get_config()->daemon) {
|
||||
if (daemon(0, 0) == -1) {
|
||||
auto error = errno;
|
||||
|
@ -503,22 +487,23 @@ int event_loop() {
|
|||
save_pid();
|
||||
}
|
||||
|
||||
auto evlistener6 = create_evlistener(listener_handler, AF_INET6);
|
||||
auto evlistener4 = create_evlistener(listener_handler, AF_INET);
|
||||
if (!evlistener6 && !evlistener4) {
|
||||
auto acceptor6 = create_acceptor(listener_handler.get(), AF_INET6);
|
||||
auto acceptor4 = create_acceptor(listener_handler.get(), AF_INET);
|
||||
if (!acceptor6 && !acceptor4) {
|
||||
LOG(FATAL) << "Failed to listen on address " << get_config()->host.get()
|
||||
<< ", port " << get_config()->port;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
listener_handler->set_evlistener4(evlistener4);
|
||||
listener_handler->set_evlistener6(evlistener6);
|
||||
listener_handler->set_acceptor4(std::move(acceptor4));
|
||||
listener_handler->set_acceptor6(std::move(acceptor6));
|
||||
|
||||
// ListenHandler loads private key, and we listen on a priveleged port.
|
||||
// After that, we drop the root privileges if needed.
|
||||
drop_privileges();
|
||||
|
||||
#ifndef NOTHREADS
|
||||
int rv;
|
||||
sigset_t signals;
|
||||
sigemptyset(&signals);
|
||||
sigaddset(&signals, REOPEN_LOG_SIGNAL);
|
||||
|
@ -545,83 +530,35 @@ int event_loop() {
|
|||
}
|
||||
#endif // !NOTHREADS
|
||||
|
||||
auto reopen_log_signal_event = evsignal_new(
|
||||
evbase, REOPEN_LOG_SIGNAL, reopen_log_signal_cb, listener_handler);
|
||||
ev_signal reopen_log_sig;
|
||||
ev_signal_init(&reopen_log_sig, reopen_log_signal_cb, REOPEN_LOG_SIGNAL);
|
||||
reopen_log_sig.data = listener_handler.get();
|
||||
ev_signal_start(loop, &reopen_log_sig);
|
||||
|
||||
if (!reopen_log_signal_event) {
|
||||
LOG(ERROR) << "evsignal_new failed";
|
||||
} else {
|
||||
rv = event_add(reopen_log_signal_event, nullptr);
|
||||
if (rv < 0) {
|
||||
LOG(ERROR) << "event_add for reopen_log_signal_event failed";
|
||||
}
|
||||
}
|
||||
ev_signal exec_bin_sig;
|
||||
ev_signal_init(&exec_bin_sig, exec_binary_signal_cb, EXEC_BINARY_SIGNAL);
|
||||
exec_bin_sig.data = listener_handler.get();
|
||||
ev_signal_start(loop, &exec_bin_sig);
|
||||
|
||||
auto exec_binary_signal_event = evsignal_new(
|
||||
evbase, EXEC_BINARY_SIGNAL, exec_binary_signal_cb, listener_handler);
|
||||
rv = event_add(exec_binary_signal_event, nullptr);
|
||||
ev_signal graceful_shutdown_sig;
|
||||
ev_signal_init(&graceful_shutdown_sig, graceful_shutdown_signal_cb,
|
||||
GRACEFUL_SHUTDOWN_SIGNAL);
|
||||
graceful_shutdown_sig.data = listener_handler.get();
|
||||
ev_signal_start(loop, &graceful_shutdown_sig);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(FATAL) << "event_add for exec_binary_signal_event failed";
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto graceful_shutdown_signal_event =
|
||||
evsignal_new(evbase, GRACEFUL_SHUTDOWN_SIGNAL,
|
||||
graceful_shutdown_signal_cb, listener_handler);
|
||||
|
||||
rv = event_add(graceful_shutdown_signal_event, nullptr);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(FATAL) << "event_add for graceful_shutdown_signal_event failed";
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto refresh_event =
|
||||
event_new(evbase, -1, EV_PERSIST, refresh_cb, listener_handler);
|
||||
|
||||
if (!refresh_event) {
|
||||
LOG(ERROR) << "event_new failed";
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
timeval refresh_timeout = {1, 0};
|
||||
rv = event_add(refresh_event, &refresh_timeout);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(ERROR) << "Adding refresh_event failed";
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
ev_timer refresh_timer;
|
||||
ev_timer_init(&refresh_timer, refresh_cb, 0., 1.);
|
||||
refresh_timer.data = listener_handler.get();
|
||||
ev_timer_again(loop, &refresh_timer);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Entering event loop";
|
||||
}
|
||||
event_base_loop(evbase, 0);
|
||||
|
||||
ev_run(loop, 0);
|
||||
|
||||
listener_handler->join_worker();
|
||||
|
||||
if (refresh_event) {
|
||||
event_free(refresh_event);
|
||||
}
|
||||
if (graceful_shutdown_signal_event) {
|
||||
event_free(graceful_shutdown_signal_event);
|
||||
}
|
||||
if (exec_binary_signal_event) {
|
||||
event_free(exec_binary_signal_event);
|
||||
}
|
||||
if (reopen_log_signal_event) {
|
||||
event_free(reopen_log_signal_event);
|
||||
}
|
||||
if (evlistener4) {
|
||||
evconnlistener_free(evlistener4);
|
||||
}
|
||||
if (evlistener6) {
|
||||
evconnlistener_free(evlistener6);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -673,26 +610,26 @@ void fill_default_config() {
|
|||
mod_config()->cert_file = nullptr;
|
||||
|
||||
// Read timeout for HTTP2 upstream connection
|
||||
mod_config()->http2_upstream_read_timeout = {180, 0};
|
||||
mod_config()->http2_upstream_read_timeout = 180.;
|
||||
|
||||
// Read timeout for non-HTTP2 upstream connection
|
||||
mod_config()->upstream_read_timeout = {180, 0};
|
||||
mod_config()->upstream_read_timeout = 180.;
|
||||
|
||||
// Write timeout for HTTP2/non-HTTP2 upstream connection
|
||||
mod_config()->upstream_write_timeout = {30, 0};
|
||||
mod_config()->upstream_write_timeout = 30.;
|
||||
|
||||
// Read/Write timeouts for downstream connection
|
||||
mod_config()->downstream_read_timeout = {180, 0};
|
||||
mod_config()->downstream_write_timeout = {30, 0};
|
||||
mod_config()->downstream_read_timeout = 180.;
|
||||
mod_config()->downstream_write_timeout = 30.;
|
||||
|
||||
// Read timeout for HTTP/2 stream
|
||||
mod_config()->stream_read_timeout = {0, 0};
|
||||
mod_config()->stream_read_timeout = 0.;
|
||||
|
||||
// Write timeout for HTTP/2 stream
|
||||
mod_config()->stream_write_timeout = {0, 0};
|
||||
mod_config()->stream_write_timeout = 0.;
|
||||
|
||||
// Timeout for pooled (idle) connections
|
||||
mod_config()->downstream_idle_read_timeout = {600, 0};
|
||||
mod_config()->downstream_idle_read_timeout = 600.;
|
||||
|
||||
// window bits for HTTP/2 and SPDY upstream/downstream connection
|
||||
// per stream. 2**16-1 = 64KiB-1, which is HTTP/2 default. Please
|
||||
|
@ -726,7 +663,7 @@ void fill_default_config() {
|
|||
mod_config()->conf_path = strcopy("/etc/nghttpx/nghttpx.conf");
|
||||
mod_config()->syslog_facility = LOG_DAEMON;
|
||||
// Default accept() backlog
|
||||
mod_config()->backlog = -1;
|
||||
mod_config()->backlog = SOMAXCONN;
|
||||
mod_config()->ciphers = nullptr;
|
||||
mod_config()->http2_proxy = false;
|
||||
mod_config()->http2_bridge = false;
|
||||
|
@ -746,16 +683,14 @@ void fill_default_config() {
|
|||
mod_config()->downstream_http_proxy_host = nullptr;
|
||||
mod_config()->downstream_http_proxy_port = 0;
|
||||
mod_config()->downstream_http_proxy_addrlen = 0;
|
||||
mod_config()->rate_limit_cfg = nullptr;
|
||||
mod_config()->read_rate = 0;
|
||||
mod_config()->read_burst = 1 << 30;
|
||||
mod_config()->read_burst = 0;
|
||||
mod_config()->write_rate = 0;
|
||||
mod_config()->write_burst = 0;
|
||||
mod_config()->worker_read_rate = 0;
|
||||
mod_config()->worker_read_burst = 0;
|
||||
mod_config()->worker_write_rate = 0;
|
||||
mod_config()->worker_write_burst = 0;
|
||||
mod_config()->worker_rate_limit_cfg = nullptr;
|
||||
mod_config()->verify_client = false;
|
||||
mod_config()->verify_client_cacert = nullptr;
|
||||
mod_config()->client_private_key_file = nullptr;
|
||||
|
@ -777,19 +712,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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2014 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef SHRPX_ACCEPT_HANDLER_H
|
||||
#define SHRPX_ACCEPT_HANDLER_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class ListenHandler;
|
||||
|
||||
class AcceptHandler {
|
||||
public:
|
||||
AcceptHandler(int fd, ListenHandler *h);
|
||||
~AcceptHandler();
|
||||
void accept_connection();
|
||||
void enable();
|
||||
void disable();
|
||||
int get_fd() const;
|
||||
|
||||
private:
|
||||
ev_io wev_;
|
||||
ListenHandler *conn_hnr_;
|
||||
int fd_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_ACCEPT_HANDLER_H
|
|
@ -42,161 +42,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -27,55 +27,35 @@
|
|||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const int INITIAL_SLEEP = 2;
|
||||
const ev_tstamp INITIAL_SLEEP = 2.;
|
||||
} // namespace
|
||||
|
||||
ConnectBlocker::ConnectBlocker() : timerev_(nullptr), sleep_(INITIAL_SLEEP) {}
|
||||
|
||||
ConnectBlocker::~ConnectBlocker() {
|
||||
if (timerev_) {
|
||||
event_free(timerev_);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void connect_blocker_cb(evutil_socket_t sig, short events, void *arg) {
|
||||
void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "unblock downstream connection";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int ConnectBlocker::init(event_base *evbase) {
|
||||
timerev_ = evtimer_new(evbase, connect_blocker_cb, this);
|
||||
|
||||
if (timerev_ == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
ConnectBlocker::ConnectBlocker(struct ev_loop *loop)
|
||||
: loop_(loop), sleep_(INITIAL_SLEEP) {
|
||||
ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
|
||||
}
|
||||
|
||||
bool ConnectBlocker::blocked() const {
|
||||
return evtimer_pending(timerev_, nullptr);
|
||||
}
|
||||
ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
|
||||
|
||||
bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
|
||||
|
||||
void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
|
||||
|
||||
void ConnectBlocker::on_failure() {
|
||||
int rv;
|
||||
|
||||
sleep_ = std::min(128, sleep_ * 2);
|
||||
sleep_ = std::min(128., sleep_ * 2);
|
||||
|
||||
LOG(WARN) << "connect failure, start sleeping " << sleep_;
|
||||
|
||||
timeval t = {sleep_, 0};
|
||||
|
||||
rv = evtimer_add(timerev_, &t);
|
||||
|
||||
if (rv == -1) {
|
||||
LOG(ERROR) << "evtimer_add for ConnectBlocker timerev_ failed";
|
||||
}
|
||||
ev_timer_set(&timer_, sleep_, 0.);
|
||||
ev_timer_start(loop_, &timer_);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -27,16 +27,15 @@
|
|||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class ConnectBlocker {
|
||||
public:
|
||||
ConnectBlocker();
|
||||
ConnectBlocker(struct ev_loop *loop);
|
||||
~ConnectBlocker();
|
||||
|
||||
int init(event_base *evbase);
|
||||
// Returns true if making connection is not allowed.
|
||||
bool blocked() const;
|
||||
// Call this function if connect operation succeeded. This will
|
||||
|
@ -47,8 +46,9 @@ public:
|
|||
void on_failure();
|
||||
|
||||
private:
|
||||
event *timerev_;
|
||||
int sleep_;
|
||||
ev_timer timer_;
|
||||
struct ev_loop *loop_;
|
||||
ev_tstamp sleep_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -38,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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,8 +55,6 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, size_t consumed);
|
||||
virtual void force_resume_read() {}
|
||||
|
||||
virtual bool get_output_buffer_full();
|
||||
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_timeout();
|
||||
|
@ -66,9 +64,6 @@ public:
|
|||
|
||||
int send();
|
||||
|
||||
int init_request_body_buf();
|
||||
evbuffer *get_request_body_buf() const;
|
||||
|
||||
void attach_stream_data(StreamData *sd);
|
||||
StreamData *detach_stream_data();
|
||||
|
||||
|
@ -77,7 +72,6 @@ public:
|
|||
|
||||
private:
|
||||
Http2Session *http2session_;
|
||||
evbuffer *request_body_buf_;
|
||||
StreamData *sd_;
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,13 +32,16 @@
|
|||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "ringbuf.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class Http2DownstreamConnection;
|
||||
|
@ -49,10 +52,10 @@ struct StreamData {
|
|||
|
||||
class Http2Session {
|
||||
public:
|
||||
Http2Session(event_base *evbase, SSL_CTX *ssl_ctx);
|
||||
~Http2Session();
|
||||
typedef RingBuf<65536> Buf;
|
||||
|
||||
int init_notification();
|
||||
Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx);
|
||||
~Http2Session();
|
||||
|
||||
int check_cert();
|
||||
|
||||
|
@ -84,32 +87,45 @@ public:
|
|||
|
||||
int on_connect();
|
||||
|
||||
int do_read();
|
||||
int do_write();
|
||||
|
||||
int on_read();
|
||||
int on_write();
|
||||
int send();
|
||||
|
||||
int on_read_proxy();
|
||||
int connected();
|
||||
int read_clear();
|
||||
int write_clear();
|
||||
int tls_handshake();
|
||||
int read_tls();
|
||||
int write_tls();
|
||||
|
||||
void clear_notify();
|
||||
void notify();
|
||||
int downstream_read_proxy();
|
||||
int downstream_connect_proxy();
|
||||
|
||||
bufferevent *get_bev() const;
|
||||
void unwrap_free_bev();
|
||||
int downstream_read();
|
||||
int downstream_write();
|
||||
|
||||
int noop();
|
||||
|
||||
void signal_write();
|
||||
void clear_write_request();
|
||||
bool write_requested() const;
|
||||
|
||||
struct ev_loop *get_loop() const;
|
||||
|
||||
ev_io *get_wev();
|
||||
|
||||
int get_state() const;
|
||||
void set_state(int state);
|
||||
|
||||
int start_settings_timer();
|
||||
void start_settings_timer();
|
||||
void stop_settings_timer();
|
||||
|
||||
size_t get_outbuf_length() const;
|
||||
|
||||
SSL *get_ssl() const;
|
||||
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
|
||||
void reset_timeouts();
|
||||
|
||||
// Returns true if request can be issued on downstream connection.
|
||||
bool can_push_request() const;
|
||||
// Initiates the connection checking if downstream connection has
|
||||
|
@ -117,13 +133,15 @@ public:
|
|||
void start_checking_connection();
|
||||
// Resets connection check timer. After timeout, we require
|
||||
// connection checking.
|
||||
int reset_connection_check_timer();
|
||||
void reset_connection_check_timer();
|
||||
// Signals that connection is alive. Internally
|
||||
// reset_connection_check_timer() is called.
|
||||
int connection_alive();
|
||||
void connection_alive();
|
||||
// Change connection check state.
|
||||
void set_connection_check_state(int state);
|
||||
|
||||
bool should_hard_fail() const;
|
||||
|
||||
enum {
|
||||
// Disconnected
|
||||
DISCONNECTED,
|
||||
|
@ -136,7 +154,9 @@ public:
|
|||
// Connecting to downstream and/or performing SSL/TLS handshake
|
||||
CONNECTING,
|
||||
// Connected to downstream
|
||||
CONNECTED
|
||||
CONNECTED,
|
||||
// Connection is started to fail
|
||||
CONNECT_FAILING,
|
||||
};
|
||||
|
||||
static const size_t OUTBUF_MAX_THRES = 64 * 1024;
|
||||
|
@ -151,20 +171,26 @@ public:
|
|||
};
|
||||
|
||||
private:
|
||||
ev_io wev_;
|
||||
ev_io rev_;
|
||||
ev_timer wt_;
|
||||
ev_timer rt_;
|
||||
ev_timer settings_timer_;
|
||||
ev_timer connchk_timer_;
|
||||
ev_prepare wrsched_prep_;
|
||||
std::set<Http2DownstreamConnection *> dconns_;
|
||||
std::set<StreamData *> streams_;
|
||||
std::function<int(Http2Session &)> read_, write_;
|
||||
std::function<int(Http2Session &)> on_read_, on_write_;
|
||||
// Used to parse the response from HTTP proxy
|
||||
std::unique_ptr<http_parser> proxy_htp_;
|
||||
event_base *evbase_;
|
||||
struct ev_loop *loop_;
|
||||
// NULL if no TLS is configured
|
||||
SSL_CTX *ssl_ctx_;
|
||||
SSL *ssl_;
|
||||
nghttp2_session *session_;
|
||||
bufferevent *bev_;
|
||||
bufferevent *wrbev_;
|
||||
bufferevent *rdbev_;
|
||||
event *settings_timerev_;
|
||||
event *connection_check_timerev_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
// fd_ is used for proxy connection and no TLS connection. For
|
||||
// direct or TLS connection, it may be -1 even after connection is
|
||||
// established. Use bufferevent_getfd(bev_) to get file descriptor
|
||||
|
@ -172,8 +198,10 @@ private:
|
|||
int fd_;
|
||||
int state_;
|
||||
int connection_check_state_;
|
||||
bool notified_;
|
||||
bool flow_control_;
|
||||
bool write_requested_;
|
||||
Buf wb_;
|
||||
Buf rb_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -45,10 +45,6 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
||||
uint32_t error_code, void *user_data) {
|
||||
|
@ -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
|
||||
|
|
|
@ -29,11 +29,15 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_downstream_queue.h"
|
||||
#include "libevent_util.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -46,16 +50,16 @@ public:
|
|||
virtual ~Http2Upstream();
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_event();
|
||||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
int send();
|
||||
int perform_send();
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||
|
||||
void add_pending_downstream(std::unique_ptr<Downstream> downstream);
|
||||
void remove_downstream(Downstream *downstream);
|
||||
Downstream *find_downstream(int32_t stream_id);
|
||||
|
@ -78,14 +82,14 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
bool get_flow_control() const;
|
||||
// Perform HTTP/2 upgrade from |upstream|. On success, this object
|
||||
// takes ownership of the |upstream|. This function returns 0 if it
|
||||
// succeeds, or -1.
|
||||
int upgrade_upstream(HttpsUpstream *upstream);
|
||||
int start_settings_timer();
|
||||
void start_settings_timer();
|
||||
void stop_settings_timer();
|
||||
int consume(int32_t stream_id, size_t len);
|
||||
void log_response_headers(Downstream *downstream,
|
||||
|
@ -95,15 +99,16 @@ public:
|
|||
|
||||
void set_deferred(bool f);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
DownstreamQueue downstream_queue_;
|
||||
// must be put before downstream_queue_
|
||||
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
||||
MemchunkPool4K mcpool_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ev_timer settings_timer_;
|
||||
ClientHandler *handler_;
|
||||
nghttp2_session *session_;
|
||||
event *settings_timerev_;
|
||||
event *write_notifyev_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
bool flow_control_;
|
||||
bool deferred_;
|
||||
};
|
||||
|
|
|
@ -36,25 +36,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
#include "http-parser/http_parser.h"
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -49,9 +52,12 @@ public:
|
|||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||
|
||||
void attach_downstream(std::unique_ptr<Downstream> downstream);
|
||||
void delete_downstream();
|
||||
Downstream *get_downstream() const;
|
||||
|
@ -70,7 +76,7 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
void reset_current_header_length();
|
||||
void log_response_headers(const std::string &hdrs) const;
|
||||
|
@ -79,6 +85,8 @@ private:
|
|||
ClientHandler *handler_;
|
||||
http_parser htp_;
|
||||
size_t current_header_length_;
|
||||
// must be put before downstream_
|
||||
MemchunkPool4K mcpool_;
|
||||
std::unique_ptr<Downstream> downstream_;
|
||||
IOControl ioctrl_;
|
||||
};
|
||||
|
|
|
@ -26,42 +26,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -29,10 +29,7 @@
|
|||
#include <cerrno>
|
||||
#include <thread>
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_thread_event_receiver.h"
|
||||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
|
@ -40,16 +37,16 @@
|
|||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_connect_blocker.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "shrpx_accept_handler.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(arg);
|
||||
void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
||||
auto h = static_cast<ListenHandler *>(w->data);
|
||||
|
||||
// If we are in graceful shutdown period, we must not enable
|
||||
// evlisteners again.
|
||||
|
@ -57,23 +54,24 @@ void evlistener_disable_cb(evutil_socket_t fd, short events, void *arg) {
|
|||
return;
|
||||
}
|
||||
|
||||
listener_handler->enable_evlistener();
|
||||
h->enable_acceptor();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ListenHandler::ListenHandler(event_base *evbase, SSL_CTX *sv_ssl_ctx,
|
||||
ListenHandler::ListenHandler(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx,
|
||||
SSL_CTX *cl_ssl_ctx)
|
||||
: evbase_(evbase), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||
rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||
evbase, get_config()->worker_rate_limit_cfg)),
|
||||
evlistener4_(nullptr), evlistener6_(nullptr),
|
||||
evlistener_disable_timerev_(
|
||||
evtimer_new(evbase, evlistener_disable_cb, this)),
|
||||
worker_stat_(util::make_unique<WorkerStat>()), num_worker_shutdown_(0),
|
||||
worker_round_robin_cnt_(0) {}
|
||||
: loop_(loop), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||
// rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||
// evbase, get_config()->worker_rate_limit_cfg)),
|
||||
worker_stat_(util::make_unique<WorkerStat>()),
|
||||
worker_round_robin_cnt_(0) {
|
||||
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||
disable_acceptor_timer_.data = this;
|
||||
}
|
||||
|
||||
ListenHandler::~ListenHandler() {
|
||||
bufferevent_rate_limit_group_free(rate_limit_group_);
|
||||
// bufferevent_rate_limit_group_free(rate_limit_group_);
|
||||
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
||||
}
|
||||
|
||||
void ListenHandler::worker_reopen_log_files() {
|
||||
|
@ -82,68 +80,19 @@ void ListenHandler::worker_reopen_log_files() {
|
|||
memset(&wev, 0, sizeof(wev));
|
||||
wev.type = REOPEN_LOG;
|
||||
|
||||
for (auto &info : workers_) {
|
||||
bufferevent_write(info->bev, &wev, sizeof(wev));
|
||||
for (auto &worker : workers_) {
|
||||
worker->send(wev);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NOTHREADS
|
||||
namespace {
|
||||
void worker_writecb(bufferevent *bev, void *ptr) {
|
||||
auto listener_handler = static_cast<ListenHandler *>(ptr);
|
||||
auto output = bufferevent_get_output(bev);
|
||||
|
||||
if (!worker_config->graceful_shutdown || evbuffer_get_length(output) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If graceful_shutdown is true and nothing left to send, we sent
|
||||
// graceful shutdown event to worker successfully. The worker is
|
||||
// now doing shutdown.
|
||||
listener_handler->notify_worker_shutdown();
|
||||
|
||||
// Disable bev so that this won' be called accidentally in the
|
||||
// future.
|
||||
util::bev_disable_unless(bev, EV_READ | EV_WRITE);
|
||||
}
|
||||
} // namespace
|
||||
#endif // NOTHREADS
|
||||
|
||||
void ListenHandler::create_worker_thread(size_t num) {
|
||||
#ifndef NOTHREADS
|
||||
workers_.resize(0);
|
||||
assert(workers_.size() == 0);
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
int rv;
|
||||
auto info = util::make_unique<WorkerInfo>();
|
||||
rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
|
||||
info->sv);
|
||||
if (rv == -1) {
|
||||
auto error = errno;
|
||||
LLOG(ERROR, this) << "socketpair() failed: errno=" << error;
|
||||
continue;
|
||||
}
|
||||
|
||||
info->sv_ssl_ctx = sv_ssl_ctx_;
|
||||
info->cl_ssl_ctx = cl_ssl_ctx_;
|
||||
|
||||
info->fut =
|
||||
std::async(std::launch::async, start_threaded_worker, info.get());
|
||||
|
||||
auto bev =
|
||||
bufferevent_socket_new(evbase_, info->sv[0], BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
LLOG(ERROR, this) << "bufferevent_socket_new() failed";
|
||||
for (size_t j = 0; j < 2; ++j) {
|
||||
close(info->sv[j]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bufferevent_setcb(bev, nullptr, worker_writecb, nullptr, this);
|
||||
|
||||
info->bev = bev;
|
||||
|
||||
workers_.push_back(std::move(info));
|
||||
auto worker = util::make_unique<Worker>(sv_ssl_ctx_, cl_ssl_ctx_);
|
||||
worker->run();
|
||||
workers_.push_back(std::move(worker));
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
|
||||
|
@ -162,7 +111,7 @@ void ListenHandler::join_worker() {
|
|||
}
|
||||
|
||||
for (auto &worker : workers_) {
|
||||
worker->fut.get();
|
||||
worker->wait();
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Thread #" << n << " joined";
|
||||
}
|
||||
|
@ -185,29 +134,22 @@ void ListenHandler::graceful_shutdown_worker() {
|
|||
LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
|
||||
}
|
||||
|
||||
auto output = bufferevent_get_output(worker->bev);
|
||||
|
||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
}
|
||||
worker->send(wev);
|
||||
}
|
||||
}
|
||||
|
||||
int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
||||
int addrlen) {
|
||||
int ListenHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
||||
}
|
||||
|
||||
evutil_make_socket_closeonexec(fd);
|
||||
|
||||
if (get_config()->num_worker == 1) {
|
||||
|
||||
if (worker_stat_->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "Too many connections >="
|
||||
LLOG(INFO, this) << "Too many connections >="
|
||||
<< get_config()->worker_frontend_connections;
|
||||
}
|
||||
|
||||
|
@ -215,9 +157,8 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
return -1;
|
||||
}
|
||||
|
||||
auto client =
|
||||
ssl::accept_connection(evbase_, rate_limit_group_, sv_ssl_ctx_, fd,
|
||||
addr, addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
auto client = ssl::accept_connection(loop_, sv_ssl_ctx_, fd, addr, addrlen,
|
||||
worker_stat_.get(), &dconn_pool_);
|
||||
if (!client) {
|
||||
LLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
|
||||
|
@ -230,6 +171,7 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t idx = worker_round_robin_cnt_ % workers_.size();
|
||||
++worker_round_robin_cnt_;
|
||||
WorkerEvent wev;
|
||||
|
@ -238,140 +180,77 @@ int ListenHandler::accept_connection(evutil_socket_t fd, sockaddr *addr,
|
|||
wev.client_fd = fd;
|
||||
memcpy(&wev.client_addr, addr, addrlen);
|
||||
wev.client_addrlen = addrlen;
|
||||
auto output = bufferevent_get_output(workers_[idx]->bev);
|
||||
if (evbuffer_add(output, &wev, sizeof(wev)) != 0) {
|
||||
LLOG(FATAL, this) << "evbuffer_add() failed";
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
workers_[idx]->send(wev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
event_base *ListenHandler::get_evbase() const { return evbase_; }
|
||||
|
||||
int ListenHandler::create_http2_session() {
|
||||
int rv;
|
||||
http2session_ = util::make_unique<Http2Session>(evbase_, cl_ssl_ctx_);
|
||||
rv = http2session_->init_notification();
|
||||
return rv;
|
||||
struct ev_loop *ListenHandler::get_loop() const {
|
||||
return loop_;
|
||||
}
|
||||
|
||||
int ListenHandler::create_http1_connect_blocker() {
|
||||
int rv;
|
||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>();
|
||||
void ListenHandler::create_http2_session() {
|
||||
http2session_ = util::make_unique<Http2Session>(loop_, cl_ssl_ctx_);
|
||||
}
|
||||
|
||||
rv = http1_connect_blocker_->init(evbase_);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
void ListenHandler::create_http1_connect_blocker() {
|
||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>(loop_);
|
||||
}
|
||||
|
||||
const WorkerStat *ListenHandler::get_worker_stat() const {
|
||||
return worker_stat_.get();
|
||||
}
|
||||
|
||||
void ListenHandler::set_evlistener4(evconnlistener *evlistener4) {
|
||||
evlistener4_ = evlistener4;
|
||||
void ListenHandler::set_acceptor4(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptor4_ = std::move(h);
|
||||
}
|
||||
|
||||
evconnlistener *ListenHandler::get_evlistener4() const { return evlistener4_; }
|
||||
AcceptHandler *ListenHandler::get_acceptor4() const { return acceptor4_.get(); }
|
||||
|
||||
void ListenHandler::set_evlistener6(evconnlistener *evlistener6) {
|
||||
evlistener6_ = evlistener6;
|
||||
void ListenHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptor6_ = std::move(h);
|
||||
}
|
||||
|
||||
evconnlistener *ListenHandler::get_evlistener6() const { return evlistener6_; }
|
||||
AcceptHandler *ListenHandler::get_acceptor6() const { return acceptor6_.get(); }
|
||||
|
||||
void ListenHandler::enable_evlistener() {
|
||||
if (evlistener4_) {
|
||||
evconnlistener_enable(evlistener4_);
|
||||
void ListenHandler::enable_acceptor() {
|
||||
if (acceptor4_) {
|
||||
acceptor4_->enable();
|
||||
}
|
||||
|
||||
if (evlistener6_) {
|
||||
evconnlistener_enable(evlistener6_);
|
||||
if (acceptor6_) {
|
||||
acceptor6_->enable();
|
||||
}
|
||||
}
|
||||
|
||||
void ListenHandler::disable_evlistener() {
|
||||
if (evlistener4_) {
|
||||
evconnlistener_disable(evlistener4_);
|
||||
void ListenHandler::disable_acceptor() {
|
||||
if (acceptor4_) {
|
||||
acceptor4_->disable();
|
||||
}
|
||||
|
||||
if (evlistener6_) {
|
||||
evconnlistener_disable(evlistener6_);
|
||||
if (acceptor6_) {
|
||||
acceptor6_->disable();
|
||||
}
|
||||
}
|
||||
|
||||
void ListenHandler::disable_evlistener_temporary(const timeval *timeout) {
|
||||
int rv;
|
||||
|
||||
if (timeout->tv_sec == 0 ||
|
||||
evtimer_pending(evlistener_disable_timerev_, nullptr)) {
|
||||
void ListenHandler::disable_acceptor_temporary(ev_tstamp t) {
|
||||
if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
disable_evlistener();
|
||||
disable_acceptor();
|
||||
|
||||
rv = evtimer_add(evlistener_disable_timerev_, timeout);
|
||||
|
||||
if (rv < 0) {
|
||||
LOG(ERROR) << "evtimer_add for evlistener_disable_timerev_ failed";
|
||||
}
|
||||
ev_timer_set(&disable_acceptor_timer_, t, 0.);
|
||||
ev_timer_start(loop_, &disable_acceptor_timer_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void perform_accept_pending_connection(ListenHandler *listener_handler,
|
||||
evconnlistener *listener) {
|
||||
if (!listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto server_fd = evconnlistener_get_fd(listener);
|
||||
|
||||
for (;;) {
|
||||
sockaddr_union sockaddr;
|
||||
socklen_t addrlen = sizeof(sockaddr);
|
||||
|
||||
auto fd = accept(server_fd, &sockaddr.sa, &addrlen);
|
||||
|
||||
if (fd == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
case ENETDOWN:
|
||||
case EPROTO:
|
||||
case ENOPROTOOPT:
|
||||
case EHOSTDOWN:
|
||||
#ifdef ENONET
|
||||
case ENONET:
|
||||
#endif // ENONET
|
||||
case EHOSTUNREACH:
|
||||
case EOPNOTSUPP:
|
||||
case ENETUNREACH:
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
evutil_make_socket_nonblocking(fd);
|
||||
|
||||
listener_handler->accept_connection(fd, &sockaddr.sa, addrlen);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ListenHandler::accept_pending_connection() {
|
||||
perform_accept_pending_connection(this, evlistener4_);
|
||||
perform_accept_pending_connection(this, evlistener6_);
|
||||
}
|
||||
|
||||
void ListenHandler::notify_worker_shutdown() {
|
||||
if (++num_worker_shutdown_ == workers_.size()) {
|
||||
event_base_loopbreak(evbase_);
|
||||
if (acceptor4_) {
|
||||
acceptor4_->accept_connection();
|
||||
}
|
||||
if (acceptor6_) {
|
||||
acceptor6_->accept_connection();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,61 +32,48 @@
|
|||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // NOTHREADS
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/listener.h>
|
||||
#include <ev.h>
|
||||
|
||||
#include "shrpx_downstream_connection_pool.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
struct WorkerInfo {
|
||||
#ifndef NOTHREADS
|
||||
std::future<void> fut;
|
||||
#endif // NOTHREADS
|
||||
SSL_CTX *sv_ssl_ctx;
|
||||
SSL_CTX *cl_ssl_ctx;
|
||||
bufferevent *bev;
|
||||
int sv[2];
|
||||
};
|
||||
|
||||
class Http2Session;
|
||||
class ConnectBlocker;
|
||||
class AcceptHandler;
|
||||
class Worker;
|
||||
struct WorkerStat;
|
||||
|
||||
// TODO should be renamed as ConnectionHandler
|
||||
class ListenHandler {
|
||||
public:
|
||||
ListenHandler(event_base *evbase, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
||||
ListenHandler(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
||||
~ListenHandler();
|
||||
int accept_connection(evutil_socket_t fd, sockaddr *addr, int addrlen);
|
||||
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
||||
void create_worker_thread(size_t num);
|
||||
void worker_reopen_log_files();
|
||||
event_base *get_evbase() const;
|
||||
int create_http2_session();
|
||||
int create_http1_connect_blocker();
|
||||
struct ev_loop *get_loop() const;
|
||||
void create_http2_session();
|
||||
void create_http1_connect_blocker();
|
||||
const WorkerStat *get_worker_stat() const;
|
||||
void set_evlistener4(evconnlistener *evlistener4);
|
||||
evconnlistener *get_evlistener4() const;
|
||||
void set_evlistener6(evconnlistener *evlistener6);
|
||||
evconnlistener *get_evlistener6() const;
|
||||
void enable_evlistener();
|
||||
void disable_evlistener();
|
||||
void disable_evlistener_temporary(const timeval *timeout);
|
||||
void set_acceptor4(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor4() const;
|
||||
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor6() const;
|
||||
void enable_acceptor();
|
||||
void disable_acceptor();
|
||||
void disable_acceptor_temporary(ev_tstamp t);
|
||||
void accept_pending_connection();
|
||||
void graceful_shutdown_worker();
|
||||
void join_worker();
|
||||
void notify_worker_shutdown();
|
||||
|
||||
private:
|
||||
DownstreamConnectionPool dconn_pool_;
|
||||
std::vector<std::unique_ptr<WorkerInfo>> workers_;
|
||||
event_base *evbase_;
|
||||
std::vector<std::unique_ptr<Worker>> workers_;
|
||||
struct ev_loop *loop_;
|
||||
// The frontend server SSL_CTX
|
||||
SSL_CTX *sv_ssl_ctx_;
|
||||
// The backend server SSL_CTX
|
||||
|
@ -95,12 +82,11 @@ private:
|
|||
// multi-threaded case, see shrpx_worker.cc.
|
||||
std::unique_ptr<Http2Session> http2session_;
|
||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
||||
bufferevent_rate_limit_group *rate_limit_group_;
|
||||
evconnlistener *evlistener4_;
|
||||
evconnlistener *evlistener6_;
|
||||
event *evlistener_disable_timerev_;
|
||||
// bufferevent_rate_limit_group *rate_limit_group_;
|
||||
std::unique_ptr<AcceptHandler> acceptor4_;
|
||||
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||
ev_timer disable_acceptor_timer_;
|
||||
std::unique_ptr<WorkerStat> worker_stat_;
|
||||
size_t num_worker_shutdown_;
|
||||
unsigned int worker_round_robin_cnt_;
|
||||
};
|
||||
|
||||
|
|
|
@ -48,10 +48,9 @@ class Downstream;
|
|||
#define LLOG(SEVERITY, LISTEN) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[LISTEN:" << LISTEN << "] ")
|
||||
|
||||
// ThreadEventReceiver log
|
||||
#define TLOG(SEVERITY, THREAD_RECV) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[THREAD_RECV:" << THREAD_RECV << "]" \
|
||||
" ")
|
||||
// Worker log
|
||||
#define WLOG(SEVERITY, WORKER) \
|
||||
(Log(SEVERITY, __FILE__, __LINE__) << "[WORKER:" << WORKER << "] ")
|
||||
|
||||
// ClientHandler log
|
||||
#define CLOG(SEVERITY, CLIENT_HANDLER) \
|
||||
|
|
|
@ -0,0 +1,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
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2015 Tatsuhiro Tsujikawa
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef SHRPX_RATE_LIMIT_H
|
||||
#define SHRPX_RATE_LIMIT_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class RateLimit {
|
||||
public:
|
||||
RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst);
|
||||
~RateLimit();
|
||||
size_t avail() const;
|
||||
void drain(size_t n);
|
||||
void regen();
|
||||
void startw();
|
||||
void stopw();
|
||||
private:
|
||||
ev_io *w_;
|
||||
ev_timer t_;
|
||||
struct ev_loop *loop_;
|
||||
size_t rate_;
|
||||
size_t burst_;
|
||||
size_t avail_;
|
||||
bool startw_req_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_RATE_LIMIT_H
|
|
@ -44,48 +44,45 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
const size_t OUTBUF_MAX_THRES = 16 * 1024;
|
||||
const size_t INBUF_MAX_THRES = 16 * 1024;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
|
||||
int flags, void *user_data) {
|
||||
int rv;
|
||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto wb = handler->get_wb();
|
||||
|
||||
// Check buffer length and return WOULDBLOCK if it is large enough.
|
||||
if (handler->get_outbuf_length() + upstream->sendbuf.get_buflen() >=
|
||||
OUTBUF_MAX_THRES) {
|
||||
if (wb->wleft() == 0) {
|
||||
return SPDYLAY_ERR_WOULDBLOCK;
|
||||
}
|
||||
|
||||
rv = upstream->sendbuf.add(data, len);
|
||||
if (rv != 0) {
|
||||
ULOG(FATAL, upstream) << "evbuffer_add() failed";
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
return len;
|
||||
auto nread = wb->write(data, len);
|
||||
|
||||
handler->update_warmup_writelen(nread);
|
||||
|
||||
return nread;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t recv_callback(spdylay_session *session, uint8_t *data, size_t len,
|
||||
ssize_t recv_callback(spdylay_session *session, uint8_t *buf, size_t len,
|
||||
int flags, void *user_data) {
|
||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto bev = handler->get_bev();
|
||||
auto input = bufferevent_get_input(bev);
|
||||
int nread = evbuffer_remove(input, data, len);
|
||||
if (nread == -1) {
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
} else if (nread == 0) {
|
||||
auto rb = handler->get_rb();
|
||||
const void *data;
|
||||
size_t nread;
|
||||
|
||||
std::tie(data, nread) = rb->get();
|
||||
if (nread == 0) {
|
||||
return SPDYLAY_ERR_WOULDBLOCK;
|
||||
} else {
|
||||
return nread;
|
||||
}
|
||||
|
||||
nread = std::min(nread, len);
|
||||
|
||||
memcpy(buf, data, nread);
|
||||
rb->drain(nread);
|
||||
|
||||
return nread;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -29,12 +29,14 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <spdylay/spdylay.h>
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
#include "shrpx_downstream_queue.h"
|
||||
#include "memchunk.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -46,15 +48,14 @@ public:
|
|||
virtual ~SpdyUpstream();
|
||||
virtual int on_read();
|
||||
virtual int on_write();
|
||||
virtual int on_event();
|
||||
virtual int on_timeout(Downstream *downstream);
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code);
|
||||
int send();
|
||||
virtual ClientHandler *get_client_handler() const;
|
||||
virtual bufferevent_data_cb get_downstream_readcb();
|
||||
virtual bufferevent_data_cb get_downstream_writecb();
|
||||
virtual bufferevent_event_cb get_downstream_eventcb();
|
||||
virtual int downstream_read(DownstreamConnection *dconn);
|
||||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||
Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
|
||||
void remove_downstream(Downstream *downstream);
|
||||
Downstream *find_downstream(int32_t stream_id);
|
||||
|
@ -76,7 +77,7 @@ public:
|
|||
virtual void on_handler_delete();
|
||||
virtual int on_downstream_reset();
|
||||
|
||||
virtual void reset_timeouts();
|
||||
virtual MemchunkPool4K *get_mcpool();
|
||||
|
||||
bool get_flow_control() const;
|
||||
|
||||
|
@ -85,9 +86,9 @@ public:
|
|||
void start_downstream(Downstream *downstream);
|
||||
void initiate_downstream(std::unique_ptr<Downstream> downstream);
|
||||
|
||||
nghttp2::util::EvbufferBuffer sendbuf;
|
||||
|
||||
private:
|
||||
// must be put before downstream_queue_
|
||||
MemchunkPool4K mcpool_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ClientHandler *handler_;
|
||||
spdylay_session *session_;
|
||||
|
|
|
@ -36,9 +36,6 @@
|
|||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#ifdef HAVE_SPDYLAY
|
||||
|
@ -450,9 +447,7 @@ SSL_CTX *create_ssl_client_context() {
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
ClientHandler *accept_connection(event_base *evbase,
|
||||
bufferevent_rate_limit_group *rate_limit_group,
|
||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
||||
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||
sockaddr *addr, int addrlen,
|
||||
WorkerStat *worker_stat,
|
||||
DownstreamConnectionPool *dconn_pool) {
|
||||
|
@ -474,7 +469,6 @@ ClientHandler *accept_connection(event_base *evbase,
|
|||
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
|
||||
}
|
||||
SSL *ssl = nullptr;
|
||||
bufferevent *bev;
|
||||
if (ssl_ctx) {
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl) {
|
||||
|
@ -490,21 +484,11 @@ ClientHandler *accept_connection(event_base *evbase,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
bev = bufferevent_openssl_socket_new(
|
||||
evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS);
|
||||
} else {
|
||||
bev = bufferevent_socket_new(evbase, fd, BEV_OPT_DEFER_CALLBACKS);
|
||||
}
|
||||
if (!bev) {
|
||||
LOG(ERROR) << "bufferevent_socket_new() failed";
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
}
|
||||
return nullptr;
|
||||
SSL_set_accept_state(ssl);
|
||||
}
|
||||
|
||||
return new ClientHandler(bev, rate_limit_group, fd, ssl, host, service,
|
||||
worker_stat, dconn_pool);
|
||||
return new ClientHandler(loop, fd, ssl, host, service, worker_stat,
|
||||
dconn_pool);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <event.h>
|
||||
#include <ev.h>
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -47,9 +47,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
|||
|
||||
SSL_CTX *create_ssl_client_context();
|
||||
|
||||
ClientHandler *accept_connection(event_base *evbase,
|
||||
bufferevent_rate_limit_group *rate_limit_group,
|
||||
SSL_CTX *ssl_ctx, evutil_socket_t fd,
|
||||
ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
|
||||
sockaddr *addr, int addrlen,
|
||||
WorkerStat *worker_stat,
|
||||
DownstreamConnectionPool *dconn_pool);
|
||||
|
|
|
@ -38,95 +38,93 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
ThreadEventReceiver::ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
||||
ThreadEventReceiver::ThreadEventReceiver(SSL_CTX *ssl_ctx,
|
||||
Http2Session *http2session,
|
||||
ConnectBlocker *http1_connect_blocker)
|
||||
: evbase_(evbase), ssl_ctx_(ssl_ctx), http2session_(http2session),
|
||||
: ssl_ctx_(ssl_ctx), http2session_(http2session),
|
||||
http1_connect_blocker_(http1_connect_blocker),
|
||||
rate_limit_group_(bufferevent_rate_limit_group_new(
|
||||
evbase_, get_config()->worker_rate_limit_cfg)),
|
||||
worker_stat_(util::make_unique<WorkerStat>()) {}
|
||||
|
||||
ThreadEventReceiver::~ThreadEventReceiver() {
|
||||
bufferevent_rate_limit_group_free(rate_limit_group_);
|
||||
}
|
||||
ThreadEventReceiver::~ThreadEventReceiver() {}
|
||||
|
||||
void ThreadEventReceiver::on_read(bufferevent *bev) {
|
||||
auto input = bufferevent_get_input(bev);
|
||||
while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
||||
WorkerEvent wev;
|
||||
int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
||||
if (nread == -1) {
|
||||
TLOG(FATAL, this) << "evbuffer_remove() failed";
|
||||
continue;
|
||||
}
|
||||
if (nread != sizeof(wev)) {
|
||||
TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
||||
<< sizeof(wev) << " Actual:" << nread;
|
||||
continue;
|
||||
}
|
||||
void ThreadEventReceiver::on_read() {
|
||||
// auto input = bufferevent_get_input(bev);
|
||||
// while (evbuffer_get_length(input) >= sizeof(WorkerEvent)) {
|
||||
// WorkerEvent wev;
|
||||
// int nread = evbuffer_remove(input, &wev, sizeof(wev));
|
||||
// if (nread == -1) {
|
||||
// TLOG(FATAL, this) << "evbuffer_remove() failed";
|
||||
// continue;
|
||||
// }
|
||||
// if (nread != sizeof(wev)) {
|
||||
// TLOG(FATAL, this) << "evbuffer_remove() removed fewer bytes. Expected:"
|
||||
// << sizeof(wev) << " Actual:" << nread;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (wev.type == REOPEN_LOG) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||
<< ")";
|
||||
}
|
||||
// if (wev.type == REOPEN_LOG) {
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||
// << ")";
|
||||
// }
|
||||
|
||||
reopen_log_files();
|
||||
// reopen_log_files();
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||
LOG(NOTICE) << "Graceful shutdown commencing";
|
||||
// if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||
// LOG(NOTICE) << "Graceful shutdown commencing";
|
||||
|
||||
worker_config->graceful_shutdown = true;
|
||||
// worker_config->graceful_shutdown = true;
|
||||
|
||||
if (worker_stat_->num_connections == 0) {
|
||||
event_base_loopbreak(evbase_);
|
||||
// if (worker_stat_->num_connections == 0) {
|
||||
// event_base_loopbreak(evbase_);
|
||||
|
||||
break;
|
||||
}
|
||||
// break;
|
||||
// }
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||
<< ", addrlen=" << wev.client_addrlen;
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||
// << ", addrlen=" << wev.client_addrlen;
|
||||
// }
|
||||
|
||||
if (worker_stat_->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
// if (worker_stat_->num_connections >=
|
||||
// get_config()->worker_frontend_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "Too many connections >= "
|
||||
<< get_config()->worker_frontend_connections;
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "Too many connections >= "
|
||||
// << get_config()->worker_frontend_connections;
|
||||
// }
|
||||
|
||||
close(wev.client_fd);
|
||||
// close(wev.client_fd);
|
||||
|
||||
continue;
|
||||
}
|
||||
// continue;
|
||||
// }
|
||||
|
||||
auto evbase = bufferevent_get_base(bev);
|
||||
auto client_handler = ssl::accept_connection(
|
||||
evbase, rate_limit_group_, ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
|
||||
wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
if (client_handler) {
|
||||
client_handler->set_http2_session(http2session_);
|
||||
client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
||||
// auto evbase = bufferevent_get_base(bev);
|
||||
// auto client_handler = ssl::accept_connection(
|
||||
// evbase, rate_limit_group_, ssl_ctx_, wev.client_fd,
|
||||
// &wev.client_addr.sa,
|
||||
// wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
// if (client_handler) {
|
||||
// client_handler->set_http2_session(http2session_);
|
||||
// client_handler->set_http1_connect_blocker(http1_connect_blocker_);
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created";
|
||||
}
|
||||
} else {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
TLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
}
|
||||
close(wev.client_fd);
|
||||
}
|
||||
}
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << "
|
||||
// created";
|
||||
// }
|
||||
// } else {
|
||||
// if (LOG_ENABLED(INFO)) {
|
||||
// TLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
// }
|
||||
// close(wev.client_fd);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -31,8 +31,6 @@
|
|||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_downstream_connection_pool.h"
|
||||
|
||||
|
@ -54,28 +52,26 @@ struct WorkerEvent {
|
|||
struct {
|
||||
sockaddr_union client_addr;
|
||||
size_t client_addrlen;
|
||||
evutil_socket_t client_fd;
|
||||
int client_fd;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class ThreadEventReceiver {
|
||||
public:
|
||||
ThreadEventReceiver(event_base *evbase, SSL_CTX *ssl_ctx,
|
||||
Http2Session *http2session,
|
||||
ThreadEventReceiver(SSL_CTX *ssl_ctx, Http2Session *http2session,
|
||||
ConnectBlocker *http1_connect_blocker);
|
||||
~ThreadEventReceiver();
|
||||
void on_read(bufferevent *bev);
|
||||
void on_read();
|
||||
|
||||
private:
|
||||
DownstreamConnectionPool dconn_pool_;
|
||||
event_base *evbase_;
|
||||
// event_base *evbase_;
|
||||
SSL_CTX *ssl_ctx_;
|
||||
// Shared HTTP2 session for each thread. NULL if not client
|
||||
// mode. Not deleted by this object.
|
||||
Http2Session *http2session_;
|
||||
ConnectBlocker *http1_connect_blocker_;
|
||||
bufferevent_rate_limit_group *rate_limit_group_;
|
||||
std::unique_ptr<WorkerStat> worker_stat_;
|
||||
};
|
||||
|
||||
|
|
|
@ -26,28 +26,29 @@
|
|||
#define SHRPX_UPSTREAM_H
|
||||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_io_control.h"
|
||||
#include "memchunk.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class ClientHandler;
|
||||
class Downstream;
|
||||
class DownstreamConnection;
|
||||
|
||||
class Upstream {
|
||||
public:
|
||||
virtual ~Upstream() {}
|
||||
virtual int on_read() = 0;
|
||||
virtual int on_write() = 0;
|
||||
virtual int on_event() = 0;
|
||||
virtual int on_timeout(Downstream *downstream) { return 0; };
|
||||
virtual int on_downstream_abort_request(Downstream *downstream,
|
||||
unsigned int status_code) = 0;
|
||||
virtual bufferevent_data_cb get_downstream_readcb() = 0;
|
||||
virtual bufferevent_data_cb get_downstream_writecb() = 0;
|
||||
virtual bufferevent_event_cb get_downstream_eventcb() = 0;
|
||||
virtual int downstream_read(DownstreamConnection *dconn) = 0;
|
||||
virtual int downstream_write(DownstreamConnection *dconn) = 0;
|
||||
virtual int downstream_eof(DownstreamConnection *dconn) = 0;
|
||||
virtual int downstream_error(DownstreamConnection *dconn, int events) = 0;
|
||||
virtual ClientHandler *get_client_handler() const = 0;
|
||||
|
||||
virtual int on_downstream_header_complete(Downstream *downstream) = 0;
|
||||
|
@ -64,7 +65,7 @@ public:
|
|||
virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||
size_t consumed) = 0;
|
||||
|
||||
virtual void reset_timeouts() = 0;
|
||||
virtual MemchunkPool4K *get_mcpool() = 0;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -25,96 +25,135 @@
|
|||
#include "shrpx_worker.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <event.h>
|
||||
#include <event2/bufferevent.h>
|
||||
|
||||
#include "shrpx_ssl.h"
|
||||
#include "shrpx_thread_event_receiver.h"
|
||||
#include "shrpx_log.h"
|
||||
#include "shrpx_client_handler.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_worker_config.h"
|
||||
#include "shrpx_connect_blocker.h"
|
||||
#include "util.h"
|
||||
#include "libevent_util.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
Worker::Worker(const WorkerInfo *info)
|
||||
: sv_ssl_ctx_(info->sv_ssl_ctx), cl_ssl_ctx_(info->cl_ssl_ctx),
|
||||
fd_(info->sv[1]) {}
|
||||
namespace {
|
||||
void eventcb(struct ev_loop *loop, ev_async *w, int revents) {
|
||||
auto worker = static_cast<Worker *>(w->data);
|
||||
worker->process_events();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Worker::Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx)
|
||||
: loop_(ev_loop_new(0)), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
|
||||
worker_stat_(util::make_unique<WorkerStat>()) {
|
||||
ev_async_init(&w_, eventcb);
|
||||
w_.data = this;
|
||||
ev_async_start(loop_, &w_);
|
||||
|
||||
if (get_config()->downstream_proto == PROTO_HTTP2) {
|
||||
http2session_ = util::make_unique<Http2Session>(loop_, cl_ssl_ctx_);
|
||||
} else {
|
||||
http1_connect_blocker_ = util::make_unique<ConnectBlocker>(loop_);
|
||||
}
|
||||
}
|
||||
|
||||
Worker::~Worker() {
|
||||
shutdown(fd_, SHUT_WR);
|
||||
close(fd_);
|
||||
ev_async_stop(loop_, &w_);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void readcb(bufferevent *bev, void *arg) {
|
||||
auto receiver = static_cast<ThreadEventReceiver *>(arg);
|
||||
receiver->on_read(bev);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void eventcb(bufferevent *bev, short events, void *arg) {
|
||||
if (events & BEV_EVENT_EOF) {
|
||||
LOG(ERROR) << "Connection to main thread lost: eof";
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
LOG(ERROR) << "Connection to main thread lost: network error";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Worker::run() {
|
||||
(void)reopen_log_files();
|
||||
|
||||
auto evbase = std::unique_ptr<event_base, decltype(&event_base_free)>(
|
||||
event_base_new(), event_base_free);
|
||||
if (!evbase) {
|
||||
LOG(ERROR) << "event_base_new() failed";
|
||||
return;
|
||||
}
|
||||
auto bev = std::unique_ptr<bufferevent, decltype(&bufferevent_free)>(
|
||||
bufferevent_socket_new(evbase.get(), fd_, BEV_OPT_DEFER_CALLBACKS),
|
||||
bufferevent_free);
|
||||
if (!bev) {
|
||||
LOG(ERROR) << "bufferevent_socket_new() failed";
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<Http2Session> http2session;
|
||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker;
|
||||
if (get_config()->downstream_proto == PROTO_HTTP2) {
|
||||
http2session = util::make_unique<Http2Session>(evbase.get(), cl_ssl_ctx_);
|
||||
if (http2session->init_notification() == -1) {
|
||||
DIE();
|
||||
}
|
||||
} else {
|
||||
http1_connect_blocker = util::make_unique<ConnectBlocker>();
|
||||
if (http1_connect_blocker->init(evbase.get()) == -1) {
|
||||
DIE();
|
||||
}
|
||||
}
|
||||
|
||||
auto receiver = util::make_unique<ThreadEventReceiver>(
|
||||
evbase.get(), sv_ssl_ctx_, http2session.get(),
|
||||
http1_connect_blocker.get());
|
||||
|
||||
util::bev_enable_unless(bev.get(), EV_READ);
|
||||
bufferevent_setcb(bev.get(), readcb, nullptr, eventcb, receiver.get());
|
||||
|
||||
event_base_loop(evbase.get(), 0);
|
||||
fut_ = std::async(std::launch::async, [this] { this->run_loop(); });
|
||||
}
|
||||
|
||||
void start_threaded_worker(WorkerInfo *info) {
|
||||
Worker worker(info);
|
||||
worker.run();
|
||||
void Worker::run_loop() {
|
||||
(void)reopen_log_files();
|
||||
ev_run(loop_);
|
||||
}
|
||||
|
||||
void Worker::wait() { fut_.get(); }
|
||||
|
||||
void Worker::send(const WorkerEvent &event) {
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_);
|
||||
|
||||
q_.push_back(event);
|
||||
}
|
||||
|
||||
ev_async_send(loop_, &w_);
|
||||
}
|
||||
|
||||
void Worker::process_events() {
|
||||
std::deque<WorkerEvent> q;
|
||||
{
|
||||
std::lock_guard<std::mutex> g(m_);
|
||||
q.swap(q_);
|
||||
}
|
||||
for (auto &wev : q) {
|
||||
if (wev.type == REOPEN_LOG) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Reopening log files: worker_info(" << worker_config
|
||||
<< ")";
|
||||
}
|
||||
|
||||
reopen_log_files();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wev.type == GRACEFUL_SHUTDOWN) {
|
||||
LOG(NOTICE) << "Graceful shutdown commencing";
|
||||
|
||||
worker_config->graceful_shutdown = true;
|
||||
|
||||
if (worker_stat_->num_connections == 0) {
|
||||
ev_break(loop_);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
|
||||
<< ", addrlen=" << wev.client_addrlen;
|
||||
}
|
||||
|
||||
if (worker_stat_->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(INFO, this) << "Too many connections >= "
|
||||
<< get_config()->worker_frontend_connections;
|
||||
}
|
||||
|
||||
close(wev.client_fd);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto client_handler = ssl::accept_connection(
|
||||
loop_, sv_ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
|
||||
wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
|
||||
if (!client_handler) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
}
|
||||
close(wev.client_fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
client_handler->set_http2_session(http2session_.get());
|
||||
client_handler->set_http1_connect_blocker(http1_connect_blocker_.get());
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -27,13 +27,26 @@
|
|||
|
||||
#include "shrpx.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <thread>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // NOTHREADS
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "shrpx_listen_handler.h"
|
||||
#include <ev.h>
|
||||
|
||||
#include "shrpx_config.h"
|
||||
#include "shrpx_downstream_connection_pool.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
class Http2Session;
|
||||
class ConnectBlocker;
|
||||
|
||||
struct WorkerStat {
|
||||
WorkerStat() : num_connections(0), next_downstream(0) {}
|
||||
|
||||
|
@ -44,20 +57,48 @@ struct WorkerStat {
|
|||
size_t next_downstream;
|
||||
};
|
||||
|
||||
class Worker {
|
||||
public:
|
||||
Worker(const WorkerInfo *info);
|
||||
~Worker();
|
||||
void run();
|
||||
|
||||
private:
|
||||
SSL_CTX *sv_ssl_ctx_;
|
||||
SSL_CTX *cl_ssl_ctx_;
|
||||
// Channel to the main thread
|
||||
int fd_;
|
||||
enum WorkerEventType {
|
||||
NEW_CONNECTION = 0x01,
|
||||
REOPEN_LOG = 0x02,
|
||||
GRACEFUL_SHUTDOWN = 0x03,
|
||||
};
|
||||
|
||||
void start_threaded_worker(WorkerInfo *info);
|
||||
struct WorkerEvent {
|
||||
WorkerEventType type;
|
||||
union {
|
||||
struct {
|
||||
sockaddr_union client_addr;
|
||||
size_t client_addrlen;
|
||||
int client_fd;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class Worker {
|
||||
public:
|
||||
Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx);
|
||||
~Worker();
|
||||
void run();
|
||||
void run_loop();
|
||||
void wait();
|
||||
void process_events();
|
||||
void send(const WorkerEvent &event);
|
||||
|
||||
private:
|
||||
#ifndef NOTHREADS
|
||||
std::future<void> fut_;
|
||||
#endif // NOTHREADS
|
||||
std::mutex m_;
|
||||
std::deque<WorkerEvent> q_;
|
||||
ev_async w_;
|
||||
DownstreamConnectionPool dconn_pool_;
|
||||
struct ev_loop *loop_;
|
||||
SSL_CTX *sv_ssl_ctx_;
|
||||
SSL_CTX *cl_ssl_ctx_;
|
||||
std::unique_ptr<Http2Session> http2session_;
|
||||
std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
|
||||
std::unique_ptr<WorkerStat> worker_stat_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
|
|
44
src/util.cc
44
src/util.cc
|
@ -30,6 +30,8 @@
|
|||
#include <netdb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
@ -827,6 +829,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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue