From bfac015d6118508dc165ca68bb3f70204c0a003a Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 28 Dec 2014 02:59:06 +0900 Subject: [PATCH] src: Use libev for rest of the applications --- src/HttpServer.cc | 18 +- src/Makefile.am | 28 +- src/h2load.cc | 574 +++++++---- src/h2load.h | 36 +- src/h2load_http2_session.cc | 95 +- src/h2load_http2_session.h | 2 +- src/h2load_session.h | 3 +- src/h2load_spdy_session.cc | 72 +- src/h2load_spdy_session.h | 5 +- src/memchunk.h | 232 +++++ src/nghttp.cc | 905 ++++++++++------- src/ringbuf.h | 22 + src/shrpx.cc | 298 +++--- src/shrpx_accept_handler.cc | 94 ++ src/shrpx_accept_handler.h | 53 + src/shrpx_client_handler.cc | 710 ++++++++----- src/shrpx_client_handler.h | 75 +- src/shrpx_config.cc | 7 +- src/shrpx_config.h | 29 +- src/shrpx_connect_blocker.cc | 42 +- src/shrpx_connect_blocker.h | 10 +- src/shrpx_downstream.cc | 348 ++++--- src/shrpx_downstream.h | 36 +- src/shrpx_downstream_connection.h | 2 +- src/shrpx_http2_downstream_connection.cc | 105 +- src/shrpx_http2_downstream_connection.h | 6 - src/shrpx_http2_session.cc | 1173 ++++++++++++---------- src/shrpx_http2_session.h | 80 +- src/shrpx_http2_upstream.cc | 514 ++++------ src/shrpx_http2_upstream.h | 33 +- src/shrpx_http_downstream_connection.cc | 380 ++++--- src/shrpx_http_downstream_connection.h | 21 +- src/shrpx_https_upstream.cc | 537 ++++------ src/shrpx_https_upstream.h | 16 +- src/shrpx_io_control.cc | 22 +- src/shrpx_io_control.h | 11 +- src/shrpx_listen_handler.cc | 249 ++--- src/shrpx_listen_handler.h | 58 +- src/shrpx_log.h | 7 +- src/shrpx_rate_limit.cc | 96 ++ src/shrpx_rate_limit.h | 55 + src/shrpx_spdy_upstream.cc | 412 +++----- src/shrpx_spdy_upstream.h | 19 +- src/shrpx_ssl.cc | 24 +- src/shrpx_ssl.h | 6 +- src/shrpx_thread_event_receiver.cc | 134 ++- src/shrpx_thread_event_receiver.h | 12 +- src/shrpx_upstream.h | 17 +- src/shrpx_worker.cc | 177 ++-- src/shrpx_worker.h | 67 +- src/util.cc | 44 + src/util.h | 8 + 52 files changed, 4402 insertions(+), 3577 deletions(-) create mode 100644 src/memchunk.h create mode 100644 src/shrpx_accept_handler.cc create mode 100644 src/shrpx_accept_handler.h create mode 100644 src/shrpx_rate_limit.cc create mode 100644 src/shrpx_rate_limit.h diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 6f4d9c5f..9a310da7 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -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(w->data); auto &sessions = worker->sessions; - std::lock_guard lock(worker->m); - - for (auto c : worker->q) { - sessions->accept_connection(c.fd); + std::deque q; + { + std::lock_guard lock(worker->m); + q.swap(worker->q); } - worker->q.clear(); + for (auto c : q) { + sessions->accept_connection(c.fd); + } } } // namespace diff --git a/src/Makefile.am b/src/Makefile.am index 85701111..82a17219 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/h2load.cc b/src/h2load.cc index be327d9c..7c6dc2e1 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -42,8 +42,6 @@ #include #endif // HAVE_SPDYLAY -#include - #include #include @@ -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(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(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(this); + } else { +#ifdef HAVE_SPDYLAY + auto spdy_version = + spdylay_npn_get_version(next_proto, next_proto_len); + if (spdy_version) { + session = util::make_unique(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(this); + break; +#ifdef HAVE_SPDYLAY + case Config::PROTO_SPDY2: + session = util::make_unique(this, SPDYLAY_PROTO_SPDY2); + break; + case Config::PROTO_SPDY3: + session = util::make_unique(this, SPDYLAY_PROTO_SPDY3); + break; + case Config::PROTO_SPDY3_1: + session = util::make_unique(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(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(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(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(client); - break; -#ifdef HAVE_SPDYLAY - case Config::PROTO_SPDY2: - client->session = - util::make_unique(client, SPDYLAY_PROTO_SPDY2); - break; - case Config::PROTO_SPDY3: - client->session = - util::make_unique(client, SPDYLAY_PROTO_SPDY3); - break; - case Config::PROTO_SPDY3_1: - client->session = - util::make_unique(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(&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(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(ptr); - rv = client->on_write(); - if (rv != 0) { - client->fail(); - } -} -} // namespace - namespace { void resolve_host() { int rv; diff --git a/src/h2load.h b/src/h2load.h index b456ea43..ac02cb98 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -38,12 +38,14 @@ #include -#include -#include +#include #include #include "http2.h" +#include "ringbuf.h" + +using namespace nghttp2; namespace h2load { @@ -107,7 +109,7 @@ struct Client; struct Worker { std::vector> 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 streams; std::unique_ptr session; + ev_io wev; + ev_io rev; + std::function readfn, writefn; + std::function on_readfn; + std::function 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 diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc index ee7b40fd..998419de 100644 --- a/src/h2load_http2_session.cc +++ b/src/h2load_http2_session.cc @@ -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(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(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; } diff --git a/src/h2load_http2_session.h b/src/h2load_http2_session.h index faf3bb08..d32b8c40 100644 --- a/src/h2load_http2_session.h +++ b/src/h2load_http2_session.h @@ -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(); diff --git a/src/h2load_session.h b/src/h2load_session.h index 3767ca44..8740535c 100644 --- a/src/h2load_session.h +++ b/src/h2load_session.h @@ -28,6 +28,7 @@ #include "nghttp2_config.h" #include +#include 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; diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc index 30d8676a..fb4a55b7 100644 --- a/src/h2load_spdy_session.cc +++ b/src/h2load_spdy_session.cc @@ -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(user_data); - auto spdy_session = static_cast(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(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; diff --git a/src/h2load_spdy_session.h b/src/h2load_spdy_session.h index de526641..07e7b91d 100644 --- a/src/h2load_spdy_session.h +++ b/src/h2load_spdy_session.h @@ -30,7 +30,6 @@ #include #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_; diff --git a/src/memchunk.h b/src/memchunk.h new file mode 100644 index 00000000..7e1bd8e2 --- /dev/null +++ b/src/memchunk.h @@ -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 +#include + +#include "util.h" + +namespace nghttp2 { + +template 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 knext; + Memchunk *kprev; + Memchunk *next; + uint8_t *pos, *last; + uint8_t *end; + uint8_t begin[N]; + static const size_t size = N; +}; + +template 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(); + 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 pool; + T *freelist; + size_t poolsize; +}; + +inline void *cpymem(void *dest, const void *src, size_t count) { + memcpy(dest, src, count); + return reinterpret_cast(dest) + count; +} + +template struct Memchunks { + Memchunks(Pool *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(data); + + if (!tail) { + head = tail = pool->get(); + } + auto all = count; + + while (count > 0) { + auto n = std::min(count, tail->left()); + tail->last = reinterpret_cast(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 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 *pool; + Memchunk *head, *tail; + size_t len; +}; + +typedef Memchunk<4096> Memchunk4K; +typedef Pool MemchunkPool4K; +typedef Memchunks Memchunks4K; + +} // namespace nghttp2 + +#endif // MEMCHUNK_H diff --git a/src/nghttp.cc b/src/nghttp.cc index 3670bfff..21fdbfec 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -56,9 +56,7 @@ #include #include -#include -#include -#include +#include #include @@ -71,11 +69,11 @@ #include "app_helper.h" #include "HtmlParser.h" #include "util.h" -#include "libevent_util.h" #include "base64.h" #include "http2.h" #include "nghttp2_gzip.h" #include "ssl.h" +#include "ringbuf.h" #ifndef O_BINARY #define O_BINARY (0) @@ -109,7 +107,7 @@ struct Config { int32_t weight; int multiply; // milliseconds - int timeout; + ev_tstamp timeout; int window_bits; int connection_window_bits; int verbose; @@ -127,7 +125,7 @@ struct Config { : output_upper_thres(1024 * 1024), padding(0), peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS), header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), - timeout(-1), window_bits(-1), connection_window_bits(-1), verbose(0), + timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0), null_out(false), remote_name(false), get_assets(false), stat(false), upgrade(false), continuation(false), no_content_length(false), no_dep(false), dep_idle(false) { @@ -144,6 +142,14 @@ namespace { Config config; } // namespace +namespace { +void print_protocol_nego_error() { + std::cerr << "[ERROR] HTTP/2 protocol was not selected." + << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" + << std::endl; +} +} // namespace + enum StatStage { STAT_INITIAL, STAT_ON_REQUEST, @@ -412,24 +418,20 @@ size_t populate_settings(nghttp2_settings_entry *iv) { } } // namespace -namespace { -void eventcb(bufferevent *bev, short events, void *ptr); -} // namespace - namespace { extern http_parser_settings htp_hooks; } // namespace namespace { -void upgrade_readcb(bufferevent *bev, void *ptr); +void readcb(struct ev_loop *loop, ev_io *w, int revents); } // namespace namespace { -void readcb(bufferevent *bev, void *ptr); +void writecb(struct ev_loop *loop, ev_io *w, int revents); } // namespace namespace { -void writecb(bufferevent *bev, void *ptr); +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents); } // namespace namespace { @@ -441,7 +443,7 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req); } // namespace namespace { -void settings_timeout_cb(evutil_socket_t fd, short what, void *arg); +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents); } // namespace enum client_state { STATE_IDLE, STATE_CONNECTED }; @@ -458,13 +460,19 @@ struct HttpClient { // Used for parse the HTTP upgrade response from server std::unique_ptr htp; SessionStat stat; + ev_io wev; + ev_io rev; + ev_timer wt; + ev_timer rt; + ev_timer settings_timer; + std::function readfn, writefn; + std::function on_readfn; + std::function on_writefn; nghttp2_session *session; const nghttp2_session_callbacks *callbacks; - event_base *evbase; + struct ev_loop *loop; SSL_CTX *ssl_ctx; SSL *ssl; - bufferevent *bev; - event *settings_timerev; addrinfo *addrs; addrinfo *next_addr; addrinfo *cur_addr; @@ -475,22 +483,43 @@ struct HttpClient { client_state state; // The HTTP status code of the response message of HTTP Upgrade. unsigned int upgrade_response_status_code; + int fd; // true if the response message of HTTP Upgrade request is fully // received. It is not relevant the upgrade succeeds, or not. bool upgrade_response_complete; + RingBuf<65536> wb; // SETTINGS payload sent as token68 in HTTP Upgrade uint8_t settings_payload[128]; - HttpClient(const nghttp2_session_callbacks *callbacks, event_base *evbase, + enum { ERR_CONNECT_FAIL = -100 }; + + HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop, SSL_CTX *ssl_ctx) - : session(nullptr), callbacks(callbacks), evbase(evbase), - ssl_ctx(ssl_ctx), ssl(nullptr), bev(nullptr), settings_timerev(nullptr), - addrs(nullptr), next_addr(nullptr), cur_addr(nullptr), complete(0), - settings_payloadlen(0), state(STATE_IDLE), - upgrade_response_status_code(0), upgrade_response_complete(false) {} + : session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx), + ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr), + complete(0), settings_payloadlen(0), state(STATE_IDLE), + upgrade_response_status_code(0), fd(-1), + upgrade_response_complete(false) { + ev_io_init(&wev, writecb, 0, EV_WRITE); + ev_io_init(&rev, readcb, 0, EV_READ); + + wev.data = this; + rev.data = this; + + ev_timer_init(&wt, timeoutcb, 0., config.timeout); + ev_timer_init(&rt, timeoutcb, 0., config.timeout); + + wt.data = this; + rt.data = this; + + ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.); + + settings_timer.data = this; + } ~HttpClient() { disconnect(); + if (addrs) { freeaddrinfo(addrs); addrs = nullptr; @@ -524,96 +553,239 @@ struct HttpClient { } int initiate_connection() { - int rv = 0; - if (ssl_ctx) { - // We are establishing TLS connection. - ssl = SSL_new(ssl_ctx); - if (!ssl) { - std::cerr << "[ERROR] SSL_new() failed: " - << ERR_error_string(ERR_get_error(), nullptr) << std::endl; - return -1; - } + int rv; - // If the user overrode the host header, use that value for the - // SNI extension - const char *host_string = nullptr; - auto i = - std::find_if(std::begin(config.headers), std::end(config.headers), - [](const Header &nv) { return "host" == nv.name; }); - if (i != std::end(config.headers)) { - host_string = (*i).value.c_str(); - } else { - host_string = host.c_str(); - } - - if (!util::numeric_host(host_string)) { - SSL_set_tlsext_host_name(ssl, host_string); - } - - bev = bufferevent_openssl_socket_new( - evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING, BEV_OPT_DEFER_CALLBACKS); - } else { - bev = bufferevent_socket_new(evbase, -1, BEV_OPT_DEFER_CALLBACKS); - } - rv = -1; cur_addr = nullptr; while (next_addr) { cur_addr = next_addr; - rv = bufferevent_socket_connect(bev, next_addr->ai_addr, - next_addr->ai_addrlen); next_addr = next_addr->ai_next; - if (rv == 0) { - break; + fd = util::create_nonblock_socket(cur_addr->ai_family); + if (fd == -1) { + continue; } + + if (ssl_ctx) { + // We are establishing TLS connection. + ssl = SSL_new(ssl_ctx); + if (!ssl) { + std::cerr << "[ERROR] SSL_new() failed: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_set_fd(ssl, fd); + SSL_set_connect_state(ssl); + + // If the user overrode the host header, use that value for + // the SNI extension + const char *host_string = nullptr; + auto i = + std::find_if(std::begin(config.headers), std::end(config.headers), + [](const Header &nv) { return "host" == nv.name; }); + if (i != std::end(config.headers)) { + host_string = (*i).value.c_str(); + } else { + host_string = host.c_str(); + } + + if (!util::numeric_host(host_string)) { + SSL_set_tlsext_host_name(ssl, host_string); + } + } + + rv = connect(fd, cur_addr->ai_addr, cur_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); + + writefn = &HttpClient::connected; + if (need_upgrade()) { - htp = util::make_unique(); - http_parser_init(htp.get(), HTTP_RESPONSE); - htp->data = this; - bufferevent_setcb(bev, upgrade_readcb, nullptr, eventcb, this); + on_readfn = &HttpClient::on_upgrade_read; + on_writefn = &HttpClient::on_upgrade_connect; } else { - bufferevent_setcb(bev, readcb, writecb, eventcb, this); - } - if (config.timeout != -1) { - timeval tv = {config.timeout, 0}; - bufferevent_set_timeouts(bev, &tv, &tv); + on_readfn = &HttpClient::on_read; + on_writefn = &HttpClient::on_write; } + + ev_io_set(&rev, fd, EV_READ); + ev_io_set(&wev, fd, EV_WRITE); + + ev_io_start(loop, &wev); + + ev_timer_again(loop, &wt); + return 0; } void disconnect() { - int fd = -1; state = STATE_IDLE; + + ev_timer_stop(loop, &settings_timer); + + ev_timer_stop(loop, &rt); + ev_timer_stop(loop, &wt); + + ev_io_stop(loop, &rev); + ev_io_stop(loop, &wev); + nghttp2_session_del(session); session = nullptr; + 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 (settings_timerev) { - event_free(settings_timerev); - settings_timerev = nullptr; - } - if (ssl) { SSL_free(ssl); ssl = nullptr; } + if (fd != -1) { shutdown(fd, SHUT_WR); close(fd); + fd = -1; } } + int read_clear() { + ev_timer_again(loop, &rt); + + 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 -1; + } + + if (nread == 0) { + return -1; + } + + if (on_readfn(*this, buf, nread) != 0) { + return -1; + } + } + + return 0; + } + + int write_clear() { + ev_timer_again(loop, &rt); + + 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(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + } + return -1; + } + wb.drain(nwrite); + continue; + } + + if (on_writefn(*this) != 0) { + return -1; + } + if (wb.rleft() == 0) { + wb.reset(); + break; + } + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + return 0; + } + + int noop() { return 0; } + + void on_connect_fail() { + if (state == STATE_IDLE) { + std::cerr << "[ERROR] Could not connect to the address " + << numeric_name(cur_addr) << std::endl; + } + auto cur_state = state; + disconnect(); + if (cur_state == STATE_IDLE) { + if (initiate_connection() == 0) { + std::cerr << "Trying next address " << numeric_name(cur_addr) + << std::endl; + } + } + } + + int connected() { + if (!util::check_socket_connected(fd)) { + return ERR_CONNECT_FAIL; + } + + if (config.verbose) { + print_timer(); + std::cout << " Connected" << std::endl; + } + + record_connect_time(); + state = STATE_CONNECTED; + + ev_io_start(loop, &rev); + ev_io_stop(loop, &wev); + + ev_timer_again(loop, &rt); + ev_timer_stop(loop, &wt); + + if (ssl) { + readfn = &HttpClient::tls_handshake; + writefn = &HttpClient::tls_handshake; + + return do_write(); + } + + readfn = &HttpClient::read_clear; + writefn = &HttpClient::write_clear; + + if (need_upgrade()) { + htp = util::make_unique(); + http_parser_init(htp.get(), HTTP_RESPONSE); + htp->data = this; + + return do_write(); + } + + if (on_connect() != 0) { + return -1; + } + + return 0; + } + int on_upgrade_connect() { ssize_t rv; record_handshake_time(); @@ -650,89 +822,113 @@ struct HttpClient { "Accept: */*\r\n" "User-Agent: nghttp2/" NGHTTP2_VERSION "\r\n" "\r\n"; - bufferevent_write(bev, req.c_str(), req.size()); + + wb.write(req.c_str(), req.size()); + if (config.verbose) { print_timer(); std::cout << " HTTP Upgrade request\n" << req << std::endl; } + + on_writefn = &HttpClient::noop; + + signal_write(); + return 0; } - int on_upgrade_read() { + int on_upgrade_read(const uint8_t *data, size_t len) { int rv; - auto input = bufferevent_get_input(bev); - for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); + auto nread = http_parser_execute(htp.get(), &htp_hooks, + reinterpret_cast(data), len); - if (inputlen == 0) { - assert(evbuffer_get_length(input) == 0); - - return 0; - } - - auto mem = evbuffer_pullup(input, inputlen); - - auto nread = http_parser_execute( - htp.get(), &htp_hooks, reinterpret_cast(mem), inputlen); - - if (config.verbose) { - std::cout.write(reinterpret_cast(mem), nread); - } - - if (evbuffer_drain(input, nread) != 0) { - return -1; - } - - auto htperr = HTTP_PARSER_ERRNO(htp.get()); - - if (htperr != HPE_OK) { - std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: " - << "(" << http_errno_name(htperr) << ") " - << http_errno_description(htperr) << std::endl; - return -1; - } - - if (upgrade_response_complete) { - - if (config.verbose) { - std::cout << std::endl; - } - - if (upgrade_response_status_code == 101) { - if (config.verbose) { - print_timer(); - std::cout << " HTTP Upgrade success" << std::endl; - } - - bufferevent_setcb(bev, readcb, writecb, eventcb, this); - - rv = on_connect(); - - if (rv != 0) { - return rv; - } - - // Read remaining data in the buffer because it is not - // notified callback anymore. - rv = on_read(); - - if (rv != 0) { - return rv; - } - - return 0; - } - - std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl; - - return -1; - } + if (config.verbose) { + std::cout.write(reinterpret_cast(data), nread); } + + auto htperr = HTTP_PARSER_ERRNO(htp.get()); + + if (htperr != HPE_OK) { + std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: " + << "(" << http_errno_name(htperr) << ") " + << http_errno_description(htperr) << std::endl; + return -1; + } + + if (!upgrade_response_complete) { + return 0; + } + + if (config.verbose) { + std::cout << std::endl; + } + + if (upgrade_response_status_code != 101) { + std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl; + + return -1; + } + + if (config.verbose) { + print_timer(); + std::cout << " HTTP Upgrade success" << std::endl; + } + + on_readfn = &HttpClient::on_read; + on_writefn = &HttpClient::on_write; + + rv = on_connect(); + if (rv != 0) { + return rv; + } + + // Read remaining data in the buffer because it is not notified + // callback anymore. + rv = on_readfn(*this, data + nread, len - nread); + if (rv != 0) { + return rv; + } + + return 0; } + int do_read() { return readfn(*this); } + int do_write() { return writefn(*this); } + int on_connect() { int rv; + + if (ssl) { + // Check NPN or ALPN result + 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 (config.verbose) { + std::cout << "The negotiated protocol: "; + std::cout.write(reinterpret_cast(next_proto), + next_proto_len); + std::cout << std::endl; + } + if (!util::check_h2_is_selected(next_proto, next_proto_len)) { + next_proto = nullptr; + } + break; + } +#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) { + print_protocol_nego_error(); + return -1; + } + } + if (!need_upgrade()) { record_handshake_time(); } @@ -763,8 +959,8 @@ struct HttpClient { } } // Send connection header here - bufferevent_write(bev, NGHTTP2_CLIENT_CONNECTION_PREFACE, - NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN); + wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE, + NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN); // If upgrade succeeds, the SETTINGS value sent with // HTTP2-Settings header field has already been submitted to // session object. @@ -822,11 +1018,8 @@ struct HttpClient { return -1; } } - assert(settings_timerev == nullptr); - settings_timerev = evtimer_new(evbase, settings_timeout_cb, this); - // SETTINGS ACK timeout is 10 seconds for now - timeval settings_timeout = {10, 0}; - evtimer_add(settings_timerev, &settings_timeout); + + ev_timer_again(loop, &settings_timer); if (config.connection_window_bits != -1) { int32_t wininc = (1 << config.connection_window_bits) - 1 - @@ -844,79 +1037,168 @@ struct HttpClient { return -1; } } - return on_write(); + + signal_write(); + + return 0; } - int on_read() { - int rv; - auto input = bufferevent_get_input(bev); - - for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); - - if (inputlen == 0) { - assert(evbuffer_get_length(input) == 0); - - return on_write(); - } - - auto mem = evbuffer_pullup(input, inputlen); - - rv = nghttp2_session_mem_recv(session, mem, inputlen); - - if (rv < 0) { - std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: " - << nghttp2_strerror(rv) << std::endl; - return -1; - } - - if (evbuffer_drain(input, rv) != 0) { - return -1; - } + int on_read(const uint8_t *data, size_t len) { + auto rv = nghttp2_session_mem_recv(session, data, len); + if (rv < 0) { + std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; + return -1; } + + assert(static_cast(rv) == len); + + if (nghttp2_session_want_read(session) == 0 && + nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) { + return -1; + } + + signal_write(); + + return 0; } int on_write() { - int rv; - uint8_t buf[4096]; - auto output = bufferevent_get_output(bev); - util::EvbufferBuffer evbbuf(output, buf, sizeof(buf)); - for (;;) { - if (evbuffer_get_length(output) + evbbuf.get_buflen() > - config.output_upper_thres) { - break; - } - - const uint8_t *data; - auto datalen = nghttp2_session_mem_send(session, &data); - - if (datalen < 0) { - std::cerr << "[ERROR] nghttp2_session_mem_send() returned error: " - << nghttp2_strerror(datalen) << std::endl; - return -1; - } - if (datalen == 0) { - break; - } - rv = evbbuf.add(data, datalen); - if (rv != 0) { - std::cerr << "[ERROR] evbuffer_add() failed" << std::endl; - return -1; - } - } - rv = evbbuf.flush(); + auto rv = nghttp2_session_send(session); if (rv != 0) { - std::cerr << "[ERROR] evbuffer_add() failed" << std::endl; + std::cerr << "[ERROR] nghttp2_session_send() returned error: " + << nghttp2_strerror(rv) << std::endl; 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 && wb.rleft() == 0) { return -1; } + return 0; } + int 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: + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + default: + return -1; + } + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + readfn = &HttpClient::read_tls; + writefn = &HttpClient::write_tls; + + if (on_connect() != 0) { + return -1; + } + + return 0; + } + + int read_tls() { + ev_timer_again(loop, &rt); + + 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(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + default: + return -1; + } + } + + if (on_readfn(*this, buf, rv) != 0) { + return -1; + } + } + } + + int write_tls() { + ev_timer_again(loop, &rt); + + 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(loop, &wev); + ev_timer_stop(loop, &wt); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop, &wev); + ev_timer_again(loop, &wt); + return 0; + default: + return -1; + } + } + + wb.drain(rv); + + continue; + } + if (on_writefn(*this) != 0) { + return -1; + } + if (wb.rleft() == 0) { + break; + } + } + + ev_io_stop(loop, &wev); + ev_timer_stop(loop, &wt); + + return 0; + } + + void signal_write() { ev_io_start(loop, &wev); } + bool all_requests_processed() const { return complete == reqvec.size(); } void update_hostport() { if (reqvec.empty()) { @@ -1384,14 +1666,13 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, } // namespace namespace { -void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) { - int rv; - auto client = get_session(arg); +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + ev_timer_stop(loop, w); + nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT); - rv = client->on_write(); - if (rv != 0) { - client->disconnect(); - } + + client->signal_write(); } } // namespace @@ -1606,11 +1887,7 @@ int on_frame_recv_callback2(nghttp2_session *session, if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { break; } - if (client->settings_timerev) { - evtimer_del(client->settings_timerev); - event_free(client->settings_timerev); - client->settings_timerev = nullptr; - } + ev_timer_stop(client->loop, &client->settings_timer); break; case NGHTTP2_PUSH_PROMISE: { auto req = static_cast(nghttp2_session_get_stream_user_data( @@ -1717,14 +1994,6 @@ void print_stats(const HttpClient &client) { } } // namespace -namespace { -void print_protocol_nego_error() { - std::cerr << "[ERROR] HTTP/2 protocol was not selected." - << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")" - << std::endl; -} -} // namespace - namespace { int client_select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, const unsigned char *in, @@ -1750,10 +2019,22 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out, } // namespace namespace { -void upgrade_readcb(bufferevent *bev, void *ptr) { - int rv; - auto client = static_cast(ptr); - rv = client->on_upgrade_read(); +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + if (client->do_read() != 0) { + client->disconnect(); + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(w->data); + auto rv = client->do_write(); + if (rv == HttpClient::ERR_CONNECT_FAIL) { + client->on_connect_fail(); + return; + } if (rv != 0) { client->disconnect(); } @@ -1761,121 +2042,10 @@ void upgrade_readcb(bufferevent *bev, void *ptr) { } // namespace namespace { -void readcb(bufferevent *bev, void *ptr) { - int rv; - auto client = static_cast(ptr); - rv = client->on_read(); - if (rv != 0) { - client->disconnect(); - } -} -} // namespace - -namespace { -void writecb(bufferevent *bev, void *ptr) { - if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { - return; - } - int rv; - auto client = static_cast(ptr); - rv = client->on_write(); - if (rv != 0) { - client->disconnect(); - } -} -} // namespace - -namespace { -void eventcb(bufferevent *bev, short events, void *ptr) { - int rv; - auto client = static_cast(ptr); - if (events & BEV_EVENT_CONNECTED) { - client->record_connect_time(); - client->state = STATE_CONNECTED; - int fd = bufferevent_getfd(bev); - int val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), - sizeof(val)) == -1) { - std::cerr << "[ERROR] Setting option TCP_NODELAY failed: errno=" << errno - << std::endl; - } - if (client->need_upgrade()) { - rv = client->on_upgrade_connect(); - } else { - if (client->ssl) { - // Check NPN or ALPN result - 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 (config.verbose) { - std::cout << "The negotiated protocol: "; - std::cout.write(reinterpret_cast(next_proto), - next_proto_len); - std::cout << std::endl; - } - if (!util::check_h2_is_selected(next_proto, next_proto_len)) { - next_proto = nullptr; - } - break; - } -#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) { - print_protocol_nego_error(); - client->disconnect(); - return; - } - } - rv = client->on_connect(); - } - if (rv != 0) { - client->disconnect(); - return; - } - return; - } - if (events & BEV_EVENT_EOF) { - std::cerr << "EOF" << std::endl; - auto state = client->state; - client->disconnect(); - if (state == STATE_IDLE) { - auto failed_name = numeric_name(client->cur_addr); - if (client->initiate_connection() == 0) { - std::cerr << "[ERROR] EOF from " << failed_name << "\n" - << "Trying next address " << numeric_name(client->cur_addr) - << std::endl; - } - } - return; - } - if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { - if (events & BEV_EVENT_ERROR) { - if (client->state == STATE_IDLE) { - std::cerr << "[ERROR] Could not connect to the address " - << numeric_name(client->cur_addr) << std::endl; - } else { - std::cerr << "[ERROR] Network error" << std::endl; - } - } else { - std::cerr << "[ERROR] Timeout" << std::endl; - } - auto state = client->state; - client->disconnect(); - if (state == STATE_IDLE) { - if (client->initiate_connection() == 0) { - std::cerr << "Trying next address " << numeric_name(client->cur_addr) - << std::endl; - } - } - return; - } +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto client = static_cast(w->data); + std::cerr << "[ERROR] Timeout" << std::endl; + client->disconnect(); } } // namespace @@ -1885,7 +2055,7 @@ int communicate( std::vector> requests, const nghttp2_session_callbacks *callbacks) { int result = 0; - auto evbase = event_base_new(); + auto loop = EV_DEFAULT; SSL_CTX *ssl_ctx = nullptr; if (scheme == "https") { ssl_ctx = SSL_CTX_new(SSLv23_client_method()); @@ -1935,7 +2105,7 @@ int communicate( #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L } { - HttpClient client{callbacks, evbase, ssl_ctx}; + HttpClient client{callbacks, loop, ssl_ctx}; nghttp2_priority_spec pri_spec; int32_t dep_stream_id = 0; @@ -1966,7 +2136,7 @@ int communicate( if (client.initiate_connection() != 0) { goto fin; } - event_base_loop(evbase, 0); + ev_run(loop, 0); #ifdef HAVE_JANSSON if (!config.harfile.empty()) { @@ -2003,9 +2173,6 @@ fin: if (ssl_ctx) { SSL_CTX_free(ssl_ctx); } - if (evbase) { - event_base_free(evbase); - } return result; } } // namespace @@ -2038,6 +2205,20 @@ ssize_t file_read_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(user_data); + auto &wb = client->wb; + + if (wb.wleft() == 0) { + return NGHTTP2_ERR_WOULDBLOCK; + } + + return wb.write(data, length); +} +} // namespace + namespace { int run(char **uris, int n) { nghttp2_session_callbacks *callbacks; @@ -2068,6 +2249,8 @@ int run(char **uris, int n) { nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + if (config.padding) { nghttp2_session_callbacks_set_select_padding_callback( callbacks, select_padding_callback); @@ -2352,7 +2535,7 @@ int main(int argc, char **argv) { ++config.verbose; break; case 't': - config.timeout = atoi(optarg) * 1000; + config.timeout = atoi(optarg); break; case 'u': config.upgrade = true; diff --git a/src/ringbuf.h b/src/ringbuf.h index 5452b1af..10b62b40 100644 --- a/src/ringbuf.h +++ b/src/ringbuf.h @@ -25,6 +25,8 @@ #ifndef RINGBUF_H #define RINGBUF_H +#include "nghttp2_config.h" + #include #include @@ -53,6 +55,11 @@ template 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 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 diff --git a/src/shrpx.cc b/src/shrpx.cc index 92eb1af1..8397cf52 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -48,7 +48,7 @@ #include #include -#include +#include #include @@ -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(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(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(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 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(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(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(arg); +void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { + auto listener_handler = static_cast(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(arg); +void exec_binary_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { + auto listener_handler = static_cast(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(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(arg); +void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w, + int revents) { + auto listener_handler = static_cast(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 generate_time() { } // namespace namespace { -void refresh_cb(evutil_socket_t sig, short events, void *arg) { - auto listener_handler = static_cast(arg); +void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto listener_handler = static_cast(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(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= 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= Set maximum average write rate on frontend @@ -914,47 +847,42 @@ Timeout: --frontend-http2-read-timeout= 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= 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= 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= 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= 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= 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= 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= 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= 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= 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 diff --git a/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc new file mode 100644 index 00000000..43239888 --- /dev/null +++ b/src/shrpx_accept_handler.cc @@ -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 + +#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(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 diff --git a/src/shrpx_accept_handler.h b/src/shrpx_accept_handler.h new file mode 100644 index 00000000..b02f35ae --- /dev/null +++ b/src/shrpx_accept_handler.h @@ -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 + +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 diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 2157993b..b87a56a9 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -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(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(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(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(w->data); + + if (handler->do_read() != 0) { delete handler; + return; } } } // namespace namespace { -void upstream_writecb(bufferevent *bev, void *arg) { - auto handler = static_cast(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(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(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(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(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(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(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(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(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(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(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(dconn_pool_, http2session_); } else { - dconn = util::make_unique(dconn_pool_); + dconn = util::make_unique(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(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(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 diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index 87a4ed9c..2c3efb79 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -29,11 +29,15 @@ #include -#include -#include +#include #include +#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 dconn); void remove_downstream_connection(DownstreamConnection *dconn); std::unique_ptr 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_; std::string ipaddr_; std::string port_; // The ALPN identifier negotiated for this connection. std::string alpn_; + std::function read_, write_; + std::function 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 diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 64fba1a0..bc7c4a5a 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -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 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; } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index abe46695..a0addb9b 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -37,9 +37,10 @@ #include #include -#include #include +#include + #include namespace shrpx { @@ -171,15 +172,15 @@ struct Config { std::vector 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 host; std::unique_ptr private_key_file; std::unique_ptr private_key_passwd; @@ -199,10 +200,10 @@ struct Config { std::unique_ptr downstream_http_proxy_host; std::unique_ptr http2_upstream_dump_request_header_file; std::unique_ptr 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. diff --git a/src/shrpx_connect_blocker.cc b/src/shrpx_connect_blocker.cc index 85f37ae7..34df7c67 100644 --- a/src/shrpx_connect_blocker.cc +++ b/src/shrpx_connect_blocker.cc @@ -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 diff --git a/src/shrpx_connect_blocker.h b/src/shrpx_connect_blocker.h index b96a8047..af445644 100644 --- a/src/shrpx_connect_blocker.h +++ b/src/shrpx_connect_blocker.h @@ -27,16 +27,15 @@ #include "shrpx.h" -#include +#include 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 diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index bafea901..ceba2c94 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -38,11 +38,76 @@ namespace shrpx { +namespace { +void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto downstream = static_cast(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(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(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(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; } diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index a929c9c2..5c48ff6a 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -34,13 +34,13 @@ #include #include -#include -#include +#include #include #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 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 diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h index 3dc4d7b1..fad0b491 100644 --- a/src/shrpx_downstream_connection.h +++ b/src/shrpx_downstream_connection.h @@ -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; diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 56409832..65ba0f61 100644 --- a/src/shrpx_http2_downstream_connection.cc +++ b/src/shrpx_http2_downstream_connection.cc @@ -26,10 +26,6 @@ #include -#include - -#include - #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; } diff --git a/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h index 8046ddf0..cee6265a 100644 --- a/src/shrpx_http2_downstream_connection.h +++ b/src/shrpx_http2_downstream_connection.h @@ -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_; }; diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 952ca605..7fa309f5 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -31,8 +31,6 @@ #include -#include - #include "shrpx_upstream.h" #include "shrpx_downstream.h" #include "shrpx_config.h" @@ -44,19 +42,172 @@ #include "shrpx_worker_config.h" #include "http2.h" #include "util.h" -#include "libevent_util.h" #include "base64.h" using namespace nghttp2; namespace shrpx { -Http2Session::Http2Session(event_base *evbase, SSL_CTX *ssl_ctx) - : evbase_(evbase), ssl_ctx_(ssl_ctx), ssl_(nullptr), session_(nullptr), - bev_(nullptr), wrbev_(nullptr), rdbev_(nullptr), - settings_timerev_(nullptr), connection_check_timerev_(nullptr), fd_(-1), +namespace { +void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + SSLOG(INFO, http2session) << "connection check required"; + ev_timer_stop(loop, w); + http2session->set_connection_check_state( + Http2Session::CONNECTION_CHECK_REQUIRED); +} +} // namespace + +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + SSLOG(INFO, http2session) << "SETTINGS timeout"; + if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { + http2session->disconnect(); + return; + } + http2session->signal_write(); +} +} // namespace + +// Do this in tls_handshake +// if (events & BEV_EVENT_CONNECTED) { +// if (LOG_ENABLED(INFO)) { +// SSLOG(INFO, http2session) << "Connection established"; +// } +// http2session->set_state(Http2Session::CONNECTED); +// if (!get_config()->downstream_no_tls && !get_config()->insecure && +// http2session->check_cert() != 0) { + +// http2session->disconnect(true); + +// return; +// } + +// if (http2session->on_connect() != 0) { +// http2session->disconnect(true); +// return; +// } + +// auto fd = bufferevent_getfd(bev); +// int val = 1; +// if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), +// sizeof(val)) == -1) { +// auto error = errno; +// SSLOG(WARN, http2session) +// << "Setting option TCP_NODELAY failed: errno=" << error; +// } +// return; +// } + +// TODO check EOF in read function +// if (events & BEV_EVENT_EOF) { +// if (LOG_ENABLED(INFO)) { +// SSLOG(INFO, http2session) << "EOF"; +// } +// http2session->disconnect(http2session->get_state() == +// Http2Session::CONNECTING); +// return; +// } + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto http2session = static_cast(w->data); + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "Timeout"; + } + + http2session->disconnect(http2session->get_state() == + Http2Session::CONNECTING); +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto http2session = static_cast(w->data); + http2session->connection_alive(); + rv = http2session->do_read(); + if (rv != 0) { + http2session->disconnect(http2session->should_hard_fail()); + } +} +} // namespace + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int rv; + auto http2session = static_cast(w->data); + http2session->clear_write_request(); + http2session->connection_alive(); + rv = http2session->do_write(); + if (rv != 0) { + http2session->disconnect(http2session->should_hard_fail()); + } +} +} // namespace + +namespace { +void wrschedcb(struct ev_loop *loop, ev_prepare *w, int revents) { + auto http2session = static_cast(w->data); + if (!http2session->write_requested()) { + return; + } + http2session->clear_write_request(); + switch (http2session->get_state()) { + case Http2Session::DISCONNECTED: + LOG(INFO) << "wrschedcb start connect"; + if (http2session->initiate_connection() != 0) { + SSLOG(FATAL, http2session) << "Could not initiate backend connection"; + http2session->disconnect(true); + } + break; + case Http2Session::CONNECTED: + writecb(loop, http2session->get_wev(), revents); + break; + } +} +} // namespace + +Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx) + : loop_(loop), ssl_ctx_(ssl_ctx), ssl_(nullptr), session_(nullptr), + data_pending_(nullptr), data_pendinglen_(0), fd_(-1), state_(DISCONNECTED), connection_check_state_(CONNECTION_CHECK_NONE), - notified_(false), flow_control_(false) {} + flow_control_(false), write_requested_(false) { + // We do not know fd yet, so just set dummy fd 0 + ev_io_init(&wev_, writecb, 0, EV_WRITE); + ev_io_init(&rev_, readcb, 0, EV_READ); + + wev_.data = this; + rev_.data = this; + + read_ = write_ = &Http2Session::noop; + on_read_ = on_write_ = &Http2Session::noop; + + 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; + + // We will resuse this many times, so use repeat timeout value. + ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 5.); + + connchk_timer_.data = this; + + // SETTINGS ACK timeout is 10 seconds for now. We will resuse this + // many times, so use repeat timeout value. + ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 10.); + + settings_timer_.data = this; + + ev_prepare_init(&wrsched_prep_, &wrschedcb); + wrsched_prep_.data = this; + + ev_prepare_start(loop_, &wrsched_prep_); +} Http2Session::~Http2Session() { disconnect(); } @@ -67,39 +218,27 @@ int Http2Session::disconnect(bool hard) { nghttp2_session_del(session_); session_ = nullptr; - if (connection_check_timerev_) { - event_free(connection_check_timerev_); - connection_check_timerev_ = nullptr; - } + rb_.reset(); + wb_.reset(); - if (settings_timerev_) { - event_free(settings_timerev_); - settings_timerev_ = nullptr; - } + ev_timer_stop(loop_, &settings_timer_); + ev_timer_stop(loop_, &connchk_timer_); + + ev_timer_stop(loop_, &rt_); + ev_timer_stop(loop_, &wt_); + + read_ = write_ = &Http2Session::noop; + on_read_ = on_write_ = &Http2Session::noop; + + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); if (ssl_) { SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN); SSL_shutdown(ssl_); - } - if (bev_) { - int fd = bufferevent_getfd(bev_); - util::bev_disable_unless(bev_, EV_READ | EV_WRITE); - bufferevent_free(bev_); - bev_ = nullptr; - if (fd != -1) { - if (fd_ == -1) { - fd_ = fd; - } else if (fd != fd_) { - SSLOG(WARN, this) << "fd in bev_ != fd_"; - shutdown(fd, SHUT_WR); - close(fd); - } - } - } - if (ssl_) { SSL_free(ssl_); + ssl_ = nullptr; } - ssl_ = nullptr; if (fd_ != -1) { if (LOG_ENABLED(INFO)) { @@ -114,7 +253,6 @@ int Http2Session::disconnect(bool hard) { proxy_htp_.reset(); } - notified_ = false; connection_check_state_ = CONNECTION_CHECK_NONE; state_ = DISCONNECTED; @@ -156,248 +294,29 @@ int Http2Session::disconnect(bool hard) { return 0; } -namespace { -void notify_readcb(bufferevent *bev, void *arg) { - int rv; - auto http2session = static_cast(arg); - http2session->clear_notify(); - switch (http2session->get_state()) { - case Http2Session::DISCONNECTED: - rv = http2session->initiate_connection(); - if (rv != 0) { - SSLOG(FATAL, http2session) << "Could not initiate backend connection"; - http2session->disconnect(); - } - break; - case Http2Session::CONNECTED: - rv = http2session->send(); - if (rv != 0) { - http2session->disconnect(); - } - break; - } -} -} // namespace +// if (events & BEV_EVENT_EOF) { +// if (LOG_ENABLED(INFO)) { +// SSLOG(INFO, http2session) << "Proxy EOF"; +// } +// http2session->disconnect(http2session->get_state() == +// Http2Session::PROXY_CONNECTING); +// return; +// } -namespace { -void notify_eventcb(bufferevent *bev, short events, void *arg) { - auto http2session = static_cast(arg); - // TODO should DIE()? - if (events & BEV_EVENT_EOF) { - SSLOG(ERROR, http2session) << "Notification connection lost: EOF"; - } - if (events & BEV_EVENT_TIMEOUT) { - SSLOG(ERROR, http2session) << "Notification connection lost: timeout"; - } - if (events & BEV_EVENT_ERROR) { - SSLOG(ERROR, http2session) << "Notification connection lost: network error"; - } -} -} // namespace - -int Http2Session::init_notification() { - int rv; - int sockpair[2]; - rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, - sockpair); - if (rv == -1) { - auto error = errno; - SSLOG(FATAL, this) << "socketpair() failed: errno=" << error; - return -1; - } - - wrbev_ = bufferevent_socket_new( - evbase_, sockpair[0], BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); - if (!wrbev_) { - SSLOG(FATAL, this) << "bufferevent_socket_new() failed"; - for (int i = 0; i < 2; ++i) { - close(sockpair[i]); - } - return -1; - } - rdbev_ = bufferevent_socket_new( - evbase_, sockpair[1], BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); - if (!rdbev_) { - SSLOG(FATAL, this) << "bufferevent_socket_new() failed"; - close(sockpair[1]); - return -1; - } - util::bev_enable_unless(rdbev_, EV_READ); - bufferevent_setcb(rdbev_, notify_readcb, nullptr, notify_eventcb, this); - return 0; -} - -namespace { -void readcb(bufferevent *bev, void *ptr) { - int rv; - auto http2session = static_cast(ptr); - http2session->reset_timeouts(); - rv = http2session->connection_alive(); - if (rv != 0) { - http2session->disconnect(); - return; - } - rv = http2session->on_read(); - if (rv != 0) { - http2session->disconnect(); - } -} -} // namespace - -namespace { -void writecb(bufferevent *bev, void *ptr) { - int rv; - auto http2session = static_cast(ptr); - http2session->reset_timeouts(); - rv = http2session->connection_alive(); - if (rv != 0) { - http2session->disconnect(); - return; - } - if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { - return; - } - rv = http2session->on_write(); - if (rv != 0) { - http2session->disconnect(); - } -} -} // namespace - -namespace { -void eventcb(bufferevent *bev, short events, void *ptr) { - auto http2session = static_cast(ptr); - if (events & BEV_EVENT_CONNECTED) { - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, http2session) << "Connection established"; - } - http2session->set_state(Http2Session::CONNECTED); - if (!get_config()->downstream_no_tls && !get_config()->insecure && - http2session->check_cert() != 0) { - - http2session->disconnect(true); - - return; - } - - if (http2session->on_connect() != 0) { - http2session->disconnect(true); - return; - } - - auto fd = bufferevent_getfd(bev); - int val = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), - sizeof(val)) == -1) { - auto error = errno; - SSLOG(WARN, http2session) - << "Setting option TCP_NODELAY failed: errno=" << error; - } - return; - } - - if (events & BEV_EVENT_EOF) { - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, http2session) << "EOF"; - } - http2session->disconnect(http2session->get_state() == - Http2Session::CONNECTING); - return; - } - - if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { - if (LOG_ENABLED(INFO)) { - if (events & BEV_EVENT_ERROR) { - SSLOG(INFO, http2session) << "Network error"; - } else { - SSLOG(INFO, http2session) << "Timeout"; - } - } - http2session->disconnect(http2session->get_state() == - Http2Session::CONNECTING); - return; - } -} -} // namespace - -namespace { -void proxy_readcb(bufferevent *bev, void *ptr) { - auto http2session = static_cast(ptr); - if (http2session->on_read_proxy() == 0) { - switch (http2session->get_state()) { - case Http2Session::PROXY_CONNECTED: - // The current bufferevent is no longer necessary, so delete it - // here. But we keep fd_ inside it. - http2session->unwrap_free_bev(); - // Initiate SSL/TLS handshake through established tunnel. - if (http2session->initiate_connection() != 0) { - http2session->disconnect(); - } - break; - case Http2Session::PROXY_FAILED: - http2session->disconnect(); - break; - } - } else { - http2session->disconnect(); - } -} -} // namespace - -namespace { -void proxy_eventcb(bufferevent *bev, short events, void *ptr) { - auto http2session = static_cast(ptr); - if (events & BEV_EVENT_CONNECTED) { - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, http2session) << "Connected to the proxy"; - } - std::string req = "CONNECT "; - req += get_config()->downstream_addrs[0].hostport.get(); - req += " HTTP/1.1\r\nHost: "; - req += get_config()->downstream_addrs[0].host.get(); - req += "\r\n"; - if (get_config()->downstream_http_proxy_userinfo) { - req += "Proxy-Authorization: Basic "; - size_t len = strlen(get_config()->downstream_http_proxy_userinfo.get()); - req += base64::encode(get_config()->downstream_http_proxy_userinfo.get(), - get_config()->downstream_http_proxy_userinfo.get() + - len); - req += "\r\n"; - } - req += "\r\n"; - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, http2session) << "HTTP proxy request headers\n" << req; - } - if (bufferevent_write(bev, req.c_str(), req.size()) != 0) { - SSLOG(ERROR, http2session) << "bufferevent_write() failed"; - http2session->disconnect(true); - } - return; - } - - if (events & BEV_EVENT_EOF) { - if (LOG_ENABLED(INFO)) { - SSLOG(INFO, http2session) << "Proxy EOF"; - } - http2session->disconnect(http2session->get_state() == - Http2Session::PROXY_CONNECTING); - return; - } - - if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { - if (LOG_ENABLED(INFO)) { - if (events & BEV_EVENT_ERROR) { - SSLOG(INFO, http2session) << "Network error"; - } else { - SSLOG(INFO, http2session) << "Timeout"; - } - } - http2session->disconnect(http2session->get_state() == - Http2Session::PROXY_CONNECTING); - return; - } -} -} // namespace +// if (events & (BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { +// if (LOG_ENABLED(INFO)) { +// if (events & BEV_EVENT_ERROR) { +// SSLOG(INFO, http2session) << "Network error"; +// } else { +// SSLOG(INFO, http2session) << "Timeout"; +// } +// } +// http2session->disconnect(http2session->get_state() == +// Http2Session::PROXY_CONNECTING); +// return; +// } +// } +// } // namespace int Http2Session::check_cert() { return ssl::check_cert(ssl_); } @@ -410,36 +329,36 @@ int Http2Session::initiate_connection() { << get_config()->downstream_http_proxy_port; } - auto fd = socket(get_config()->downstream_http_proxy_addr.storage.ss_family, - SOCK_STREAM | SOCK_CLOEXEC, 0); + fd_ = util::create_nonblock_socket( + get_config()->downstream_http_proxy_addr.storage.ss_family); - if (fd == -1) { - return SHRPX_ERR_NETWORK; + if (fd_ == -1) { + return -1; } - bev_ = bufferevent_socket_new(evbase_, fd, BEV_OPT_DEFER_CALLBACKS); - if (!bev_) { - SSLOG(ERROR, this) << "bufferevent_socket_new() failed"; - close(fd); - return SHRPX_ERR_NETWORK; - } - util::bev_enable_unless(bev_, EV_READ); - bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout, - &get_config()->downstream_write_timeout); - - // No need to set writecb because we write the request when - // connected at once. - bufferevent_setcb(bev_, proxy_readcb, nullptr, proxy_eventcb, this); - rv = bufferevent_socket_connect( - bev_, - const_cast(&get_config()->downstream_http_proxy_addr.sa), - get_config()->downstream_http_proxy_addrlen); - if (rv != 0) { + rv = connect(fd_, const_cast( + &get_config()->downstream_http_proxy_addr.sa), + get_config()->downstream_http_proxy_addrlen); + if (rv != 0 && errno != EINPROGRESS) { SSLOG(ERROR, this) << "Failed to connect to the proxy " << get_config()->downstream_http_proxy_host.get() << ":" << get_config()->downstream_http_proxy_port; - return SHRPX_ERR_NETWORK; + return -1; } + + ev_io_set(&rev_, fd_, EV_READ); + ev_io_set(&wev_, fd_, EV_WRITE); + + ev_io_start(loop_, &wev_); + + // TODO we should have timeout for connection establishment + ev_timer_again(loop_, &wt_); + + write_ = &Http2Session::connected; + + on_read_ = &Http2Session::downstream_read_proxy; + on_write_ = &Http2Session::downstream_connect_proxy; + proxy_htp_ = util::make_unique(); http_parser_init(proxy_htp_.get(), HTTP_RESPONSE); proxy_htp_->data = this; @@ -480,76 +399,73 @@ int Http2Session::initiate_connection() { if (state_ == DISCONNECTED) { assert(fd_ == -1); - fd_ = socket(get_config()->downstream_addrs[0].addr.storage.ss_family, - SOCK_STREAM | SOCK_CLOEXEC, 0); - } + fd_ = util::create_nonblock_socket( + get_config()->downstream_addrs[0].addr.storage.ss_family); + if (fd_ == -1) { + return -1; + } - bev_ = bufferevent_openssl_socket_new(evbase_, fd_, ssl_, - BUFFEREVENT_SSL_CONNECTING, - BEV_OPT_DEFER_CALLBACKS); - if (!bev_) { - SSLOG(ERROR, this) << "bufferevent_socket_new() failed"; - return SHRPX_ERR_NETWORK; - } - if (state_ == DISCONNECTED) { - rv = bufferevent_socket_connect( - bev_, + rv = connect( + fd_, // TODO maybe not thread-safe? const_cast(&get_config()->downstream_addrs[0].addr.sa), get_config()->downstream_addrs[0].addrlen); - } else { - rv = 0; + if (rv != 0 && errno != EINPROGRESS) { + return -1; + } } + + if (SSL_set_fd(ssl_, fd_) == 0) { + return -1; + } + + SSL_set_connect_state(ssl_); } else { if (state_ == DISCONNECTED) { // Without TLS and proxy. assert(fd_ == -1); - fd_ = socket(get_config()->downstream_addrs[0].addr.storage.ss_family, - SOCK_STREAM | SOCK_CLOEXEC, 0); + fd_ = util::create_nonblock_socket( + get_config()->downstream_addrs[0].addr.storage.ss_family); if (fd_ == -1) { - return SHRPX_ERR_NETWORK; + return -1; } - } - bev_ = bufferevent_socket_new(evbase_, fd_, BEV_OPT_DEFER_CALLBACKS); - if (!bev_) { - SSLOG(ERROR, this) << "bufferevent_socket_new() failed"; - return SHRPX_ERR_NETWORK; - } - - if (state_ == DISCONNECTED) { - rv = bufferevent_socket_connect( - bev_, - const_cast(&get_config()->downstream_addrs[0].addr.sa), - get_config()->downstream_addrs[0].addrlen); + rv = connect(fd_, const_cast( + &get_config()->downstream_addrs[0].addr.sa), + get_config()->downstream_addrs[0].addrlen); + if (rv != 0 && errno != EINPROGRESS) { + return -1; + } } else { - // Without TLS but with proxy. - - // Connection already established. - eventcb(bev_, BEV_EVENT_CONNECTED, this); - // eventcb() has no return value. Check state_ to whether it was - // failed or not. - if (state_ == DISCONNECTED) { + // Without TLS but with proxy. Connection already + // established. + if (on_connect() != -1) { + state_ = CONNECT_FAILING; return -1; } } } - if (rv != 0) { - return SHRPX_ERR_NETWORK; - } + ev_io_set(&rev_, fd_, EV_READ); + ev_io_set(&wev_, fd_, EV_WRITE); - bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK); - util::bev_enable_unless(bev_, EV_READ); - bufferevent_setcb(bev_, readcb, writecb, eventcb, this); - // Set timeout for HTTP2 session - reset_timeouts(); + ev_io_start(loop_, &wev_); + + write_ = &Http2Session::connected; + + on_write_ = &Http2Session::downstream_write; + on_read_ = &Http2Session::downstream_read; // We have been already connected when no TLS and proxy is used. if (state_ != CONNECTED) { state_ = CONNECTING; + ev_io_start(loop_, &wev_); + // TODO we should have timeout for connection establishment + ev_timer_again(loop_, &wt_); + } else { + ev_timer_again(loop_, &rt_); } return 0; @@ -560,13 +476,6 @@ int Http2Session::initiate_connection() { return 0; } -void Http2Session::unwrap_free_bev() { - assert(fd_ == -1); - fd_ = bufferevent_getfd(bev_); - bufferevent_free(bev_); - bev_ = nullptr; -} - namespace { int htp_hdrs_completecb(http_parser *htp) { auto http2session = static_cast(htp->data); @@ -580,7 +489,7 @@ int htp_hdrs_completecb(http_parser *htp) { return 0; } - SSLOG(WARN, http2session) << "Tunneling failed"; + SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code; http2session->set_state(Http2Session::PROXY_FAILED); return 0; @@ -600,35 +509,72 @@ http_parser_settings htp_hooks = { }; } // namespace -int Http2Session::on_read_proxy() { - auto input = bufferevent_get_input(bev_); - +int Http2Session::downstream_read_proxy() { for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); + const void *data; + size_t datalen; + std::tie(data, datalen) = rb_.get(); - if (inputlen == 0) { - assert(evbuffer_get_length(input) == 0); - - return 0; + if (datalen == 0) { + break; } - auto mem = evbuffer_pullup(input, inputlen); - size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks, - reinterpret_cast(mem), inputlen); + reinterpret_cast(data), datalen); - if (evbuffer_drain(input, nread) != 0) { - SSLOG(FATAL, this) << "evbuffer_drain() failed"; - return -1; - } + rb_.drain(nread); auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get()); if (htperr != HPE_OK) { return -1; } + + switch (state_) { + case Http2Session::PROXY_CONNECTED: + // Initiate SSL/TLS handshake through established tunnel. + if (initiate_connection() != 0) { + return -1; + } + break; + case Http2Session::PROXY_FAILED: + return -1; + } } + return 0; +} + +int Http2Session::downstream_connect_proxy() { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connected to the proxy"; + } + std::string req = "CONNECT "; + req += get_config()->downstream_addrs[0].hostport.get(); + req += " HTTP/1.1\r\nHost: "; + req += get_config()->downstream_addrs[0].host.get(); + req += "\r\n"; + if (get_config()->downstream_http_proxy_userinfo) { + req += "Proxy-Authorization: Basic "; + size_t len = strlen(get_config()->downstream_http_proxy_userinfo.get()); + req += base64::encode(get_config()->downstream_http_proxy_userinfo.get(), + get_config()->downstream_http_proxy_userinfo.get() + + len); + req += "\r\n"; + } + req += "\r\n"; + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "HTTP proxy request headers\n" << req; + } + auto nwrite = wb_.write(req.c_str(), req.size()); + if (nwrite != req.size()) { + SSLOG(WARN, this) << "HTTP proxy request is too large"; + return -1; + } + on_write_ = &Http2Session::noop; + + signal_write(); + return 0; } void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) { @@ -734,9 +680,11 @@ namespace { void call_downstream_readcb(Http2Session *http2session, Downstream *downstream) { auto upstream = downstream->get_upstream(); - if (upstream) { - (upstream->get_downstream_readcb())( - http2session->get_bev(), downstream->get_downstream_connection()); + if (!upstream) { + return; + } + if (upstream->downstream_read(downstream->get_downstream_connection()) != 0) { + delete upstream->get_client_handler(); } } } // namespace @@ -785,45 +733,12 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, } } // namespace -namespace { -void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) { - auto http2session = static_cast(arg); - SSLOG(INFO, http2session) << "SETTINGS timeout"; - if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { - http2session->disconnect(); - return; - } - if (http2session->send() != 0) { - http2session->disconnect(); - } -} -} // namespace - -int Http2Session::start_settings_timer() { - int rv; - // We submit SETTINGS only once - if (settings_timerev_) { - return 0; - } - settings_timerev_ = evtimer_new(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 Http2Session::start_settings_timer() { + ev_timer_start(loop_, &settings_timer_); } void Http2Session::stop_settings_timer() { - if (settings_timerev_ == nullptr) { - return; - } - event_free(settings_timerev_); - settings_timerev_ = nullptr; + ev_timer_stop(loop_, &settings_timer_); } namespace { @@ -1265,9 +1180,7 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, if (frame->hd.type == NGHTTP2_SETTINGS && (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { - if (http2session->start_settings_timer() != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } + http2session->start_settings_timer(); } return 0; } @@ -1306,17 +1219,11 @@ int on_frame_not_send_callback(nghttp2_session *session, } } // namespace -namespace { -void connection_check_timeout_cb(evutil_socket_t fd, short what, void *arg) { - auto http2session = static_cast(arg); - SSLOG(INFO, http2session) << "connection check required"; - http2session->set_connection_check_state( - Http2Session::CONNECTION_CHECK_REQUIRED); -} -} // namespace - int Http2Session::on_connect() { int rv; + + state_ = Http2Session::CONNECTED; + if (ssl_ctx_) { const unsigned char *next_proto = nullptr; unsigned int next_proto_len; @@ -1342,6 +1249,7 @@ int Http2Session::on_connect() { return -1; } } + nghttp2_session_callbacks *callbacks; rv = nghttp2_session_callbacks_new(&callbacks); @@ -1412,10 +1320,10 @@ int Http2Session::on_connect() { } } - rv = bufferevent_write(bev_, NGHTTP2_CLIENT_CONNECTION_PREFACE, - NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN); - if (rv != 0) { - SSLOG(FATAL, this) << "bufferevent_write() failed"; + auto nwrite = wb_.write(NGHTTP2_CLIENT_CONNECTION_PREFACE, + NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN); + if (nwrite != NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN) { + SSLOG(FATAL, this) << "buffer is too small to send connection preface"; return -1; } @@ -1430,25 +1338,11 @@ int Http2Session::on_connect() { } } - rv = send(); - if (rv != 0) { - return -1; - } - if (must_terminate) { return 0; } - connection_check_timerev_ = - evtimer_new(evbase_, connection_check_timeout_cb, this); - if (connection_check_timerev_ == nullptr) { - return -1; - } - - rv = reset_connection_check_timer(); - if (rv != 0) { - return -1; - } + reset_connection_check_timer(); // submit pending request for (auto dconn : dconns_) { @@ -1473,25 +1367,29 @@ int Http2Session::on_connect() { upstream->on_downstream_abort_request(downstream, 400); } + signal_write(); return 0; } -int Http2Session::on_read() { +int Http2Session::do_read() { return read_(*this); } +int Http2Session::do_write() { return write_(*this); } + +int Http2Session::on_read() { return on_read_(*this); } +int Http2Session::on_write() { return on_write_(*this); } + +int Http2Session::downstream_read() { ssize_t rv = 0; - auto input = bufferevent_get_input(bev_); 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(data), nread); if (rv < 0) { SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: " @@ -1499,80 +1397,77 @@ int Http2Session::on_read() { return -1; } - if (evbuffer_drain(input, rv) != 0) { - SSLOG(FATAL, this) << "evbuffer_drain() faild"; - return -1; - } + rb_.drain(nread); } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "No more read/write for this HTTP2 session"; + } + return -1; + } + + signal_write(); + return 0; } -int Http2Session::on_write() { return send(); } - -int Http2Session::send() { - int rv; - uint8_t buf[16384]; - auto output = bufferevent_get_output(bev_); - util::EvbufferBuffer evbbuf(output, buf, sizeof(buf)); - for (;;) { - // Check buffer length and return WOULDBLOCK if it is large enough. - if (evbuffer_get_length(output) + evbbuf.get_buflen() > - Http2Session::OUTBUF_MAX_THRES) { - return NGHTTP2_ERR_WOULDBLOCK; +int Http2Session::downstream_write() { + 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); if (datalen < 0) { SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: " << nghttp2_strerror(datalen); - break; + return -1; } if (datalen == 0) { break; } - rv = evbbuf.add(data, datalen); - if (rv != 0) { - SSLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; + auto n = wb_.write(data, datalen); + if (n < static_cast(datalen)) { + data_pending_ = data + n; + data_pendinglen_ = datalen - n; + return 0; } } - rv = evbbuf.flush(); - if (rv != 0) { - SSLOG(FATAL, this) << "evbuffer_add() failed"; - 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 && wb_.rleft() == 0) { if (LOG_ENABLED(INFO)) { SSLOG(INFO, this) << "No more read/write for this session"; } return -1; } + return 0; } -void Http2Session::clear_notify() { - auto input = bufferevent_get_output(rdbev_); - if (evbuffer_drain(input, evbuffer_get_length(input)) != 0) { - SSLOG(FATAL, this) << "evbuffer_drain() failed"; - } - notified_ = false; +void Http2Session::signal_write() { write_requested_ = true; } + +void Http2Session::clear_write_request() { write_requested_ = false; } + +bool Http2Session::write_requested() const { return write_requested_; } + +struct ev_loop *Http2Session::get_loop() const { + return loop_; } -void Http2Session::notify() { - if (!notified_) { - if (bufferevent_write(wrbev_, "1", 1) != 0) { - SSLOG(FATAL, this) << "bufferevent_write failed"; - } - notified_ = true; - } -} - -bufferevent *Http2Session::get_bev() const { return bev_; } +ev_io *Http2Session::get_wev() { return &wev_; } int Http2Session::get_state() const { return state_; } @@ -1587,14 +1482,6 @@ int Http2Session::terminate_session(uint32_t error_code) { return 0; } -size_t Http2Session::get_outbuf_length() const { - if (bev_) { - return evbuffer_get_length(bufferevent_get_output(bev_)); - } else { - return OUTBUF_MAX_THRES; - } -} - SSL *Http2Session::get_ssl() const { return ssl_; } int Http2Session::consume(int32_t stream_id, size_t len) { @@ -1616,11 +1503,6 @@ int Http2Session::consume(int32_t stream_id, size_t len) { return 0; } -void Http2Session::reset_timeouts() { - bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout, - &get_config()->downstream_write_timeout); -} - bool Http2Session::can_push_request() const { return state_ == CONNECTED && connection_check_state_ == CONNECTION_CHECK_NONE; @@ -1638,31 +1520,18 @@ void Http2Session::start_checking_connection() { // ping frame to see whether connection is alive. nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, NULL); - notify(); + signal_write(); } -int Http2Session::reset_connection_check_timer() { - int rv; - timeval timeout = {5, 0}; - - rv = evtimer_add(connection_check_timerev_, &timeout); - if (rv == -1) { - return -1; - } - - return 0; +void Http2Session::reset_connection_check_timer() { + ev_timer_again(loop_, &connchk_timer_); } -int Http2Session::connection_alive() { - int rv; - - rv = reset_connection_check_timer(); - if (rv != 0) { - return -1; - } +void Http2Session::connection_alive() { + reset_connection_check_timer(); if (connection_check_state_ == CONNECTION_CHECK_NONE) { - return 0; + return; } SSLOG(INFO, this) << "Connection alive"; @@ -1691,12 +1560,262 @@ int Http2Session::connection_alive() { upstream->on_downstream_abort_request(downstream, 400); } - - return 0; } void Http2Session::set_connection_check_state(int state) { connection_check_state_ = state; } +int Http2Session::noop() { return 0; } + +int Http2Session::connected() { + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "Connection established"; + } + + ev_io_start(loop_, &rev_); + + if (ssl_) { + read_ = &Http2Session::tls_handshake; + write_ = &Http2Session::tls_handshake; + + return do_write(); + } + + read_ = &Http2Session::read_clear; + write_ = &Http2Session::write_clear; + + if (state_ == PROXY_CONNECTING) { + return do_write(); + } + + if (on_connect() != 0) { + state_ = CONNECT_FAILING; + return -1; + } + + return 0; +} + +int Http2Session::read_clear() { + ev_timer_again(loop_, &rt_); + + for (;;) { + if (rb_.rleft() && on_read() != 0) { + return -1; + } + rb_.reset(); + struct iovec iov[2]; + auto iovcnt = rb_.wiovec(iov); + + if (iovcnt > 0) { + ssize_t nread; + while ((nread = readv(fd_, iov, iovcnt)) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } + return -1; + } + + if (nread == 0) { + return -1; + } + + rb_.write(nread); + } + } + + return 0; +} + +int Http2Session::write_clear() { + ev_timer_again(loop_, &rt_); + + 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(loop_, &wev_); + ev_timer_again(loop_, &wt_); + return 0; + } + return -1; + } + wb_.drain(nwrite); + continue; + } + + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + wb_.reset(); + break; + } + } + + ev_io_stop(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + + return 0; +} + +int Http2Session::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: + ev_io_stop(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop_, &wev_); + ev_timer_again(loop_, &wt_); + return 0; + default: + return -1; + } + } + + ev_io_stop(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + + if (LOG_ENABLED(INFO)) { + SSLOG(INFO, this) << "SSL/TLS handshake completed"; + } + if (LOG_ENABLED(INFO)) { + if (SSL_session_reused(ssl_)) { + CLOG(INFO, this) << "SSL/TLS session reused"; + } + } + + if (!get_config()->downstream_no_tls && !get_config()->insecure && + check_cert() != 0) { + return -1; + } + + read_ = &Http2Session::read_tls; + write_ = &Http2Session::write_tls; + + if (on_connect() != 0) { + state_ = CONNECT_FAILING; + return -1; + } + + return 0; +} + +int Http2Session::read_tls() { + ev_timer_again(loop_, &rt_); + + for (;;) { + if (rb_.rleft() && on_read() != 0) { + return -1; + } + + struct iovec iov[2]; + auto iovcnt = rb_.wiovec(iov); + if (iovcnt > 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: + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop_, &wev_); + ev_timer_again(loop_, &wt_); + return 0; + default: + return -1; + } + } + + rb_.write(rv); + } + } +} + +int Http2Session::write_tls() { + ev_timer_again(loop_, &rt_); + + 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(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(loop_, &wev_); + ev_timer_again(loop_, &wt_); + return 0; + default: + return -1; + } + } + + wb_.drain(rv); + + continue; + } + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + ev_io_stop(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + + return 0; +} + +bool Http2Session::should_hard_fail() const { + switch (state_) { + case PROXY_CONNECTING: + case PROXY_FAILED: + case CONNECTING: + case CONNECT_FAILING: + return true; + default: + return false; + } +} + } // namespace shrpx diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h index 1237d974..d575c232 100644 --- a/src/shrpx_http2_session.h +++ b/src/shrpx_http2_session.h @@ -32,13 +32,16 @@ #include -#include -#include +#include #include #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 dconns_; std::set streams_; + std::function read_, write_; + std::function on_read_, on_write_; // Used to parse the response from HTTP proxy std::unique_ptr 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 diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 4f6a9d12..12f5af4c 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -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(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(arg); - upstream->perform_send(); +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto upstream = static_cast(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(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(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(ptr); +int Http2Upstream::downstream_read(DownstreamConnection *dconn) { auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(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(ptr); +int Http2Upstream::downstream_eof(DownstreamConnection *dconn) { auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(&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(source->ptr); auto upstream = static_cast(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(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_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 diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h index 3cea0119..7f534e78 100644 --- a/src/shrpx_http2_upstream.h +++ b/src/shrpx_http2_upstream.h @@ -29,11 +29,15 @@ #include +#include + #include #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); 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 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_; }; diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 9c9816ff..6903dff7 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -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(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(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(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(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( + &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(&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(arg); +void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto dconn = static_cast(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(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(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(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(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(mem), inputlen); + auto nproc = http_parser_execute(&response_htp_, &htp_hooks, + reinterpret_cast(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 diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h index c0b40633..66132325 100644 --- a/src/shrpx_http_downstream_connection.h +++ b/src/shrpx_http_downstream_connection.h @@ -27,9 +27,6 @@ #include "shrpx.h" -#include -#include - #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 diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 93fd8b57..3b3a9ebc 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -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(mem), inputlen); + reinterpret_cast(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(mem), inputlen); + &htp_, &htp_hooks, reinterpret_cast(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(ptr); +int HttpsUpstream::downstream_read(DownstreamConnection *dconn) { auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(ptr); - auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(ptr); - auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(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) { @@ -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 diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h index ef0efa9c..694d056b 100644 --- a/src/shrpx_https_upstream.h +++ b/src/shrpx_https_upstream.h @@ -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); 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_; IOControl ioctrl_; }; diff --git a/src/shrpx_io_control.cc b/src/shrpx_io_control.cc index a619e2e1..38d75753 100644 --- a/src/shrpx_io_control.cc +++ b/src/shrpx_io_control.cc @@ -26,42 +26,42 @@ #include +#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(); } } diff --git a/src/shrpx_io_control.h b/src/shrpx_io_control.h index 65a1fc23..f3dd24f5 100644 --- a/src/shrpx_io_control.h +++ b/src/shrpx_io_control.h @@ -29,8 +29,9 @@ #include -#include -#include +#include + +#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_; }; diff --git a/src/shrpx_listen_handler.cc b/src/shrpx_listen_handler.cc index 8a6f5d5e..55cfa9bc 100644 --- a/src/shrpx_listen_handler.cc +++ b/src/shrpx_listen_handler.cc @@ -29,10 +29,7 @@ #include #include -#include - #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(arg); +void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) { + auto h = static_cast(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()), 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()), + 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(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(); - 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(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(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(); +void ListenHandler::create_http2_session() { + http2session_ = util::make_unique(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(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 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 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(); } } diff --git a/src/shrpx_listen_handler.h b/src/shrpx_listen_handler.h index ed61af44..e6b5ff60 100644 --- a/src/shrpx_listen_handler.h +++ b/src/shrpx_listen_handler.h @@ -32,61 +32,48 @@ #include #include -#ifndef NOTHREADS -#include -#endif // NOTHREADS #include -#include -#include -#include +#include #include "shrpx_downstream_connection_pool.h" namespace shrpx { -struct WorkerInfo { -#ifndef NOTHREADS - std::future 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 h); + AcceptHandler *get_acceptor4() const; + void set_acceptor6(std::unique_ptr 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> workers_; - event_base *evbase_; + std::vector> 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_; std::unique_ptr 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 acceptor4_; + std::unique_ptr acceptor6_; + ev_timer disable_acceptor_timer_; std::unique_ptr worker_stat_; - size_t num_worker_shutdown_; unsigned int worker_round_robin_cnt_; }; diff --git a/src/shrpx_log.h b/src/shrpx_log.h index dfb66b00..901f022a 100644 --- a/src/shrpx_log.h +++ b/src/shrpx_log.h @@ -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) \ diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc new file mode 100644 index 00000000..259140b6 --- /dev/null +++ b/src/shrpx_rate_limit.cc @@ -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(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 diff --git a/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h new file mode 100644 index 00000000..db51df40 --- /dev/null +++ b/src/shrpx_rate_limit.h @@ -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 + +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 diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 08e6dd50..1ebe927b 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -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(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(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(ptr); +int SpdyUpstream::downstream_read(DownstreamConnection *dconn) { auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(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(ptr); +int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) { auto downstream = dconn->get_downstream(); - auto upstream = static_cast(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(&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(source->ptr); auto upstream = static_cast(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(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(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 diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h index b42a9a4f..b2753c3f 100644 --- a/src/shrpx_spdy_upstream.h +++ b/src/shrpx_spdy_upstream.h @@ -29,12 +29,14 @@ #include +#include + #include #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); - nghttp2::util::EvbufferBuffer sendbuf; - private: + // must be put before downstream_queue_ + MemchunkPool4K mcpool_; DownstreamQueue downstream_queue_; ClientHandler *handler_; spdylay_session *session_; diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 93da8f2c..deddd9ae 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -36,9 +36,6 @@ #include #include -#include -#include - #include #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 { diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index 90c1159d..813f91c6 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -32,7 +32,7 @@ #include #include -#include +#include 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); diff --git a/src/shrpx_thread_event_receiver.cc b/src/shrpx_thread_event_receiver.cc index c5f05371..162ac0ea 100644 --- a/src/shrpx_thread_event_receiver.cc +++ b/src/shrpx_thread_event_receiver.cc @@ -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()) {} -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 diff --git a/src/shrpx_thread_event_receiver.h b/src/shrpx_thread_event_receiver.h index e0595acb..710dbf0e 100644 --- a/src/shrpx_thread_event_receiver.h +++ b/src/shrpx_thread_event_receiver.h @@ -31,8 +31,6 @@ #include -#include - #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 worker_stat_; }; diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h index 5b1e06b6..9f2792db 100644 --- a/src/shrpx_upstream.h +++ b/src/shrpx_upstream.h @@ -26,28 +26,29 @@ #define SHRPX_UPSTREAM_H #include "shrpx.h" - -#include - #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 diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index cc45552a..3d00954c 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -25,96 +25,135 @@ #include "shrpx_worker.h" #include -#include #include -#include -#include - #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(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()) { + ev_async_init(&w_, eventcb); + w_.data = this; + ev_async_start(loop_, &w_); + + if (get_config()->downstream_proto == PROTO_HTTP2) { + http2session_ = util::make_unique(loop_, cl_ssl_ctx_); + } else { + http1_connect_blocker_ = util::make_unique(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(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_new(), event_base_free); - if (!evbase) { - LOG(ERROR) << "event_base_new() failed"; - return; - } - auto bev = std::unique_ptr( - 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; - std::unique_ptr http1_connect_blocker; - if (get_config()->downstream_proto == PROTO_HTTP2) { - http2session = util::make_unique(evbase.get(), cl_ssl_ctx_); - if (http2session->init_notification() == -1) { - DIE(); - } - } else { - http1_connect_blocker = util::make_unique(); - if (http1_connect_blocker->init(evbase.get()) == -1) { - DIE(); - } - } - - auto receiver = util::make_unique( - 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 g(m_); + + q_.push_back(event); + } + + ev_async_send(loop_, &w_); +} + +void Worker::process_events() { + std::deque q; + { + std::lock_guard 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 diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index ebfff386..4d441349 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -27,13 +27,26 @@ #include "shrpx.h" +#include +#include +#include +#ifndef NOTHREADS +#include +#endif // NOTHREADS + #include #include -#include "shrpx_listen_handler.h" +#include + +#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 fut_; +#endif // NOTHREADS + std::mutex m_; + std::deque 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_; + std::unique_ptr http1_connect_blocker_; + std::unique_ptr worker_stat_; +}; } // namespace shrpx diff --git a/src/util.cc b/src/util.cc index cce54329..dcbea80c 100644 --- a/src/util.cc +++ b/src/util.cc @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -827,6 +829,48 @@ std::vector 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(&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 diff --git a/src/util.h b/src/util.h index dd940e71..a8150abf 100644 --- a/src/util.h +++ b/src/util.h @@ -466,6 +466,14 @@ template 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