diff --git a/.travis.yml b/.travis.yml index 59d85960..59d8c3a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ before_install: libcunit1-dev libssl-dev libxml2-dev + libev-dev libevent-dev libjansson-dev libjemalloc-dev diff --git a/README.rst b/README.rst index c1e8b3f7..14f565a1 100644 --- a/README.rst +++ b/README.rst @@ -71,7 +71,7 @@ To build and run the application programs (``nghttp``, ``nghttpd`` and required: * OpenSSL >= 1.0.1 -* libevent-openssl >= 2.0.8 +* libev >= 4.15 * zlib >= 1.2.3 ALPN support requires unreleased version OpenSSL >= 1.0.2. @@ -90,6 +90,10 @@ The HPACK tools require the following package: * jansson >= 2.5 +To build sources under examples directory, libevent is required: + +* libevent-openssl >= 2.0.8 + To mitigate heap fragmentation in long running server programs (``nghttpd`` and ``nghttpx``), jemalloc is recommended: @@ -119,6 +123,7 @@ installed: * libcunit1-dev * libssl-dev * libxml2-dev +* libev-dev * libevent-dev * libjansson-dev * libjemalloc-dev diff --git a/configure.ac b/configure.ac index 95c971fa..905c6be8 100644 --- a/configure.ac +++ b/configure.ac @@ -275,6 +275,21 @@ fi AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) +# libev (for src) +# libev does not have pkg-config file. Check it in an old way. +LIBS_OLD=$LIBS +AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no]) +if test "x${have_libev}" = "xyes"; then + AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no]) + if test "x${have_libev}" = "xyes"; then + LIBEV_LIBS=-lev + LIBEV_CFLAGS= + AC_SUBST([LIBEV_LIBS]) + AC_SUBST([LIBEV_CFLAGS]) + fi +fi +LIBS=$LIBS_OLD + # openssl (for src) PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1], [have_openssl=yes], [have_openssl=no]) @@ -282,7 +297,7 @@ if test "x${have_openssl}" = "xno"; then AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) fi -# libevent_openssl (for src) +# libevent_openssl (for examples) # 2.0.8 is required because we use evconnlistener_set_error_cb() PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8], [have_libevent_openssl=yes], [have_libevent_openssl=no]) @@ -375,12 +390,12 @@ if test "x${request_asio_lib}" = "xyes"; then fi # The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL -# and libevent_openssl +# and libev enable_app=no if test "x${request_app}" != "xno" && test "x${have_zlib}" = "xyes" && test "x${have_openssl}" = "xyes" && - test "x${have_libevent_openssl}" = "xyes"; then + test "x${have_libev}" = "xyes"; then enable_app=yes fi @@ -505,6 +520,7 @@ if test "x$cross_compiling" != "xyes"; then fi AC_CHECK_FUNCS([ \ _Exit \ + accept4 \ getpwnam \ memmove \ memset \ @@ -649,6 +665,7 @@ AC_MSG_NOTICE([summary of build options: Libs: OpenSSL: ${have_openssl} Libxml2: ${have_libxml2} + Libev: ${have_libev} Libevent(SSL): ${have_libevent_openssl} Spdylay: ${have_spdylay} Jansson: ${have_jansson} diff --git a/genheaderfunc.py b/genheaderfunc.py new file mode 100755 index 00000000..17894359 --- /dev/null +++ b/genheaderfunc.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +HEADERS = [ + ':authority', + ':method', + ':path', + ':scheme', + ':status', + ':host', # for spdy + 'expect', + 'host', + 'if-modified-since', + "te", + "cookie", + "http2-settings", + "server", + "via", + "x-forwarded-for", + "x-forwarded-proto", + "alt-svc", + "content-length", + "location", + # disallowed h1 headers + 'connection', + 'keep-alive', + 'proxy-connection', + 'transfer-encoding', + 'upgrade' +] + +def to_enum_hd(k): + res = 'HD_' + for c in k.upper(): + if c == ':' or c == '-': + res += '_' + continue + res += c + return res + +def build_header(headers): + res = {} + for k in headers: + size = len(k) + if size not in res: + res[size] = {} + ent = res[size] + c = k[-1] + if c not in ent: + ent[c] = [] + ent[c].append(k) + + return res + +def gen_enum(): + print '''\ +enum {''' + for k in sorted(HEADERS): + print '''\ + {},'''.format(to_enum_hd(k)) + print '''\ + HD_MAXIDX, +};''' + +def gen_index_header(): + print '''\ +int lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) {''' + b = build_header(HEADERS) + for size in sorted(b.keys()): + ents = b[size] + print '''\ + case {}:'''.format(size) + print '''\ + switch (name[namelen - 1]) {''' + for c in sorted(ents.keys()): + headers = sorted(ents[c]) + print '''\ + case '{}':'''.format(c) + for k in headers: + print '''\ + if (util::streq("{}", name, {})) {{ + return {}; + }}'''.format(k[:-1], size - 1, to_enum_hd(k)) + print '''\ + break;''' + print '''\ + } + break;''' + print '''\ + } + return -1; +}''' + +if __name__ == '__main__': + gen_enum() + print '' + gen_index_header() diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 7f428f2b..8072905b 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -36,29 +36,16 @@ #include #include #include +#include +#include #include #include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#include "nghttp2_helper.h" - -#ifdef __cplusplus -} -#endif - #include "app_helper.h" #include "http2.h" #include "util.h" -#include "libevent_util.h" #include "ssl.h" #ifndef O_BINARY @@ -90,7 +77,12 @@ void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; } namespace { void append_nv(Stream *stream, const std::vector &nva) { - for (auto &nv : nva) { + for (size_t i = 0; i < nva.size(); ++i) { + auto &nv = nva[i]; + auto token = http2::lookup_token(nv.name, nv.namelen); + if (token != -1) { + http2::index_header(stream->hdidx, token, i); + } http2::add_header(stream->headers, nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX); } @@ -98,7 +90,7 @@ void append_nv(Stream *stream, const std::vector &nva) { } // namespace Config::Config() - : stream_read_timeout{60, 0}, stream_write_timeout{60, 0}, + : stream_read_timeout(60.), stream_write_timeout(60.), session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1), header_table_size(-1), port(0), verbose(false), daemon(false), verify_client(false), no_tls(false), error_gzip(false), @@ -109,33 +101,16 @@ Config::Config() Config::~Config() { nghttp2_option_del(session_option); } -Stream::Stream(Http2Handler *handler, int32_t stream_id) - : handler(handler), rtimer(nullptr), wtimer(nullptr), body_left(0), - stream_id(stream_id), file(-1) { - headers.reserve(10); -} - -Stream::~Stream() { - if (file != -1) { - close(file); - } - - if (wtimer) { - event_free(wtimer); - } - - if (rtimer) { - event_free(rtimer); - } -} - namespace { -void stream_timeout_cb(evutil_socket_t fd, short what, void *arg) { +void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { int rv; - auto stream = static_cast(arg); + auto stream = static_cast(w->data); 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(); @@ -154,19 +129,15 @@ void stream_timeout_cb(evutil_socket_t fd, short what, void *arg) { namespace { void add_stream_read_timeout(Stream *stream) { auto hd = stream->handler; - auto config = hd->get_config(); - - evtimer_add(stream->rtimer, &config->stream_read_timeout); + ev_timer_again(hd->get_loop(), &stream->rtimer); } } // namespace namespace { void add_stream_read_timeout_if_pending(Stream *stream) { auto hd = stream->handler; - auto config = hd->get_config(); - - if (evtimer_pending(stream->rtimer, nullptr)) { - evtimer_add(stream->rtimer, &config->stream_read_timeout); + if (ev_is_active(&stream->rtimer)) { + ev_timer_again(hd->get_loop(), &stream->rtimer); } } } // namespace @@ -174,25 +145,21 @@ void add_stream_read_timeout_if_pending(Stream *stream) { namespace { void add_stream_write_timeout(Stream *stream) { auto hd = stream->handler; - auto config = hd->get_config(); - - evtimer_add(stream->wtimer, &config->stream_write_timeout); + ev_timer_again(hd->get_loop(), &stream->wtimer); } } // namespace namespace { void remove_stream_read_timeout(Stream *stream) { - if (stream->rtimer) { - evtimer_del(stream->rtimer); - } + auto hd = stream->handler; + ev_timer_stop(hd->get_loop(), &stream->rtimer); } } // namespace namespace { void remove_stream_write_timeout(Stream *stream) { - if (stream->wtimer) { - evtimer_del(stream->wtimer); - } + auto hd = stream->handler; + ev_timer_stop(hd->get_loop(), &stream->wtimer); } } // namespace @@ -201,7 +168,7 @@ std::shared_ptr cached_date; } // namespace namespace { -void refresh_cb(evutil_socket_t sig, short events, void *arg) { +void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) { cached_date = std::make_shared(util::http_date(time(nullptr))); } } // namespace @@ -212,9 +179,9 @@ void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config); class Sessions { public: - Sessions(event_base *evbase, const Config *config, SSL_CTX *ssl_ctx) - : evbase_(evbase), config_(config), ssl_ctx_(ssl_ctx), - callbacks_(nullptr), next_session_id_(1) { + Sessions(struct ev_loop *loop, const Config *config, SSL_CTX *ssl_ctx) + : loop_(loop), config_(config), ssl_ctx_(ssl_ctx), callbacks_(nullptr), + next_session_id_(1) { nghttp2_session_callbacks_new(&callbacks_); fill_callback(callbacks_, config_); @@ -242,7 +209,9 @@ public: return ssl; } const Config *get_config() const { return config_; } - event_base *get_evbase() const { return evbase_; } + struct ev_loop *get_loop() const { + return loop_; + } int64_t get_next_session_id() { auto session_id = next_session_id_; if (next_session_id_ == std::numeric_limits::max()) { @@ -254,9 +223,7 @@ public: } const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; } void accept_connection(int fd) { - int val = 1; - (void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&val), sizeof(val)); + util::make_socket_nodelay(fd); SSL *ssl = nullptr; if (ssl_ctx_) { ssl = ssl_session_new(fd); @@ -278,13 +245,36 @@ public: private: std::set handlers_; - event_base *evbase_; + struct ev_loop *loop_; const Config *config_; SSL_CTX *ssl_ctx_; nghttp2_session_callbacks *callbacks_; int64_t next_session_id_; }; +Stream::Stream(Http2Handler *handler, int32_t stream_id) + : handler(handler), body_left(0), stream_id(stream_id), file(-1) { + auto config = handler->get_config(); + ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout); + ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout); + rtimer.data = this; + wtimer.data = this; + + headers.reserve(10); + + http2::init_hdidx(hdidx); +} + +Stream::~Stream() { + if (file != -1) { + close(file); + } + + auto loop = handler->get_loop(); + ev_timer_stop(loop, &rtimer); + ev_timer_stop(loop, &wtimer); +} + namespace { void on_session_closed(Http2Handler *hd, int64_t session_id) { if (hd->get_config()->verbose) { @@ -295,38 +285,18 @@ void on_session_closed(Http2Handler *hd, int64_t session_id) { } } // namespace -Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl, - int64_t session_id) - : session_id_(session_id), session_(nullptr), sessions_(sessions), - ssl_(ssl), bev_(nullptr), settings_timerev_(nullptr), fd_(fd) {} - -Http2Handler::~Http2Handler() { - on_session_closed(this, session_id_); - if (settings_timerev_) { - event_free(settings_timerev_); - } - nghttp2_session_del(session_); - if (ssl_) { - SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN); - SSL_shutdown(ssl_); - } - if (bev_) { - bufferevent_disable(bev_, EV_READ | EV_WRITE); - bufferevent_free(bev_); - } - if (ssl_) { - SSL_free(ssl_); - } - shutdown(fd_, SHUT_WR); - close(fd_); +namespace { +void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto hd = static_cast(w->data); + hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT); + hd->on_write(); } - -void Http2Handler::remove_self() { sessions_->remove_handler(this); } +} // namespace namespace { -void readcb(bufferevent *bev, void *arg) { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { int rv; - auto handler = static_cast(arg); + auto handler = static_cast(w->data); rv = handler->on_read(); if (rv == -1) { @@ -336,9 +306,9 @@ void readcb(bufferevent *bev, void *arg) { } // namespace namespace { -void writecb(bufferevent *bev, void *arg) { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { int rv; - auto handler = static_cast(arg); + auto handler = static_cast(w->data); rv = handler->on_write(); if (rv == -1) { @@ -347,63 +317,72 @@ void writecb(bufferevent *bev, void *arg) { } } // namespace -namespace { -void eventcb(bufferevent *bev, short events, void *arg) { - auto handler = static_cast(arg); +Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl, + int64_t session_id) + : session_id_(session_id), session_(nullptr), sessions_(sessions), + ssl_(ssl), data_pending_(nullptr), data_pendinglen_(0), fd_(fd) { + ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.); + ev_io_init(&wev_, writecb, fd, EV_WRITE); + ev_io_init(&rev_, readcb, fd, EV_READ); - if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR | BEV_EVENT_TIMEOUT)) { - delete_handler(handler); + settings_timerev_.data = this; + wev_.data = this; + rev_.data = this; - return; - } + auto loop = sessions_->get_loop(); + ev_io_start(loop, &rev_); - if (events & BEV_EVENT_CONNECTED) { - if (handler->get_sessions()->get_config()->verbose) { - std::cerr << "SSL/TLS handshake completed" << std::endl; - } - - if (handler->verify_npn_result() != 0) { - delete_handler(handler); - - return; - } - - if (handler->on_connect() != 0) { - delete_handler(handler); - - return; - } - } -} -} // namespace - -int Http2Handler::setup_bev() { - auto evbase = sessions_->get_evbase(); - - if (ssl_) { - bev_ = bufferevent_openssl_socket_new( - evbase, fd_, ssl_, BUFFEREVENT_SSL_ACCEPTING, BEV_OPT_DEFER_CALLBACKS); + if (ssl) { + SSL_set_accept_state(ssl); + read_ = &Http2Handler::tls_handshake; + write_ = &Http2Handler::tls_handshake; } else { - bev_ = bufferevent_socket_new(evbase, fd_, BEV_OPT_DEFER_CALLBACKS); + read_ = &Http2Handler::read_clear; + write_ = &Http2Handler::write_clear; } - - bufferevent_enable(bev_, EV_READ); - bufferevent_setcb(bev_, readcb, writecb, eventcb, this); - - return 0; } -int Http2Handler::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 break if it is large enough. - if (evbuffer_get_length(output) > 0) { - break; +Http2Handler::~Http2Handler() { + on_session_closed(this, session_id_); + nghttp2_session_del(session_); + if (ssl_) { + SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(ssl_); + } + auto loop = sessions_->get_loop(); + ev_timer_stop(loop, &settings_timerev_); + ev_io_stop(loop, &rev_); + ev_io_stop(loop, &wev_); + if (ssl_) { + SSL_free(ssl_); + } + shutdown(fd_, SHUT_WR); + close(fd_); +} + +void Http2Handler::remove_self() { sessions_->remove_handler(this); } + +struct ev_loop *Http2Handler::get_loop() const { + return sessions_->get_loop(); +} + +int Http2Handler::setup_bev() { return 0; } + +int Http2Handler::fill_wb() { + if (data_pending_) { + auto n = std::min(wb_.wleft(), data_pendinglen_); + wb_.write(data_pending_, n); + if (n < data_pendinglen_) { + data_pending_ += n; + data_pendinglen_ -= n; + return 0; } + data_pending_ = nullptr; + data_pendinglen_ = 0; + } + + for (;;) { const uint8_t *data; auto datalen = nghttp2_session_mem_send(session_, &data); @@ -415,62 +394,220 @@ int Http2Handler::send() { if (datalen == 0) { break; } - rv = evbbuf.add(data, datalen); - if (rv != 0) { - std::cerr << "evbuffer_add() failed" << std::endl; + auto n = wb_.write(data, datalen); + if (n < static_cast(datalen)) { + data_pending_ = data + n; + data_pendinglen_ = datalen - n; + break; + } + } + return 0; +} + +int Http2Handler::read_clear() { + int rv; + 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) { + break; + } + return -1; + } + if (nread == 0) { + return -1; + } + rv = nghttp2_session_mem_recv(session_, buf, nread); + if (rv < 0) { + std::cerr << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; return -1; } } - rv = evbbuf.flush(); - if (rv != 0) { - std::cerr << "evbuffer_add() failed" << std::endl; - return -1; + return write_(*this); +} + +int Http2Handler::write_clear() { + auto loop = sessions_->get_loop(); + 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_); + return 0; + } + return -1; + } + wb_.drain(nwrite); + continue; + } + wb_.reset(); + if (fill_wb() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + if (wb_.rleft() == 0) { + ev_io_stop(loop, &wev_); + } else { + ev_io_start(loop, &wev_); } 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 Http2Handler::on_read() { - int rv; +int Http2Handler::tls_handshake() { + ev_io_stop(sessions_->get_loop(), &wev_); - auto input = bufferevent_get_input(bev_); - auto len = evbuffer_get_length(input); - if (len == 0) { - return 0; - } - auto data = evbuffer_pullup(input, -1); + auto rv = SSL_do_handshake(ssl_); - rv = nghttp2_session_mem_recv(session_, data, len); - if (rv < 0) { - std::cerr << "nghttp2_session_mem_recv() returned error: " - << nghttp2_strerror(rv) << std::endl; + if (rv == 0) { return -1; } - if (evbuffer_drain(input, len) == -1) { - std::cerr << "evbuffer_drain() failed" << std::endl; + 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(sessions_->get_loop(), &wev_); + return 0; + default: + return -1; + } } - return send(); + if (sessions_->get_config()->verbose) { + std::cerr << "SSL/TLS handshake completed" << std::endl; + } + + if (verify_npn_result() != 0) { + return -1; + } + + read_ = &Http2Handler::read_tls; + write_ = &Http2Handler::write_tls; + + if (on_connect() != 0) { + return -1; + } + + return 0; } -int Http2Handler::on_write() { return send(); } +int Http2Handler::read_tls() { + uint8_t buf[8192]; -namespace { -void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) { - auto hd = static_cast(arg); - hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT); - hd->on_write(); + 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: + goto fin; + case SSL_ERROR_WANT_WRITE: + ev_io_start(sessions_->get_loop(), &wev_); + goto fin; + default: + return -1; + } + } + + auto nread = rv; + rv = nghttp2_session_mem_recv(session_, buf, nread); + if (rv < 0) { + std::cerr << "nghttp2_session_mem_recv() returned error: " + << nghttp2_strerror(rv) << std::endl; + return -1; + } + } + +fin: + return write_(*this); } -} // namespace + +int Http2Handler::write_tls() { + auto loop = sessions_->get_loop(); + 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_); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(sessions_->get_loop(), &wev_); + return 0; + default: + return -1; + } + } + + wb_.drain(rv); + continue; + } + wb_.reset(); + if (fill_wb() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + if (wb_.rleft() == 0) { + ev_io_stop(loop, &wev_); + } else { + ev_io_start(loop, &wev_); + } + + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) { + return -1; + } + + return 0; +} + +int Http2Handler::on_read() { return read_(*this); } + +int Http2Handler::on_write() { return write_(*this); } int Http2Handler::on_connect() { int r; @@ -495,12 +632,8 @@ int Http2Handler::on_connect() { if (r != 0) { return r; } - assert(settings_timerev_ == nullptr); - settings_timerev_ = - evtimer_new(sessions_->get_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_start(sessions_->get_loop(), &settings_timerev_); return on_write(); } @@ -594,13 +727,12 @@ int Http2Handler::submit_non_final_response(const std::string &status, int Http2Handler::submit_push_promise(Stream *stream, const std::string &push_path) { - auto itr = - std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header(":authority", "")); + auto authority = + http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers); - if (itr == std::end(stream->headers) || (*itr).name != ":authority") { - itr = std::lower_bound(std::begin(stream->headers), - std::end(stream->headers), Header("host", "")); + if (!authority) { + authority = + http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); } auto nva = std::vector{ @@ -608,7 +740,7 @@ int Http2Handler::submit_push_promise(Stream *stream, http2::make_nv_ls(":path", push_path), get_config()->no_tls ? http2::make_nv_ll(":scheme", "http") : http2::make_nv_ll(":scheme", "https"), - http2::make_nv_ls(":authority", (*itr).value)}; + http2::make_nv_ls(":authority", authority->value)}; auto promised_stream_id = nghttp2_submit_push_promise( session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(), @@ -661,11 +793,7 @@ const Config *Http2Handler::get_config() const { } void Http2Handler::remove_settings_timer() { - if (settings_timerev_) { - evtimer_del(settings_timerev_); - event_free(settings_timerev_); - settings_timerev_ = nullptr; - } + ev_timer_stop(sessions_->get_loop(), &settings_timerev_); } void Http2Handler::terminate_session(uint32_t error_code) { @@ -760,10 +888,13 @@ namespace { void prepare_redirect_response(Stream *stream, Http2Handler *hd, const std::string &path, const std::string &status) { - auto scheme = http2::get_unique_header(stream->headers, ":scheme"); - auto authority = http2::get_unique_header(stream->headers, ":authority"); + auto scheme = + http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers); + auto authority = + http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers); if (!authority) { - authority = http2::get_unique_header(stream->headers, "host"); + authority = + http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers); } auto redirect_url = scheme->value; @@ -782,17 +913,15 @@ void prepare_response(Stream *stream, Http2Handler *hd, bool allow_push = true) { int rv; auto reqpath = - (*std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header(":path", ""))).value; + http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value; auto ims = - std::lower_bound(std::begin(stream->headers), std::end(stream->headers), - Header("if-modified-since", "")); + get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers); time_t last_mod = 0; bool last_mod_found = false; - if (ims != std::end(stream->headers) && (*ims).name == "if-modified-since") { + if (ims) { last_mod_found = true; - last_mod = util::parse_http_date((*ims).value); + last_mod = util::parse_http_date(ims->value); } auto query_pos = reqpath.find("?"); std::string url; @@ -875,10 +1004,6 @@ void prepare_response(Stream *stream, Http2Handler *hd, } } // namespace -namespace { -const char *REQUIRED_HEADERS[] = {":method", ":path", ":scheme", nullptr}; -} // namespace - namespace { int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, @@ -902,10 +1027,12 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { if ((!stream->headers.empty() && stream->headers.back().name.c_str()[0] != ':') || - !http2::check_http2_request_pseudo_header(name, namelen)) { + !http2::check_http2_request_pseudo_header(stream->hdidx, token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -913,31 +1040,19 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } } + if (!http2::http2_header_allowed(token)) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + http2::index_header(stream->hdidx, token, stream->headers.size()); http2::add_header(stream->headers, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); return 0; } } // namespace -namespace { -int setup_stream_timeout(Stream *stream) { - auto hd = stream->handler; - auto evbase = hd->get_sessions()->get_evbase(); - - stream->rtimer = evtimer_new(evbase, stream_timeout_cb, stream); - if (!stream->rtimer) { - return -1; - } - - stream->wtimer = evtimer_new(evbase, stream_timeout_cb, stream); - if (!stream->wtimer) { - return -1; - } - - return 0; -} -} // namespace - namespace { int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { @@ -949,10 +1064,6 @@ int on_begin_headers_callback(nghttp2_session *session, } auto stream = util::make_unique(hd, frame->hd.stream_id); - if (setup_stream_timeout(stream.get()) != 0) { - hd->submit_rst_stream(stream.get(), NGHTTP2_INTERNAL_ERROR); - return 0; - } add_stream_read_timeout(stream.get()); @@ -997,27 +1108,13 @@ int hd_on_frame_recv_callback(nghttp2_session *session, if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { - http2::normalize_headers(stream->headers); - if (!http2::check_http2_request_headers(stream->headers)) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - for (size_t i = 0; REQUIRED_HEADERS[i]; ++i) { - if (!http2::get_unique_header(stream->headers, REQUIRED_HEADERS[i])) { - hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); - return 0; - } - } - // intermediary translating from HTTP/1 request to HTTP/2 may - // not produce :authority header field. In this case, it should - // provide host HTTP/1.1 header field. - if (!http2::get_unique_header(stream->headers, ":authority") && - !http2::get_unique_header(stream->headers, "host")) { + if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) { hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR); return 0; } - auto expect100 = http2::get_header(stream->headers, "expect"); + auto expect100 = + http2::get_header(stream->hdidx, http2::HD_EXPECT, stream->headers); if (expect100 && util::strieq("100-continue", expect100->value.c_str())) { hd->submit_non_final_response("100", frame->hd.stream_id); @@ -1099,13 +1196,6 @@ int hd_on_frame_send_callback(nghttp2_session *session, return 0; } - if (setup_stream_timeout(promised_stream) != 0) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, promised_stream_id, - NGHTTP2_INTERNAL_ERROR); - - return 0; - } - add_stream_read_timeout_if_pending(stream); add_stream_write_timeout(stream); @@ -1195,38 +1285,39 @@ struct ClientInfo { int fd; }; +struct Worker { + std::unique_ptr sessions; + ev_async w; + // protectes q + std::mutex m; + std::deque q; +}; + namespace { -void worker_readcb(bufferevent *bev, void *arg) { - auto sessions = static_cast(arg); - auto input = bufferevent_get_input(bev); - while (evbuffer_get_length(input) >= sizeof(ClientInfo)) { - ClientInfo client; - if (evbuffer_remove(input, &client, sizeof(client)) == -1) { - std::cerr << "evbuffer_remove() failed" << std::endl; - } - sessions->accept_connection(client.fd); +void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) { + auto worker = static_cast(w->data); + auto &sessions = worker->sessions; + + std::deque q; + { + std::lock_guard lock(worker->m); + q.swap(worker->q); + } + + for (auto c : q) { + sessions->accept_connection(c.fd); } } } // namespace namespace { -void run_worker(int thread_id, int fd, SSL_CTX *ssl_ctx, const Config *config) { - auto evbase = event_base_new(); - auto bev = bufferevent_socket_new(evbase, fd, BEV_OPT_DEFER_CALLBACKS | - BEV_OPT_CLOSE_ON_FREE); - auto sessions = Sessions(evbase, config, ssl_ctx); - - bufferevent_enable(bev, EV_READ); - bufferevent_setcb(bev, worker_readcb, nullptr, nullptr, &sessions); - event_base_loop(evbase, 0); -} +void run_worker(Worker *worker) { ev_run(worker->sessions->get_loop(), 0); } } // namespace -class ListenEventHandler { +class AcceptHandler { public: - ListenEventHandler(Sessions *sessions, const Config *config) + AcceptHandler(Sessions *sessions, const Config *config) : sessions_(sessions), config_(config), next_worker_(0) { - int rv; if (config_->num_worker == 1) { return; } @@ -1234,47 +1325,42 @@ public: if (config_->verbose) { std::cerr << "spawning thread #" << i << std::endl; } - int socks[2]; - rv = socketpair(AF_UNIX, SOCK_STREAM, 0, socks); - if (rv == -1) { - std::cerr << "socketpair() failed: errno=" << errno << std::endl; - assert(0); - } - evutil_make_socket_nonblocking(socks[0]); - evutil_make_socket_nonblocking(socks[1]); - auto bev = bufferevent_socket_new(sessions_->get_evbase(), socks[0], - BEV_OPT_DEFER_CALLBACKS | - BEV_OPT_CLOSE_ON_FREE); - if (!bev) { - std::cerr << "bufferevent_socket_new() failed" << std::endl; - assert(0); - } - workers_.push_back(bev); - auto t = std::thread(run_worker, i, socks[1], sessions_->get_ssl_ctx(), - config_); + auto worker = util::make_unique(); + auto loop = ev_loop_new(0); + worker->sessions = + util::make_unique(loop, config_, sessions_->get_ssl_ctx()); + ev_async_init(&worker->w, worker_acceptcb); + worker->w.data = worker.get(); + ev_async_start(loop, &worker->w); + + auto t = std::thread(run_worker, worker.get()); t.detach(); + workers_.push_back(std::move(worker)); } } - void accept_connection(int fd, sockaddr *addr, int addrlen) { + void accept_connection(int fd) { if (config_->num_worker == 1) { sessions_->accept_connection(fd); return; } + // Dispatch client to the one of the worker threads, in a round // robin manner. - auto client = ClientInfo{fd}; - bufferevent_write(workers_[next_worker_], &client, sizeof(client)); + auto &worker = workers_[next_worker_]; if (next_worker_ == config_->num_worker - 1) { next_worker_ = 0; } else { ++next_worker_; } + { + std::lock_guard lock(worker->m); + worker->q.push_back({fd}); + } + ev_async_send(worker->sessions->get_loop(), &worker->w); } private: - // In multi threading mode, this includes bufferevent to dispatch - // client to the worker threads. - std::vector workers_; + std::vector> workers_; Sessions *sessions_; const Config *config_; // In multi threading mode, this points to the next thread that @@ -1282,6 +1368,53 @@ private: size_t next_worker_; }; +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revents); +} // namespace + +class ListenEventHandler { +public: + ListenEventHandler(Sessions *sessions, int fd, + std::shared_ptr acceptor) + : acceptor_(acceptor), sessions_(sessions), fd_(fd) { + ev_io_init(&w_, acceptcb, fd, EV_READ); + w_.data = this; + ev_io_start(sessions_->get_loop(), &w_); + } + void accept_connection() { + for (;;) { +#ifdef HAVE_ACCEPT4 + auto fd = accept4(fd_, nullptr, nullptr, SOCK_NONBLOCK); +#else // !HAVE_ACCEPT4 + auto fd = accept(fd_, nullptr, nullptr); +#endif // !HAVE_ACCEPT4 + if (fd == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } + continue; + } +#ifndef HAVE_ACCEPT4 + util::make_socket_nonblocking(fd); +#endif // !HAVE_ACCEPT4 + acceptor_->accept_connection(fd); + } + } + +private: + ev_io w_; + std::shared_ptr acceptor_; + Sessions *sessions_; + int fd_; +}; + +namespace { +void acceptcb(struct ev_loop *loop, ev_io *w, int revents) { + auto handler = static_cast(w->data); + handler->accept_connection(); +} +} // namespace + HttpServer::HttpServer(const Config *config) : config_(config) {} namespace { @@ -1303,24 +1436,13 @@ int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { } // namespace namespace { -void evlistener_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 evlistener_errorcb(evconnlistener *listener, void *ptr) { - std::cerr << "Accepting incoming connection failed" << std::endl; -} -} // namespace - -namespace { -int start_listen(event_base *evbase, Sessions *sessions, const Config *config) { +int start_listen(struct ev_loop *loop, Sessions *sessions, + const Config *config) { addrinfo hints; int r; + bool ok = false; + auto acceptor = std::make_shared(sessions, config); auto service = util::utos(config->port); memset(&hints, 0, sizeof(addrinfo)); @@ -1331,10 +1453,6 @@ int start_listen(event_base *evbase, Sessions *sessions, const Config *config) { hints.ai_flags |= AI_ADDRCONFIG; #endif // AI_ADDRCONFIG - auto listen_handler_store = - util::make_unique(sessions, config); - auto listen_handler = listen_handler_store.get(); - addrinfo *res, *rp; r = getaddrinfo(nullptr, service.c_str(), &hints, &res); if (r != 0) { @@ -1352,7 +1470,7 @@ int start_listen(event_base *evbase, Sessions *sessions, const Config *config) { close(fd); continue; } - evutil_make_socket_nonblocking(fd); + util::make_socket_nonblocking(fd); #ifdef IPV6_V6ONLY if (rp->ai_family == AF_INET6) { if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, @@ -1362,18 +1480,14 @@ int start_listen(event_base *evbase, Sessions *sessions, const Config *config) { } } #endif // IPV6_V6ONLY - if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0) { - auto evlistener = - evconnlistener_new(evbase, evlistener_acceptcb, listen_handler, - LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1, fd); - evconnlistener_set_error_cb(evlistener, evlistener_errorcb); - - listen_handler_store.release(); + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 && listen(fd, 1000) == 0) { + new ListenEventHandler(sessions, fd, acceptor); if (config->verbose) { std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6") << ": listen on port " << config->port << std::endl; } + ok = true; continue; } else { std::cerr << strerror(errno) << std::endl; @@ -1382,7 +1496,7 @@ int start_listen(event_base *evbase, Sessions *sessions, const Config *config) { } freeaddrinfo(res); - if (listen_handler_store) { + if (!ok) { return -1; } return 0; @@ -1512,32 +1626,21 @@ int HttpServer::run() { #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L } - auto evcfg = event_config_new(); - event_config_set_flag(evcfg, EVENT_BASE_FLAG_NOLOCK); + auto loop = EV_DEFAULT; - auto evbase = event_base_new_with_config(evcfg); - - Sessions sessions(evbase, config_, ssl_ctx); - if (start_listen(evbase, &sessions, config_) != 0) { + Sessions sessions(loop, config_, ssl_ctx); + if (start_listen(loop, &sessions, config_) != 0) { std::cerr << "Could not listen" << std::endl; return -1; } - auto refresh_ev = event_new(evbase, -1, EV_PERSIST, refresh_cb, nullptr); - if (!refresh_ev) { - std::cerr << "Could not add refresh timer" << std::endl; - return -1; - } - - timeval refresh_timeout = {1, 0}; - if (event_add(refresh_ev, &refresh_timeout) == -1) { - std::cerr << "Adding refresh event failed" << std::endl; - return -1; - } + ev_timer refresh_wtc; + ev_timer_init(&refresh_wtc, refresh_cb, 1.0, 0.); + ev_timer_again(loop, &refresh_wtc); cached_date = std::make_shared(util::http_date(time(nullptr))); - event_base_loop(evbase, 0); + ev_run(loop, 0); return 0; } diff --git a/src/HttpServer.h b/src/HttpServer.h index 38d8f5e1..87d32964 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -39,22 +39,12 @@ #include -#include -#include +#include #include -#ifdef __cplusplus -extern "C" { -#endif - -#include "nghttp2_buf.h" - -#ifdef __cplusplus -} -#endif - #include "http2.h" +#include "ringbuf.h" namespace nghttp2 { @@ -65,8 +55,8 @@ struct Config { std::string private_key_file; std::string cert_file; std::string dh_param_file; - timeval stream_read_timeout; - timeval stream_write_timeout; + ev_tstamp stream_read_timeout; + ev_tstamp stream_write_timeout; nghttp2_option *session_option; void *data_ptr; size_t padding; @@ -88,11 +78,12 @@ class Http2Handler; struct Stream { Headers headers; Http2Handler *handler; - event *rtimer; - event *wtimer; + ev_timer rtimer; + ev_timer wtimer; int64_t body_left; int32_t stream_id; int file; + int hdidx[http2::HD_MAXIDX]; Stream(Http2Handler *handler, int32_t stream_id); ~Stream(); }; @@ -106,7 +97,6 @@ public: void remove_self(); int setup_bev(); - int send(); int on_read(); int on_write(); int on_connect(); @@ -137,14 +127,29 @@ public: void remove_settings_timer(); void terminate_session(uint32_t error_code); + int fill_wb(); + + int read_clear(); + int write_clear(); + int tls_handshake(); + int read_tls(); + int write_tls(); + + struct ev_loop *get_loop() const; + private: + ev_io wev_; + ev_io rev_; + ev_timer settings_timerev_; std::map> id2stream_; + RingBuf<65536> wb_; + std::function read_, write_; int64_t session_id_; nghttp2_session *session_; Sessions *sessions_; SSL *ssl_; - bufferevent *bev_; - event *settings_timerev_; + const uint8_t *data_pending_; + size_t data_pendinglen_; int fd_; }; diff --git a/src/Makefile.am b/src/Makefile.am index 983f512b..d8885216 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,7 +36,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/third-party \ @LIBSPDYLAY_CFLAGS@ \ @XML_CPPFLAGS@ \ - @LIBEVENT_OPENSSL_CFLAGS@ \ + @LIBEV_CFLAGS@ \ @OPENSSL_CFLAGS@ \ @JANSSON_CFLAGS@ \ @ZLIB_CFLAGS@ \ @@ -45,7 +45,7 @@ AM_LDFLAGS = \ @JEMALLOC_LIBS@ \ @LIBSPDYLAY_LIBS@ \ @XML_LIBS@ \ - @LIBEVENT_OPENSSL_LIBS@ \ + @LIBEV_LIBS@ \ @OPENSSL_LIBS@ \ @JANSSON_LIBS@ \ @ZLIB_LIBS@ \ @@ -59,9 +59,9 @@ if ENABLE_APP bin_PROGRAMS += nghttp nghttpd nghttpx -HELPER_OBJECTS = util.cc libevent_util.cc \ +HELPER_OBJECTS = util.cc \ http2.cc timegm.c app_helper.cc nghttp2_gzip.c -HELPER_HFILES = util.h libevent_util.h \ +HELPER_HFILES = util.h \ http2.h timegm.h app_helper.h nghttp2_config.h \ nghttp2_gzip.h @@ -78,11 +78,12 @@ nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc \ nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \ ssl.cc ssl.h \ - HttpServer.cc HttpServer.h + HttpServer.cc HttpServer.h \ + ringbuf.h bin_PROGRAMS += h2load -h2load_SOURCES = util.cc util.h libevent_util.cc libevent_util.h \ +h2load_SOURCES = util.cc util.h \ http2.cc http2.h h2load.cc h2load.h \ timegm.c timegm.h \ ssl.cc ssl.h \ @@ -95,31 +96,32 @@ endif # HAVE_SPDYLAY NGHTTPX_SRCS = \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ - libevent_util.cc libevent_util.h \ app_helper.cc app_helper.h \ ssl.cc ssl.h \ shrpx_config.cc shrpx_config.h \ shrpx_error.h \ + shrpx_accept_handler.cc shrpx_accept_handler.h \ shrpx_listen_handler.cc shrpx_listen_handler.h \ shrpx_client_handler.cc shrpx_client_handler.h \ shrpx_upstream.h \ shrpx_http2_upstream.cc shrpx_http2_upstream.h \ shrpx_https_upstream.cc shrpx_https_upstream.h \ - shrpx_downstream_queue.cc shrpx_downstream_queue.h \ shrpx_downstream.cc shrpx_downstream.h \ shrpx_downstream_connection.cc shrpx_downstream_connection.h \ shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \ shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \ shrpx_http2_session.cc shrpx_http2_session.h \ + shrpx_downstream_queue.cc shrpx_downstream_queue.h \ shrpx_log.cc shrpx_log.h \ shrpx_http.cc shrpx_http.h \ shrpx_io_control.cc shrpx_io_control.h \ shrpx_ssl.cc shrpx_ssl.h \ - shrpx_thread_event_receiver.cc shrpx_thread_event_receiver.h \ shrpx_worker.cc shrpx_worker.h \ shrpx_worker_config.cc shrpx_worker_config.h \ shrpx_connect_blocker.cc shrpx_connect_blocker.h \ - shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h + shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \ + shrpx_rate_limit.cc shrpx_rate_limit.h \ + ringbuf.h memchunk.h if HAVE_SPDYLAY NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h @@ -141,7 +143,8 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \ http2_test.cc http2_test.h \ util_test.cc util_test.h \ nghttp2_gzip_test.c nghttp2_gzip_test.h \ - nghttp2_gzip.c nghttp2_gzip.h + nghttp2_gzip.c nghttp2_gzip.h \ + ringbuf_test.cc ringbuf_test.h nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\ -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\" nghttpx_unittest_LDFLAGS = ${AM_LDFLAGS} \ diff --git a/src/h2load.cc b/src/h2load.cc index be327d9c..66fd854c 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,113 @@ void debug(const char *format, ...) { } } // namespace +namespace { +void debug_nextproto_error() { +#ifdef HAVE_SPDYLAY + debug("no supported protocol was negotiated, expected: %s, " + "spdy/2, spdy/3, spdy/3.1\n", + NGHTTP2_PROTO_VERSION_ID); +#else // !HAVE_SPDYLAY + debug("no supported protocol was negotiated, expected: %s\n", + NGHTTP2_PROTO_VERSION_ID); +#endif // !HAVE_SPDYLAY +} +} // namespace + Stream::Stream() : status_success(-1) {} +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto client = static_cast(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); + auto rv = client->do_write(); + if (rv == Client::ERR_CONNECT_FAIL) { + client->disconnect(); + rv = client->connect(); + if (rv != 0) { + client->fail(); + return; + } + return; + } + if (rv != 0) { + client->fail(); + } +} +} // namespace + Client::Client(Worker *worker, size_t req_todo) - : worker(worker), ssl(nullptr), bev(nullptr), next_addr(config.addrs), - reqidx(0), state(CLIENT_IDLE), req_todo(req_todo), req_started(0), - req_done(0) {} + : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0), + state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0), + fd(-1) { + ev_io_init(&wev, writecb, 0, EV_WRITE); + ev_io_init(&rev, readcb, 0, EV_READ); + + wev.data = this; + rev.data = this; +} Client::~Client() { disconnect(); } +int Client::do_read() { return readfn(*this); } +int Client::do_write() { return writefn(*this); } + int Client::connect() { - if (config.scheme == "https") { - ssl = SSL_new(worker->ssl_ctx); - - auto config = worker->config; - - if (!util::numeric_host(config->host.c_str())) { - SSL_set_tlsext_host_name(ssl, config->host.c_str()); - } - - bev = bufferevent_openssl_socket_new(worker->evbase, -1, ssl, - BUFFEREVENT_SSL_CONNECTING, - BEV_OPT_DEFER_CALLBACKS); - } else { - bev = bufferevent_socket_new(worker->evbase, -1, BEV_OPT_DEFER_CALLBACKS); - } - - int rv = -1; while (next_addr) { - rv = bufferevent_socket_connect(bev, next_addr->ai_addr, - next_addr->ai_addrlen); + auto addr = next_addr; next_addr = next_addr->ai_next; - if (rv == 0) { - break; + fd = util::create_nonblock_socket(addr->ai_family); + if (fd == -1) { + continue; } + if (config.scheme == "https") { + ssl = SSL_new(worker->ssl_ctx); + + auto config = worker->config; + + if (!util::numeric_host(config->host.c_str())) { + SSL_set_tlsext_host_name(ssl, config->host.c_str()); + } + + SSL_set_fd(ssl, fd); + SSL_set_connect_state(ssl); + } + + auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen); + if (rv != 0 && errno != EINPROGRESS) { + if (ssl) { + SSL_free(ssl); + ssl = nullptr; + } + close(fd); + fd = -1; + continue; + } + break; } - if (rv != 0) { + + if (fd == -1) { return -1; } - bufferevent_enable(bev, EV_READ); - bufferevent_setcb(bev, readcb, writecb, eventcb, this); + + writefn = &Client::connected; + + on_readfn = &Client::on_read; + on_writefn = &Client::on_write; + + ev_io_set(&rev, fd, EV_READ); + ev_io_set(&wev, fd, EV_WRITE); + + ev_io_start(worker->loop, &wev); + return 0; } @@ -144,27 +197,21 @@ void Client::fail() { } void Client::disconnect() { - int fd = -1; streams.clear(); session.reset(); state = CLIENT_IDLE; + ev_io_stop(worker->loop, &wev); + ev_io_stop(worker->loop, &rev); if (ssl) { - fd = SSL_get_fd(ssl); SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); SSL_shutdown(ssl); - } - if (bev) { - bufferevent_disable(bev, EV_READ | EV_WRITE); - bufferevent_free(bev); - bev = nullptr; - } - if (ssl) { SSL_free(ssl); ssl = nullptr; } if (fd != -1) { shutdown(fd, SHUT_WR); close(fd); + fd = -1; } } @@ -325,7 +372,74 @@ void Client::on_stream_close(int32_t stream_id, bool success) { } } +int Client::noop() { return 0; } + int Client::on_connect() { + if (ssl) { + report_tls_info(); + + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len); + for (int i = 0; i < 2; ++i) { + if (next_proto) { + if (util::check_h2_is_selected(next_proto, next_proto_len)) { + session = util::make_unique(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 +448,235 @@ int Client::on_connect() { for (; nreq > 0; --nreq) { submit_request(); } - return session->on_write(); + + signal_write(); + + return 0; } -int Client::on_read() { - ssize_t rv = session->on_read(); - if (rv < 0) { +int Client::on_net_error() { + if (state == CLIENT_IDLE) { + disconnect(); + if (connect() == 0) { + return 0; + } + } + debug("error/eof\n"); + return -1; +} + +int Client::on_read(const uint8_t *data, size_t len) { + auto rv = session->on_read(data, len); + if (rv != 0) { return -1; } - worker->stats.bytes_total += rv; - - return on_write(); + worker->stats.bytes_total += len; + signal_write(); + return 0; } -int Client::on_write() { return session->on_write(); } +int Client::on_write() { + if (session->on_write() != 0) { + return -1; + } + return 0; +} + +int Client::read_clear() { + uint8_t buf[8192]; + + for (;;) { + ssize_t nread; + while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return on_net_error(); + } + + if (nread == 0) { + return -1; + } + + if (on_read(buf, nread) != 0) { + return -1; + } + } + + return 0; +} + +int Client::write_clear() { + for (;;) { + if (wb.rleft() > 0) { + struct iovec iov[2]; + auto iovcnt = wb.riovec(iov); + + ssize_t nwrite; + while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ev_io_start(worker->loop, &wev); + return 0; + } + return -1; + } + wb.drain(nwrite); + continue; + } + + if (on_write() != 0) { + return -1; + } + if (wb.rleft() == 0) { + wb.reset(); + break; + } + } + + ev_io_stop(worker->loop, &wev); + + return 0; +} + +int Client::connected() { + if (!util::check_socket_connected(fd)) { + return ERR_CONNECT_FAIL; + } + ev_io_start(worker->loop, &rev); + ev_io_stop(worker->loop, &wev); + + if (ssl) { + readfn = &Client::tls_handshake; + writefn = &Client::tls_handshake; + + return do_write(); + } + + readfn = &Client::read_clear; + writefn = &Client::write_clear; + + if (on_connect() != 0) { + return -1; + } + + return 0; +} + +int Client::tls_handshake() { + auto rv = SSL_do_handshake(ssl); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + ev_io_stop(worker->loop, &wev); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(worker->loop, &wev); + return 0; + default: + return -1; + } + } + + ev_io_stop(worker->loop, &wev); + + readfn = &Client::read_tls; + writefn = &Client::write_tls; + + if (on_connect() != 0) { + return -1; + } + + return 0; +} + +int Client::read_tls() { + uint8_t buf[8192]; + for (;;) { + auto rv = SSL_read(ssl, buf, sizeof(buf)); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(worker->loop, &wev); + return 0; + default: + return -1; + } + } + + if (on_read(buf, rv) != 0) { + return -1; + } + } +} + +int Client::write_tls() { + for (;;) { + if (wb.rleft() > 0) { + const void *p; + size_t len; + std::tie(p, len) = wb.get(); + + auto rv = SSL_write(ssl, p, len); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + ev_io_stop(worker->loop, &wev); + return 0; + case SSL_ERROR_WANT_WRITE: + ev_io_start(worker->loop, &wev); + return 0; + default: + return -1; + } + } + + wb.drain(rv); + + continue; + } + if (on_write() != 0) { + return -1; + } + if (wb.rleft() == 0) { + break; + } + } + + ev_io_stop(worker->loop, &wev); + + return 0; +} + +void Client::signal_write() { ev_io_start(worker->loop, &wev); } Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, Config *config) - : stats{0}, evbase(event_base_new()), ssl_ctx(ssl_ctx), config(config), - id(id), tls_info_report_done(false) { + : stats{0}, loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config), id(id), + tls_info_report_done(false) { stats.req_todo = req_todo; progress_interval = std::max((size_t)1, req_todo / 10); @@ -369,7 +693,12 @@ Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, } } -Worker::~Worker() { event_base_free(evbase); } +Worker::~Worker() { + // first clear clients so that io watchers are stopped before + // destructing ev_loop. + clients.clear(); + ev_loop_destroy(loop); +} void Worker::run() { for (auto &client : clients) { @@ -378,145 +707,9 @@ void Worker::run() { client->fail(); } } - event_base_loop(evbase, 0); + ev_run(loop, 0); } -namespace { -void debug_nextproto_error() { -#ifdef HAVE_SPDYLAY - debug("no supported protocol was negotiated, expected: %s, " - "spdy/2, spdy/3, spdy/3.1\n", - NGHTTP2_PROTO_VERSION_ID); -#else // !HAVE_SPDYLAY - debug("no supported protocol was negotiated, expected: %s\n", - NGHTTP2_PROTO_VERSION_ID); -#endif // !HAVE_SPDYLAY -} -} // namespace - -namespace { -void eventcb(bufferevent *bev, short events, void *ptr) { - int rv; - auto client = static_cast(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..6ff53525 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,10 @@ struct Client { size_t req_started; // The number of requests this client has done so far. size_t req_done; + int fd; + RingBuf<65536> wb; + + enum { ERR_CONNECT_FAIL = -100 }; Client(Worker *worker, size_t req_todo); ~Client(); @@ -151,13 +161,29 @@ struct Client { void report_progress(); void report_tls_info(); void terminate_session(); - int on_connect(); - int on_read(); + + int do_read(); + int do_write(); + + int connected(); + int read_clear(); + int write_clear(); + int tls_handshake(); + int read_tls(); + int write_tls(); + + int on_read(const uint8_t *data, size_t len); int on_write(); + int on_connect(); + int on_net_error(); + int noop(); + void on_request(int32_t stream_id); void on_header(int32_t stream_id, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen); void on_stream_close(int32_t stream_id, bool success); + + void signal_write(); }; } // namespace h2load 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/http2.cc b/src/http2.cc index 5d4374ec..a0d94344 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -174,138 +174,6 @@ void copy_url_component(std::string &dest, const http_parser_url *u, int field, } } -bool check_http2_allowed_header(const char *name) { - return check_http2_allowed_header(reinterpret_cast(name), - strlen(name)); -} - -bool check_http2_allowed_header(const uint8_t *name, size_t namelen) { - return !util::strieq("connection", name, namelen) && - !util::strieq("host", name, namelen) && - !util::strieq("keep-alive", name, namelen) && - !util::strieq("proxy-connection", name, namelen) && - !util::strieq("te", name, namelen) && - !util::strieq("transfer-encoding", name, namelen) && - !util::strieq("upgrade", name, namelen); -} - -namespace { -const char *DISALLOWED_HD[] = { - "connection", "keep-alive", "proxy-connection", - "te", "transfer-encoding", "upgrade", -}; -} // namespace - -namespace { -auto DISALLOWED_HDLEN = util::array_size(DISALLOWED_HD); -} // namespace - -namespace { -const char *REQUEST_PSEUDO_HD[] = { - ":authority", ":method", ":path", ":scheme", -}; -} // namespace - -namespace { -auto REQUEST_PSEUDO_HDLEN = util::array_size(REQUEST_PSEUDO_HD); -} // namespace - -namespace { -const char *RESPONSE_PSEUDO_HD[] = { - ":status", -}; -} // namespace - -namespace { -auto RESPONSE_PSEUDO_HDLEN = util::array_size(RESPONSE_PSEUDO_HD); -} // namespace - -namespace { -const char *IGN_HD[] = { - "connection", "http2-settings", "keep-alive", "proxy-connection", - "server", "te", "transfer-encoding", "upgrade", - "via", "x-forwarded-for", "x-forwarded-proto", -}; -} // namespace - -namespace { -auto IGN_HDLEN = util::array_size(IGN_HD); -} // namespace - -namespace { -const char *HTTP1_IGN_HD[] = { - "connection", "cookie", "http2-settings", "keep-alive", - "proxy-connection", "server", "upgrade", "via", - "x-forwarded-for", "x-forwarded-proto", -}; -} // namespace - -namespace { -auto HTTP1_IGN_HDLEN = util::array_size(HTTP1_IGN_HD); -} // namespace - -bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs) { - if (lhs.name.c_str()[0] == ':') { - if (rhs.name.c_str()[0] != ':') { - return true; - } - } else if (rhs.name.c_str()[0] == ':') { - return false; - } - - return lhs.name < rhs.name; -} - -bool check_http2_headers(const Headers &nva) { - for (size_t i = 0; i < DISALLOWED_HDLEN; ++i) { - if (std::binary_search(std::begin(nva), std::end(nva), - Header(DISALLOWED_HD[i], ""), name_less)) { - return false; - } - } - return true; -} - -bool check_http2_request_headers(const Headers &nva) { - return check_http2_headers(nva); -} - -bool check_http2_response_headers(const Headers &nva) { - return check_http2_headers(nva); -} - -namespace { -template -bool check_pseudo_header(const uint8_t *name, size_t namelen, - InputIterator allowed_first, - InputIterator allowed_last) { - for (auto i = allowed_first; i != allowed_last; ++i) { - if (util::streq(*i, name, namelen)) { - return true; - } - } - - return false; -} -} // namespace - -bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen) { - return check_pseudo_header(name, namelen, REQUEST_PSEUDO_HD, - REQUEST_PSEUDO_HD + REQUEST_PSEUDO_HDLEN); -} - -bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen) { - return check_pseudo_header(name, namelen, RESPONSE_PSEUDO_HD, - RESPONSE_PSEUDO_HD + RESPONSE_PSEUDO_HDLEN); -} - -void normalize_headers(Headers &nva) { - for (auto &kv : nva) { - util::inp_strlower(kv.name); - } - std::stable_sort(std::begin(nva), std::end(nva), name_less); -} - Headers::value_type to_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index) { @@ -328,26 +196,14 @@ void add_header(Headers &nva, const uint8_t *name, size_t namelen, nva.push_back(to_header(name, namelen, value, valuelen, no_index)); } -const Headers::value_type *get_unique_header(const Headers &nva, - const char *name) { - auto nv = Headers::value_type(name, ""); - auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less); - if (i != std::end(nva) && (*i).name == nv.name) { - auto j = i + 1; - if (j == std::end(nva) || (*j).name != nv.name) { - return &(*i); +const Headers::value_type *get_header(const Headers &nva, const char *name) { + const Headers::value_type *res = nullptr; + for (auto &nv : nva) { + if (nv.name == name) { + res = &nv; } } - return nullptr; -} - -const Headers::value_type *get_header(const Headers &nva, const char *name) { - auto nv = Headers::value_type(name, ""); - auto i = std::lower_bound(std::begin(nva), std::end(nva), nv, name_less); - if (i != std::end(nva) && (*i).name == nv.name) { - return &(*i); - } - return nullptr; + return res; } std::string value_to_str(const Headers::value_type *nv) { @@ -371,65 +227,54 @@ nghttp2_nv make_nv(const std::string &name, const std::string &value, value.size(), flags}; } -void copy_norm_headers_to_nva(std::vector &nva, - const Headers &headers) { - size_t i, j; - for (i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) { - auto &kv = headers[i]; - int rv = strcmp(kv.name.c_str(), IGN_HD[j]); - if (rv < 0) { - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); - } - ++i; - } else if (rv > 0) { - ++j; - } else { - ++i; +void copy_headers_to_nva(std::vector &nva, const Headers &headers) { + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; } - } - for (; i < headers.size(); ++i) { - auto &kv = headers[i]; - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); + switch (lookup_token(kv.name)) { + case HD_COOKIE: + case HD_CONNECTION: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_TRANSFER_ENCODING: + case HD_UPGRADE: + case HD_VIA: + case HD_X_FORWARDED_FOR: + case HD_X_FORWARDED_PROTO: + continue; } + nva.push_back(make_nv(kv.name, kv.value, kv.no_index)); } } -void build_http1_headers_from_norm_headers(std::string &hdrs, - const Headers &headers) { - size_t i, j; - for (i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) { - auto &kv = headers[i]; - auto rv = strcmp(kv.name.c_str(), HTTP1_IGN_HD[j]); - - if (rv < 0) { - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - hdrs += kv.name; - capitalize(hdrs, hdrs.size() - kv.name.size()); - hdrs += ": "; - hdrs += kv.value; - sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); - hdrs += "\r\n"; - } - ++i; - } else if (rv > 0) { - ++j; - } else { - ++i; +void build_http1_headers_from_headers(std::string &hdrs, + const Headers &headers) { + for (auto &kv : headers) { + if (kv.name.empty() || kv.name[0] == ':') { + continue; } - } - for (; i < headers.size(); ++i) { - auto &kv = headers[i]; - - if (!kv.name.empty() && kv.name.c_str()[0] != ':') { - hdrs += kv.name; - capitalize(hdrs, hdrs.size() - kv.name.size()); - hdrs += ": "; - hdrs += kv.value; - sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); - hdrs += "\r\n"; + switch (lookup_token(kv.name)) { + case HD_CONNECTION: + case HD_COOKIE: + case HD_HTTP2_SETTINGS: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_SERVER: + case HD_UPGRADE: + case HD_VIA: + case HD_X_FORWARDED_FOR: + case HD_X_FORWARDED_PROTO: + continue; } + hdrs += kv.name; + capitalize(hdrs, hdrs.size() - kv.name.size()); + hdrs += ": "; + hdrs += kv.value; + sanitize_header_value(hdrs, hdrs.size() - kv.value.size()); + hdrs += "\r\n"; } } @@ -572,6 +417,258 @@ int parse_http_status_code(const std::string &src) { return status; } +int lookup_token(const std::string &name) { + return lookup_token(reinterpret_cast(name.c_str()), + name.size()); +} + +// This function was generated by genheaderfunc.py. Inspired by h2o +// header lookup. https://github.com/h2o/h2o +int lookup_token(const uint8_t *name, size_t namelen) { + switch (namelen) { + case 2: + switch (name[namelen - 1]) { + case 'e': + if (util::streq("t", name, 1)) { + return HD_TE; + } + break; + } + break; + case 3: + switch (name[namelen - 1]) { + case 'a': + if (util::streq("vi", name, 2)) { + return HD_VIA; + } + break; + } + break; + case 4: + switch (name[namelen - 1]) { + case 't': + if (util::streq("hos", name, 3)) { + return HD_HOST; + } + break; + } + break; + case 5: + switch (name[namelen - 1]) { + case 'h': + if (util::streq(":pat", name, 4)) { + return HD__PATH; + } + break; + case 't': + if (util::streq(":hos", name, 4)) { + return HD__HOST; + } + break; + } + break; + case 6: + switch (name[namelen - 1]) { + case 'e': + if (util::streq("cooki", name, 5)) { + return HD_COOKIE; + } + break; + case 'r': + if (util::streq("serve", name, 5)) { + return HD_SERVER; + } + break; + case 't': + if (util::streq("expec", name, 5)) { + return HD_EXPECT; + } + break; + } + break; + case 7: + switch (name[namelen - 1]) { + case 'c': + if (util::streq("alt-sv", name, 6)) { + return HD_ALT_SVC; + } + break; + case 'd': + if (util::streq(":metho", name, 6)) { + return HD__METHOD; + } + break; + case 'e': + if (util::streq(":schem", name, 6)) { + return HD__SCHEME; + } + if (util::streq("upgrad", name, 6)) { + return HD_UPGRADE; + } + break; + case 's': + if (util::streq(":statu", name, 6)) { + return HD__STATUS; + } + break; + } + break; + case 8: + switch (name[namelen - 1]) { + case 'n': + if (util::streq("locatio", name, 7)) { + return HD_LOCATION; + } + break; + } + break; + case 10: + switch (name[namelen - 1]) { + case 'e': + if (util::streq("keep-aliv", name, 9)) { + return HD_KEEP_ALIVE; + } + break; + case 'n': + if (util::streq("connectio", name, 9)) { + return HD_CONNECTION; + } + break; + case 'y': + if (util::streq(":authorit", name, 9)) { + return HD__AUTHORITY; + } + break; + } + break; + case 14: + switch (name[namelen - 1]) { + case 'h': + if (util::streq("content-lengt", name, 13)) { + return HD_CONTENT_LENGTH; + } + break; + case 's': + if (util::streq("http2-setting", name, 13)) { + return HD_HTTP2_SETTINGS; + } + break; + } + break; + case 15: + switch (name[namelen - 1]) { + case 'r': + if (util::streq("x-forwarded-fo", name, 14)) { + return HD_X_FORWARDED_FOR; + } + break; + } + break; + case 16: + switch (name[namelen - 1]) { + case 'n': + if (util::streq("proxy-connectio", name, 15)) { + return HD_PROXY_CONNECTION; + } + break; + } + break; + case 17: + switch (name[namelen - 1]) { + case 'e': + if (util::streq("if-modified-sinc", name, 16)) { + return HD_IF_MODIFIED_SINCE; + } + break; + case 'g': + if (util::streq("transfer-encodin", name, 16)) { + return HD_TRANSFER_ENCODING; + } + break; + case 'o': + if (util::streq("x-forwarded-prot", name, 16)) { + return HD_X_FORWARDED_PROTO; + } + break; + } + break; + } + return -1; +} + +void init_hdidx(int *hdidx) { memset(hdidx, -1, sizeof(hdidx[0]) * HD_MAXIDX); } + +void index_headers(int *hdidx, const Headers &headers) { + for (size_t i = 0; i < headers.size(); ++i) { + auto &kv = headers[i]; + auto token = lookup_token( + reinterpret_cast(kv.name.c_str()), kv.name.size()); + if (token >= 0) { + http2::index_header(hdidx, token, i); + } + } +} + +void index_header(int *hdidx, int token, size_t idx) { + if (token == -1) { + return; + } + assert(token < HD_MAXIDX); + hdidx[token] = idx; +} + +bool check_http2_request_pseudo_header(const int *hdidx, int token) { + switch (token) { + case HD__AUTHORITY: + case HD__METHOD: + case HD__PATH: + case HD__SCHEME: + return hdidx[token] == -1; + default: + return false; + } +} + +bool check_http2_response_pseudo_header(const int *hdidx, int token) { + switch (token) { + case HD__STATUS: + return hdidx[token] == -1; + default: + return false; + } +} + +bool http2_header_allowed(int token) { + switch (token) { + case HD_CONNECTION: + case HD_KEEP_ALIVE: + case HD_PROXY_CONNECTION: + case HD_TRANSFER_ENCODING: + case HD_UPGRADE: + return false; + default: + return true; + } +} + +bool http2_mandatory_request_headers_presence(const int *hdidx) { + if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 || + hdidx[HD__SCHEME] == -1 || + (hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) { + return false; + } + return true; +} + +const Headers::value_type *get_header(const int *hdidx, int token, + const Headers &nva) { + auto i = hdidx[token]; + if (i == -1) { + return nullptr; + } + return &nva[i]; +} + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2.h b/src/http2.h index 0efcfc5b..c21cfe7b 100644 --- a/src/http2.h +++ b/src/http2.h @@ -76,35 +76,6 @@ void sanitize_header_value(std::string &s, size_t offset); void copy_url_component(std::string &dest, const http_parser_url *u, int field, const char *url); -// Returns true if the header field |name| with length |namelen| bytes -// is valid for HTTP/2. -bool check_http2_allowed_header(const uint8_t *name, size_t namelen); - -// Calls check_http2_allowed_header with |name| and strlen(name), -// assuming |name| is null-terminated string. -bool check_http2_allowed_header(const char *name); - -// Checks that headers |nva| do not contain disallowed header fields -// in HTTP/2 spec. This function returns true if |nva| does not -// contains such headers. -bool check_http2_headers(const Headers &nva); - -// Calls check_http2_headers() -bool check_http2_request_headers(const Headers &nva); - -// Calls check_http2_headers() -bool check_http2_response_headers(const Headers &nva); - -// Returns true if |name| is allowed pusedo header for request. -bool check_http2_request_pseudo_header(const uint8_t *name, size_t namelen); - -// Returns true if |name| is allowed pusedo header for response. -bool check_http2_response_pseudo_header(const uint8_t *name, size_t namelen); - -bool name_less(const Headers::value_type &lhs, const Headers::value_type &rhs); - -void normalize_headers(Headers &nva); - Headers::value_type to_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index); @@ -115,16 +86,9 @@ Headers::value_type to_header(const uint8_t *name, size_t namelen, void add_header(Headers &nva, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, bool no_index); -// Returns the iterator to the entry in |nva| which has name |name| -// and the |name| is uinque in the |nva|. If no such entry exist, -// returns nullptr. -const Headers::value_type *get_unique_header(const Headers &nva, - const char *name); - -// Returns the iterator to the entry in |nva| which has name -// |name|. If more than one entries which have the name |name|, first -// occurrence in |nva| is returned. If no such entry exist, returns -// nullptr. +// Returns pointer to the entry in |nva| which has name |name|. If +// more than one entries which have the name |name|, last occurrence +// in |nva| is returned. If no such entry exist, returns nullptr. const Headers::value_type *get_header(const Headers &nva, const char *name); // Returns nv->second if nv is not nullptr. Otherwise, returns "". @@ -165,14 +129,13 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) { // Appends headers in |headers| to |nv|. Certain headers, including // disallowed headers in HTTP/2 spec and headers which require // special handling (i.e. via), are not copied. -void copy_norm_headers_to_nva(std::vector &nva, - const Headers &headers); +void copy_headers_to_nva(std::vector &nva, const Headers &headers); // Appends HTTP/1.1 style header lines to |hdrs| from headers in // |headers|. Certain headers, which requires special handling // (i.e. via and cookie), are not appended. -void build_http1_headers_from_norm_headers(std::string &hdrs, - const Headers &headers); +void build_http1_headers_from_headers(std::string &hdrs, + const Headers &headers); // Return positive window_size_increment if WINDOW_UPDATE should be // sent for the stream |stream_id|. If |stream_id| == 0, this function @@ -218,6 +181,70 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value, // Returns parsed HTTP status code. Returns -1 on failure. int parse_http_status_code(const std::string &src); +// Header fields to be indexed, except HD_MAXIDX which is convenient +// member to get maximum value. +enum { + HD__AUTHORITY, + HD__HOST, + HD__METHOD, + HD__PATH, + HD__SCHEME, + HD__STATUS, + HD_ALT_SVC, + HD_CONNECTION, + HD_CONTENT_LENGTH, + HD_COOKIE, + HD_EXPECT, + HD_HOST, + HD_HTTP2_SETTINGS, + HD_IF_MODIFIED_SINCE, + HD_KEEP_ALIVE, + HD_LOCATION, + HD_PROXY_CONNECTION, + HD_SERVER, + HD_TE, + HD_TRANSFER_ENCODING, + HD_UPGRADE, + HD_VIA, + HD_X_FORWARDED_FOR, + HD_X_FORWARDED_PROTO, + HD_MAXIDX, +}; + +// Looks up header token for header name |name| of length |namelen|. +// Only headers we are interested in are tokenized. If header name +// cannot be tokenized, returns -1. +int lookup_token(const uint8_t *name, size_t namelen); +int lookup_token(const std::string &name); + +// Initializes |hdidx|, header index. The |hdidx| must point to the +// array containing at least HD_MAXIDX elements. +void init_hdidx(int *hdidx); +// Indexes header |token| using index |idx|. +void index_header(int *hdidx, int token, size_t idx); +// Iterates |headers| and for each element, call index_header. +void index_headers(int *hdidx, const Headers &headers); + +// Returns true if HTTP/2 request pseudo header |token| is not indexed +// yet and not -1. +bool check_http2_request_pseudo_header(const int *hdidx, int token); + +// Returns true if HTTP/2 response pseudo header |token| is not +// indexed yet and not -1. +bool check_http2_response_pseudo_header(const int *hdidx, int token); + +// Returns true if header field denoted by |token| is allowed for +// HTTP/2. +bool http2_header_allowed(int token); + +// Returns true that |hdidx| contains mandatory HTTP/2 request +// headers. +bool http2_mandatory_request_headers_presence(const int *hdidx); + +// Returns header denoted by |token| using index |hdidx|. +const Headers::value_type *get_header(const int *hdidx, int token, + const Headers &nva); + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2_test.cc b/src/http2_test.cc index 35dbe93f..9edc1bd1 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -100,55 +100,14 @@ void test_http2_add_header(void) { CU_ASSERT(Headers::value_type("a", "") == nva[0]); } -void test_http2_check_http2_headers(void) { - auto nva1 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"upgrade", "http2"}}; - CU_ASSERT(!http2::check_http2_headers(nva1)); - - auto nva2 = Headers{{"connection", "1"}, {"delta", "2"}, {"echo", "3"}}; - CU_ASSERT(!http2::check_http2_headers(nva2)); - - auto nva3 = Headers{{"alpha", "1"}, {"bravo", "2"}, {"te2", "3"}}; - CU_ASSERT(http2::check_http2_headers(nva3)); - - auto n1 = ":authority"; - auto n1u8 = reinterpret_cast(n1); - - CU_ASSERT(http2::check_http2_request_pseudo_header(n1u8, strlen(n1))); - CU_ASSERT(!http2::check_http2_response_pseudo_header(n1u8, strlen(n1))); - - auto n2 = ":status"; - auto n2u8 = reinterpret_cast(n2); - - CU_ASSERT(!http2::check_http2_request_pseudo_header(n2u8, strlen(n2))); - CU_ASSERT(http2::check_http2_response_pseudo_header(n2u8, strlen(n2))); -} - -void test_http2_get_unique_header(void) { - auto nva = Headers{{"alpha", "1"}, - {"bravo", "2"}, - {"bravo", "3"}, - {"charlie", "4"}, - {"delta", "5"}, - {"echo", "6"}}; - const Headers::value_type *rv; - rv = http2::get_unique_header(nva, "delta"); - CU_ASSERT(rv != nullptr); - CU_ASSERT("delta" == rv->name); - - rv = http2::get_unique_header(nva, "bravo"); - CU_ASSERT(rv == nullptr); - - rv = http2::get_unique_header(nva, "foxtrot"); - CU_ASSERT(rv == nullptr); -} - void test_http2_get_header(void) { auto nva = Headers{{"alpha", "1"}, {"bravo", "2"}, {"bravo", "3"}, {"charlie", "4"}, {"delta", "5"}, - {"echo", "6"}}; + {"echo", "6"}, + {"content-length", "7"}}; const Headers::value_type *rv; rv = http2::get_header(nva, "delta"); CU_ASSERT(rv != nullptr); @@ -160,6 +119,12 @@ void test_http2_get_header(void) { rv = http2::get_header(nva, "foxtrot"); CU_ASSERT(rv == nullptr); + + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + hdidx[http2::HD_CONTENT_LENGTH] = 6; + rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva); + CU_ASSERT("content-length" == rv->name); } namespace { @@ -178,11 +143,11 @@ auto headers = Headers{{"alpha", "0", true}, {"zulu", "12"}}; } // namespace -void test_http2_copy_norm_headers_to_nva(void) { +void test_http2_copy_headers_to_nva(void) { std::vector nva; - http2::copy_norm_headers_to_nva(nva, headers); - CU_ASSERT(7 == nva.size()); - auto ans = std::vector{0, 1, 4, 5, 6, 7, 12}; + http2::copy_headers_to_nva(nva, headers); + CU_ASSERT(9 == nva.size()); + auto ans = std::vector{0, 1, 4, 5, 6, 7, 8, 9, 12}; for (size_t i = 0; i < ans.size(); ++i) { check_nv(headers[ans[i]], &nva[i]); @@ -194,9 +159,9 @@ void test_http2_copy_norm_headers_to_nva(void) { } } -void test_http2_build_http1_headers_from_norm_headers(void) { +void test_http2_build_http1_headers_from_headers(void) { std::string hdrs; - http2::build_http1_headers_from_norm_headers(hdrs, headers); + http2::build_http1_headers_from_headers(hdrs, headers); CU_ASSERT(hdrs == "Alpha: 0\r\n" "Bravo: 1\r\n" "Delta: 4\r\n" @@ -206,15 +171,6 @@ void test_http2_build_http1_headers_from_norm_headers(void) { "Te: 8\r\n" "Te: 9\r\n" "Zulu: 12\r\n"); - - hdrs.clear(); - // Both nghttp2 and spdylay do not allow \r and \n in header value - // now. - - // auto hd2 = std::vector> - // {{"alpha", "bravo\r\ncharlie\r\n"}}; - // http2::build_http1_headers_from_norm_headers(hdrs, hd2); - // CU_ASSERT(hdrs == "Alpha: bravo charlie \r\n"); } void test_http2_lws(void) { @@ -270,4 +226,68 @@ void test_http2_parse_http_status_code(void) { CU_ASSERT(-1 == http2::parse_http_status_code("")); } +void test_http2_index_header(void) { + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + + http2::index_header(hdidx, http2::HD__AUTHORITY, 0); + http2::index_header(hdidx, -1, 1); + + CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]); +} + +void test_http2_lookup_token(void) { + CU_ASSERT(http2::HD__AUTHORITY == http2::lookup_token(":authority")); + CU_ASSERT(-1 == http2::lookup_token(":authorit")); + CU_ASSERT(-1 == http2::lookup_token(":Authority")); + CU_ASSERT(http2::HD_EXPECT == http2::lookup_token("expect")); +} + +void test_http2_check_http2_pseudo_header(void) { + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + + CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + hdidx[http2::HD__PATH] = 0; + CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + hdidx[http2::HD__METHOD] = 1; + CU_ASSERT( + !http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD)); + CU_ASSERT(!http2::check_http2_request_pseudo_header(hdidx, http2::HD_VIA)); + + http2::init_hdidx(hdidx); + + CU_ASSERT( + http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS)); + hdidx[http2::HD__STATUS] = 0; + CU_ASSERT( + !http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS)); + CU_ASSERT(!http2::check_http2_response_pseudo_header(hdidx, http2::HD_VIA)); +} + +void test_http2_http2_header_allowed(void) { + CU_ASSERT(http2::http2_header_allowed(http2::HD__PATH)); + CU_ASSERT(http2::http2_header_allowed(http2::HD_CONTENT_LENGTH)); + CU_ASSERT(!http2::http2_header_allowed(http2::HD_CONNECTION)); +} + +void test_http2_mandatory_request_headers_presence(void) { + int hdidx[http2::HD_MAXIDX]; + http2::init_hdidx(hdidx); + + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__AUTHORITY] = 0; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__METHOD] = 1; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__PATH] = 2; + CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx)); + hdidx[http2::HD__SCHEME] = 3; + CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx)); + + hdidx[http2::HD__AUTHORITY] = -1; + hdidx[http2::HD_HOST] = 0; + CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx)); +} + } // namespace shrpx diff --git a/src/http2_test.h b/src/http2_test.h index 9e1e2fc0..f8127dc7 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -28,14 +28,17 @@ namespace shrpx { void test_http2_add_header(void); -void test_http2_check_http2_headers(void); -void test_http2_get_unique_header(void); void test_http2_get_header(void); -void test_http2_copy_norm_headers_to_nva(void); -void test_http2_build_http1_headers_from_norm_headers(void); +void test_http2_copy_headers_to_nva(void); +void test_http2_build_http1_headers_from_headers(void); void test_http2_lws(void); void test_http2_rewrite_location_uri(void); void test_http2_parse_http_status_code(void); +void test_http2_index_header(void); +void test_http2_lookup_token(void); +void test_http2_check_http2_pseudo_header(void); +void test_http2_http2_header_allowed(void); +void test_http2_mandatory_request_headers_presence(void); } // namespace shrpx diff --git a/src/memchunk.h b/src/memchunk.h new file mode 100644 index 00000000..43e4bfa9 --- /dev/null +++ b/src/memchunk.h @@ -0,0 +1,235 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef MEMCHUNK_H +#define MEMCHUNK_H + +#include "nghttp2_config.h" + +#include +#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() { + if (!pool) { + return; + } + for (auto m = head; m;) { + auto next = m->next; + pool->recycle(m); + m = next; + } + } + size_t append(const void *data, size_t count) { + if (count == 0) { + return 0; + } + + auto p = reinterpret_cast(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..68f1cd2f 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, @@ -218,6 +224,9 @@ struct Request { int level; // RequestPriority value defined in HtmlParser.h int pri; + int res_hdidx[http2::HD_MAXIDX]; + // used for incoming PUSH_PROMISE + int req_hdidx[http2::HD_MAXIDX]; bool expect_final_response; // For pushed request, |uri| is empty and |u| is zero-cleared. @@ -229,7 +238,10 @@ struct Request { data_length(data_length), data_offset(0), response_len(0), inflater(nullptr), html_parser(nullptr), data_prd(data_prd), stream_id(-1), status(0), level(level), pri(pri), - expect_final_response(false) {} + expect_final_response(false) { + http2::init_hdidx(res_hdidx); + http2::init_hdidx(req_hdidx); + } ~Request() { nghttp2_gzip_inflate_del(inflater); @@ -348,12 +360,47 @@ struct Request { } } - bool response_pseudo_header_allowed() const { - return res_nva.empty() || res_nva.back().name.c_str()[0] == ':'; + bool response_pseudo_header_allowed(int token) const { + if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') { + return false; + } + switch (token) { + case http2::HD__STATUS: + return res_hdidx[token] == -1; + default: + return false; + } } - bool push_request_pseudo_header_allowed() const { - return res_nva.empty() || req_nva.back().name.c_str()[0] == ':'; + bool push_request_pseudo_header_allowed(int token) const { + if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') { + return false; + } + switch (token) { + case http2::HD__AUTHORITY: + case http2::HD__METHOD: + case http2::HD__PATH: + case http2::HD__SCHEME: + return req_hdidx[token] == -1; + default: + return false; + } + } + + Headers::value_type *get_res_header(int token) { + auto idx = res_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &res_nva[idx]; + } + + Headers::value_type *get_req_header(int token) { + auto idx = req_hdidx[token]; + if (idx == -1) { + return nullptr; + } + return &req_nva[idx]; } void record_request_time() { @@ -412,24 +459,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 +484,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 +501,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 +524,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 +594,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 +863,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 +1000,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 +1059,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 +1078,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()) { @@ -1255,8 +1578,6 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) { build_headers.emplace_back(kv.name, kv.value, kv.no_index); } - std::stable_sort(std::begin(build_headers), std::end(build_headers), - http2::name_less); auto nva = std::vector(); nva.reserve(build_headers.size()); @@ -1384,14 +1705,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 @@ -1409,36 +1729,36 @@ void check_response_header(nghttp2_session *session, Request *req) { req->expect_final_response = false; + auto status_hd = req->get_res_header(http2::HD__STATUS); + + if (!status_hd) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + auto status = http2::parse_http_status_code(status_hd->value); + if (status == -1) { + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, + NGHTTP2_PROTOCOL_ERROR); + return; + } + + req->status = status; + for (auto &nv : req->res_nva) { if ("content-encoding" == nv.name) { gzip = util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value); continue; } - if (":status" == nv.name) { - int status; - if (req->status != 0 || - (status = http2::parse_http_status_code(nv.value)) == -1) { - - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, - NGHTTP2_PROTOCOL_ERROR); - return; - } - - req->status = status; - } - } - - if (req->status == 0) { - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id, - NGHTTP2_PROTOCOL_ERROR); - return; } if (req->status / 100 == 1) { req->expect_final_response = true; req->status = 0; req->res_nva.clear(); + http2::init_hdidx(req->res_hdidx); return; } @@ -1516,15 +1836,17 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (namelen > 0 && name[0] == ':') { - if (!req->response_pseudo_header_allowed() || - !http2::check_http2_response_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { + if (!req->response_pseudo_header_allowed(token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } + http2::index_header(req->res_hdidx, token, req->res_nva.size()); http2::add_header(req->res_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); break; @@ -1537,9 +1859,10 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, break; } - if (namelen > 0 && name[0] == ':') { - if (!req->push_request_pseudo_header_allowed() || - !http2::check_http2_request_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + + if (name[0] == ':') { + if (!req->push_request_pseudo_header_allowed(token)) { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -1547,6 +1870,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, } } + http2::index_header(req->req_hdidx, token, req->req_nva.size()); http2::add_header(req->req_nva, name, namelen, value, valuelen, flags & NGHTTP2_NV_FLAG_NO_INDEX); break; @@ -1606,11 +1930,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( @@ -1618,36 +1938,27 @@ int on_frame_recv_callback2(nghttp2_session *session, if (!req) { break; } - std::string scheme, authority, method, path; - for (auto &nv : req->req_nva) { - if (nv.name == ":scheme") { - scheme = nv.value; - continue; - } - if (nv.name == ":authority" || nv.name == "host") { - authority = nv.value; - continue; - } - if (nv.name == ":method") { - method = nv.value; - continue; - } - if (nv.name == ":path") { - path = nv.value; - continue; - } + auto scheme = req->get_req_header(http2::HD__SCHEME); + auto authority = req->get_req_header(http2::HD__AUTHORITY); + auto method = req->get_req_header(http2::HD__METHOD); + auto path = req->get_req_header(http2::HD__PATH); + + if (!authority) { + authority = req->get_req_header(http2::HD_HOST); } - if (scheme.empty() || authority.empty() || method.empty() || path.empty() || - path[0] != '/') { + + if (!scheme || !authority || !method || !path || scheme->value.empty() || + authority->value.empty() || method->value.empty() || + path->value.empty() || path->value[0] != '/') { nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->push_promise.promised_stream_id, NGHTTP2_PROTOCOL_ERROR); break; } - std::string uri = scheme; + std::string uri = scheme->value; uri += "://"; - uri += authority; - uri += path; + uri += authority->value; + uri += path->value; http_parser_url u; memset(&u, 0, sizeof(u)); if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { @@ -1717,14 +2028,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 +2053,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 +2076,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 +2089,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 +2139,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 +2170,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 +2207,6 @@ fin: if (ssl_ctx) { SSL_CTX_free(ssl_ctx); } - if (evbase) { - event_base_free(evbase); - } return result; } } // namespace @@ -2038,6 +2239,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 +2283,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 +2569,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 new file mode 100644 index 00000000..10b62b40 --- /dev/null +++ b/src/ringbuf.h @@ -0,0 +1,143 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef RINGBUF_H +#define RINGBUF_H + +#include "nghttp2_config.h" + +#include + +#include +#include + +namespace nghttp2 { + +template struct RingBuf { + RingBuf() : pos(0), len(0) {} + // Returns the number of bytes to read. + size_t rleft() const { return len; } + // Returns the number of bytes this buffer can store. + size_t wleft() const { return N - len; } + // Writes up to min(wleft(), |count|) bytes from buffer pointed by + // |buf|. Returns number of bytes written. + size_t write(const void *buf, size_t count) { + count = std::min(count, wleft()); + auto last = (pos + len) % N; + if (count > N - last) { + auto c = N - last; + memcpy(begin + last, buf, c); + memcpy(begin, reinterpret_cast(buf) + c, count - c); + } else { + memcpy(begin + last, buf, count); + } + len += count; + return count; + } + size_t write(size_t count) { + count = std::min(count, wleft()); + len += count; + return count; + } + // Drains min(rleft(), |count|) bytes from start of the buffer. + size_t drain(size_t count) { + count = std::min(count, rleft()); + pos = (pos + count) % N; + len -= count; + return count; + } + // Returns pointer to the next contiguous readable buffer and its + // length. + std::pair get() const { + if (pos + len > N) { + return {begin + pos, N - pos}; + } + return {begin + pos, len}; + } + void reset() { pos = len = 0; } + // Fills |iov| for reading. |iov| must contain at least 2 elements. + // Returns the number of filled elements. + int riovec(struct iovec *iov) { + if (len == 0) { + return 0; + } + if (pos + len > N) { + auto c = N - pos; + iov[0].iov_base = begin + pos; + iov[0].iov_len = c; + iov[1].iov_base = begin; + iov[1].iov_len = len - c; + return 2; + } + iov[0].iov_base = begin + pos; + iov[0].iov_len = len; + return 1; + } + // Fills |iov| for writing. |iov| must contain at least 2 elements. + // Returns the number of filled elements. + int wiovec(struct iovec *iov) { + if (len == N) { + return 0; + } + if (pos == 0) { + iov[0].iov_base = begin + pos + len; + iov[0].iov_len = N - pos - len; + return 1; + } + if (pos + len < N) { + auto c = N - pos - len; + iov[0].iov_base = begin + pos + len; + iov[0].iov_len = c; + iov[1].iov_base = begin; + iov[1].iov_len = N - len - c; + return 2; + } + auto last = (pos + len) % N; + iov[0].iov_base = begin + last; + iov[0].iov_len = N - len; + return 1; + } + size_t pos; + size_t len; + uint8_t begin[N]; +}; + +inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) { + if (max == 0) { + return 0; + } + for (int i = 0; i < iovcnt; ++i) { + auto d = std::min(max, iov[i].iov_len); + iov[i].iov_len = d; + max -= d; + if (max == 0) { + return i + 1; + } + } + return iovcnt; +} + +} // namespace nghttp2 + +#endif // RINGBUF_H diff --git a/src/ringbuf_test.cc b/src/ringbuf_test.cc new file mode 100644 index 00000000..ec530498 --- /dev/null +++ b/src/ringbuf_test.cc @@ -0,0 +1,183 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ringbuf_test.h" + +#include +#include +#include + +#include + +#include + +#include "ringbuf.h" + +namespace nghttp2 { + +void test_ringbuf_write(void) { + RingBuf<16> b; + CU_ASSERT(0 == b.rleft()); + CU_ASSERT(16 == b.wleft()); + + b.write("012", 3); + + CU_ASSERT(3 == b.rleft()); + CU_ASSERT(13 == b.wleft()); + CU_ASSERT(0 == b.pos); + CU_ASSERT(3 == b.len); + + b.drain(3); + + CU_ASSERT(0 == b.rleft()); + CU_ASSERT(16 == b.wleft()); + CU_ASSERT(3 == b.pos); + CU_ASSERT(0 == b.len); + + b.write("0123456789ABCDEF", 16); + + CU_ASSERT(16 == b.rleft()); + CU_ASSERT(0 == b.wleft()); + CU_ASSERT(3 == b.pos); + CU_ASSERT(16 == b.len); + CU_ASSERT(0 == memcmp(b.begin, "DEF0123456789ABC", 16)); + + const void *p; + size_t len; + std::tie(p, len) = b.get(); + CU_ASSERT(13 == len); + CU_ASSERT(0 == memcmp(p, "0123456789ABC", len)); + + b.drain(14); + + CU_ASSERT(2 == b.rleft()); + CU_ASSERT(14 == b.wleft()); + CU_ASSERT(1 == b.pos); + CU_ASSERT(2 == b.len); + + std::tie(p, len) = b.get(); + CU_ASSERT(2 == len); + CU_ASSERT(0 == memcmp(p, "EF", len)); +} + +void test_ringbuf_iovec(void) { + RingBuf<16> b; + struct iovec iov[2]; + + auto rv = b.riovec(iov); + + CU_ASSERT(0 == rv); + + rv = b.wiovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin == iov[0].iov_base); + CU_ASSERT(16 == iov[0].iov_len); + + // set pos to somewhere middle of the buffer, this will require 2 + // iovec for writing. + b.pos = 6; + + rv = b.riovec(iov); + + CU_ASSERT(0 == rv); + + rv = b.wiovec(iov); + + CU_ASSERT(2 == rv); + CU_ASSERT(b.begin + b.pos == iov[0].iov_base); + CU_ASSERT(10 == iov[0].iov_len); + CU_ASSERT(b.begin == iov[1].iov_base); + CU_ASSERT(6 == iov[1].iov_len); + + // occupy first region of buffer + b.pos = 0; + b.len = 10; + + rv = b.riovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin == iov[0].iov_base); + CU_ASSERT(10 == iov[0].iov_len); + + rv = b.wiovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin + b.len == iov[0].iov_base); + CU_ASSERT(6 == iov[0].iov_len); + + // occupy last region of buffer + b.pos = 6; + b.len = 10; + + rv = b.riovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin + b.pos == iov[0].iov_base); + CU_ASSERT(10 == iov[0].iov_len); + + rv = b.wiovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin == iov[0].iov_base); + CU_ASSERT(6 == iov[0].iov_len); + + // occupy middle of buffer + b.pos = 3; + b.len = 10; + + rv = b.riovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin + b.pos == iov[0].iov_base); + CU_ASSERT(10 == iov[0].iov_len); + + rv = b.wiovec(iov); + + CU_ASSERT(2 == rv); + CU_ASSERT(b.begin + b.pos + b.len == iov[0].iov_base); + CU_ASSERT(3 == iov[0].iov_len); + CU_ASSERT(b.begin == iov[1].iov_base); + CU_ASSERT(3 == iov[1].iov_len); + + // crossover + b.pos = 13; + b.len = 10; + + rv = b.riovec(iov); + + CU_ASSERT(2 == rv); + CU_ASSERT(b.begin + b.pos == iov[0].iov_base); + CU_ASSERT(3 == iov[0].iov_len); + CU_ASSERT(b.begin == iov[1].iov_base); + CU_ASSERT(7 == iov[1].iov_len); + + rv = b.wiovec(iov); + + CU_ASSERT(1 == rv); + CU_ASSERT(b.begin + 7 == iov[0].iov_base); + CU_ASSERT(6 == iov[0].iov_len); +} + +} // namespace nghttp2 diff --git a/src/ringbuf_test.h b/src/ringbuf_test.h new file mode 100644 index 00000000..c7c42a27 --- /dev/null +++ b/src/ringbuf_test.h @@ -0,0 +1,35 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef RINGBUF_TEST_H +#define RINGBUF_TEST_H + +namespace nghttp2 { + +void test_ringbuf_write(void); +void test_ringbuf_iovec(void); + +} // namespace nghttp2 + +#endif // RINGBUF_TEST_H diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 65448a53..6fe0456e 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -38,6 +38,8 @@ #include "http2_test.h" #include "util_test.h" #include "nghttp2_gzip_test.h" +#include "ringbuf_test.h" +#include "shrpx_config.h" static int init_suite1(void) { return 0; } @@ -51,6 +53,8 @@ int main(int argc, char *argv[]) { SSL_load_error_strings(); SSL_library_init(); + shrpx::create_config(); + // initialize the CUnit test registry if (CUE_SUCCESS != CU_initialize_registry()) return CU_get_error(); @@ -68,35 +72,40 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file", shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) || !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) || - !CU_add_test(pSuite, "http2_check_http2_headers", - shrpx::test_http2_check_http2_headers) || - !CU_add_test(pSuite, "http2_get_unique_header", - shrpx::test_http2_get_unique_header) || !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) || - !CU_add_test(pSuite, "http2_copy_norm_headers_to_nva", - shrpx::test_http2_copy_norm_headers_to_nva) || - !CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers", - shrpx::test_http2_build_http1_headers_from_norm_headers) || + !CU_add_test(pSuite, "http2_copy_headers_to_nva", + shrpx::test_http2_copy_headers_to_nva) || + !CU_add_test(pSuite, "http2_build_http1_headers_from_headers", + shrpx::test_http2_build_http1_headers_from_headers) || !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) || !CU_add_test(pSuite, "http2_rewrite_location_uri", shrpx::test_http2_rewrite_location_uri) || !CU_add_test(pSuite, "http2_parse_http_status_code", shrpx::test_http2_parse_http_status_code) || - !CU_add_test(pSuite, "downstream_normalize_request_headers", - shrpx::test_downstream_normalize_request_headers) || - !CU_add_test(pSuite, "downstream_normalize_response_headers", - shrpx::test_downstream_normalize_response_headers) || - !CU_add_test(pSuite, "downstream_get_norm_request_header", - shrpx::test_downstream_get_norm_request_header) || - !CU_add_test(pSuite, "downstream_get_norm_response_header", - shrpx::test_downstream_get_norm_response_header) || + !CU_add_test(pSuite, "http2_index_header", + shrpx::test_http2_index_header) || + !CU_add_test(pSuite, "http2_lookup_token", + shrpx::test_http2_lookup_token) || + !CU_add_test(pSuite, "http2_check_http2_pseudo_header", + shrpx::test_http2_check_http2_pseudo_header) || + !CU_add_test(pSuite, "http2_http2_header_allowed", + shrpx::test_http2_http2_header_allowed) || + !CU_add_test(pSuite, "http2_mandatory_request_headers_presence", + shrpx::test_http2_mandatory_request_headers_presence) || + !CU_add_test(pSuite, "downstream_index_request_headers", + shrpx::test_downstream_index_request_headers) || + !CU_add_test(pSuite, "downstream_index_response_headers", + shrpx::test_downstream_index_response_headers) || + !CU_add_test(pSuite, "downstream_get_request_header", + shrpx::test_downstream_get_request_header) || + !CU_add_test(pSuite, "downstream_get_response_header", + shrpx::test_downstream_get_response_header) || !CU_add_test(pSuite, "downstream_crumble_request_cookie", shrpx::test_downstream_crumble_request_cookie) || !CU_add_test(pSuite, "downstream_assemble_request_cookie", shrpx::test_downstream_assemble_request_cookie) || - !CU_add_test( - pSuite, "downstream_rewrite_norm_location_response_header", - shrpx::test_downstream_rewrite_norm_location_response_header) || + !CU_add_test(pSuite, "downstream_rewrite_location_response_header", + shrpx::test_downstream_rewrite_location_response_header) || !CU_add_test(pSuite, "config_parse_config_str_list", shrpx::test_shrpx_config_parse_config_str_list) || !CU_add_test(pSuite, "config_parse_header", @@ -115,7 +124,9 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) || !CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) || !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) || - !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate)) { + !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) || + !CU_add_test(pSuite, "ringbuf_write", nghttp2::test_ringbuf_write) || + !CU_add_test(pSuite, "ringbuf_iovec", nghttp2::test_ringbuf_iovec)) { CU_cleanup_registry(); return CU_get_error(); } diff --git a/src/shrpx.cc b/src/shrpx.cc index 92eb1af1..31beee1f 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,20 @@ void fill_default_config() { mod_config()->argc = 0; mod_config()->argv = nullptr; mod_config()->downstream_connections_per_host = 8; - mod_config()->listener_disable_timeout = {0, 0}; + mod_config()->downstream_connections_per_frontend = 0; + mod_config()->listener_disable_timeout = 0.; } } // namespace -namespace { -size_t get_rate_limit(size_t rate_limit) { - if (rate_limit == 0) { - return EV_RATE_LIMIT_MAX; - } else { - return rate_limit; - } -} -} // namespace +// namespace { +// size_t get_rate_limit(size_t rate_limit) { +// if (rate_limit == 0) { +// return EV_RATE_LIMIT_MAX; +// } else { +// return rate_limit; +// } +// } +// } // namespace namespace { void print_version(std::ostream &out) { @@ -864,10 +800,8 @@ Performance: Default: )" << get_config()->read_rate << R"( --read-burst= 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 @@ -882,22 +816,26 @@ Performance: --worker-read-rate= Set maximum average read rate on frontend connection per worker. Setting 0 to this option - means read rate is unlimited. + means read rate is unlimited. Not implemented + yet. Default: )" << get_config()->worker_read_rate << R"( --worker-read-burst= Set maximum read burst size on frontend connection per worker. Setting 0 to this option - means read burst size is unlimited. + means read burst size is unlimited. Not + implemented yet. Default: )" << get_config()->worker_read_burst << R"( --worker-write-rate= Set maximum average write rate on frontend connection per worker. Setting 0 to this option - means write rate is unlimited. + means write rate is unlimited. Not implemented + yet. Default: )" << get_config()->worker_write_rate << R"( --worker-write-burst= Set maximum write burst size on frontend connection per worker. Setting 0 to this option - means write burst size is unlimited. + means write burst size is unlimited. Not + implemented yet. Default: )" << get_config()->worker_write_burst << R"( --worker-frontend-connections= Set maximum number of simultaneous connections @@ -906,55 +844,61 @@ Performance: --backend-http1-connections-per-host= Set maximum number of backend concurrent HTTP/1 connections per host. This option is meaningful - when -s option is used. + when -s option is used. To limit the number of + connections per frontend for default mode, use + --backend-http1-connections-per-frontend. Default: )" << get_config()->downstream_connections_per_host << R"( + --backend-http1-connections-per-frontend= + Set maximum number of backend concurrent HTTP/1 + connections per frontend. This option is only + used for default mode. 0 means unlimited. To + limit the number of connections per host for + HTTP/2 or SPDY proxy mode (-s option), use + --backend-http1-connections-per-host. + Default: )" + << get_config()->downstream_connections_per_frontend << R"( Timeout: --frontend-http2-read-timeout= 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 @@ -1283,6 +1227,8 @@ int main(int argc, char **argv) { {"listener-disable-timeout", required_argument, &flag, 64}, {"strip-incoming-x-forwarded-for", no_argument, &flag, 65}, {"accesslog-format", required_argument, &flag, 66}, + {"backend-http1-connections-per-frontend", required_argument, &flag, + 67}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -1587,6 +1533,11 @@ int main(int argc, char **argv) { // --accesslog-format cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, optarg); break; + case 67: + // --backend-http1-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + optarg); + break; default: break; } @@ -1829,17 +1780,17 @@ int main(int argc, char **argv) { } } - mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new( - get_rate_limit(get_config()->read_rate), - get_rate_limit(get_config()->read_burst), - get_rate_limit(get_config()->write_rate), - get_rate_limit(get_config()->write_burst), nullptr); + // mod_config()->rate_limit_cfg = ev_token_bucket_cfg_new( + // get_rate_limit(get_config()->read_rate), + // get_rate_limit(get_config()->read_burst), + // get_rate_limit(get_config()->write_rate), + // get_rate_limit(get_config()->write_burst), nullptr); - mod_config()->worker_rate_limit_cfg = ev_token_bucket_cfg_new( - get_rate_limit(get_config()->worker_read_rate), - get_rate_limit(get_config()->worker_read_burst), - get_rate_limit(get_config()->worker_write_rate), - get_rate_limit(get_config()->worker_write_burst), nullptr); + // mod_config()->worker_rate_limit_cfg = ev_token_bucket_cfg_new( + // get_rate_limit(get_config()->worker_read_rate), + // get_rate_limit(get_config()->worker_read_burst), + // get_rate_limit(get_config()->worker_write_rate), + // get_rate_limit(get_config()->worker_write_burst), nullptr); if (get_config()->upstream_frame_debug) { // To make it sync to logging diff --git a/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc new file mode 100644 index 00000000..1eab4d70 --- /dev/null +++ b/src/shrpx_accept_handler.cc @@ -0,0 +1,103 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_accept_handler.h" + +#include + +#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); + +#ifdef HAVE_ACCEPT4 + auto cfd = + accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC); +#else // !HAVE_ACCEPT4 + auto cfd = accept(fd_, &sockaddr.sa, &addrlen); +#endif // !HAVE_ACCEPT4 + + if (cfd == -1) { + switch (errno) { + case EINTR: + case ENETDOWN: + case EPROTO: + case ENOPROTOOPT: + case EHOSTDOWN: +#ifdef ENONET + case ENONET: +#endif // ENONET + case EHOSTUNREACH: + case EOPNOTSUPP: + case ENETUNREACH: + continue; + } + + return; + } + +#ifndef HAVE_ACCEPT4 + util::make_socket_nonblocking(cfd); + util::make_socket_closeonexec(cfd); +#endif // !HAVE_ACCEPT4 + + util::make_socket_nodelay(cfd); + + conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen); + } +} + +void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); } + +void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); } + +int AcceptHandler::get_fd() const { return fd_; } + +} // namespace shrpx 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..96983420 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -42,161 +42,446 @@ #include "shrpx_spdy_upstream.h" #endif // HAVE_SPDYLAY #include "util.h" -#include "libevent_util.h" using namespace nghttp2; namespace shrpx { namespace { -void upstream_readcb(bufferevent *bev, void *arg) { - auto handler = static_cast(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; + } + wb_.reset(); + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; + } + } + + wlimit_.stopw(); + ev_timer_stop(loop_, &wt_); + + return 0; +} + +int ClientHandler::tls_handshake() { + ev_timer_again(loop_, &rt_); + + auto rv = SSL_do_handshake(ssl_); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + wlimit_.stopw(); + ev_timer_stop(loop_, &wt_); + return 0; + case SSL_ERROR_WANT_WRITE: + wlimit_.startw(); + ev_timer_again(loop_, &wt_); + return 0; + default: + return -1; + } + } + + wlimit_.stopw(); + ev_timer_stop(loop_, &wt_); + + set_tls_handshake(true); + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "SSL/TLS handshake completed"; + } + if (validate_next_proto() != 0) { + return -1; + } + if (LOG_ENABLED(INFO)) { + if (SSL_session_reused(ssl_)) { + CLOG(INFO, this) << "SSL/TLS session reused"; + } + } + + read_ = &ClientHandler::read_tls; + write_ = &ClientHandler::write_tls; + + return 0; +} + +int ClientHandler::read_tls() { + ev_timer_again(loop_, &rt_); + + for (;;) { + if (rb_.rleft() && on_read() != 0) { + return -1; + } + + rb_.reset(); + struct iovec iov[2]; + auto iovcnt = rb_.wiovec(iov); + iovcnt = limit_iovec(iov, iovcnt, rlimit_.avail()); + if (iovcnt == 0) { + return 0; + } + + auto rv = SSL_read(ssl_, iov[0].iov_base, iov[0].iov_len); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + goto fin; + case SSL_ERROR_WANT_WRITE: + wlimit_.startw(); + ev_timer_again(loop_, &wt_); + goto fin; + default: + return -1; + } + } + + rb_.write(rv); + rlimit_.drain(rv); + } + +fin: + return 0; +} + +int ClientHandler::write_tls() { + ev_timer_again(loop_, &rt_); + + for (;;) { + if (wb_.rleft() > 0) { + const void *p; + size_t len; + std::tie(p, len) = wb_.get(); + + len = std::min(len, wlimit_.avail()); + if (len == 0) { + return 0; + } + + auto limit = get_write_limit(); + if (limit != -1) { + len = std::min(len, static_cast(limit)); + } + auto rv = SSL_write(ssl_, p, len); + + if (rv == 0) { + return -1; + } + + if (rv < 0) { + auto err = SSL_get_error(ssl_, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + wlimit_.stopw(); + ev_timer_stop(loop_, &wt_); + return 0; + case SSL_ERROR_WANT_WRITE: + wlimit_.startw(); + ev_timer_again(loop_, &wt_); + return 0; + default: + return -1; } } + + wb_.drain(rv); + wlimit_.drain(rv); + + update_warmup_writelen(rv); + update_last_write_time(); + + continue; + } + wb_.reset(); + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + break; } } -} -} // namespace -namespace { -void upstream_http2_connhd_readcb(bufferevent *bev, void *arg) { - // This callback assumes upstream is Http2Upstream. - auto handler = static_cast(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 +496,18 @@ ClientHandler::~ClientHandler() { --worker_stat_->num_connections; + ev_timer_stop(loop_, &reneg_shutdown_timer_); + + ev_timer_stop(loop_, &rt_); + ev_timer_stop(loop_, &wt_); + + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); + // TODO If backend is http/2, and it is in CONNECTED state, signal // it and make it loopbreak when output is zero. if (worker_config->graceful_shutdown && worker_stat_->num_connections == 0) { - event_base_loopbreak(get_evbase()); - } - - if (reneg_shutdown_timerev_) { - event_free(reneg_shutdown_timerev_); + ev_break(loop_); } if (ssl_) { @@ -227,11 +516,6 @@ ClientHandler::~ClientHandler() { SSL_shutdown(ssl_); } - bufferevent_remove_from_rate_limit_group(bev_); - - util::bev_disable_unless(bev_, EV_READ | EV_WRITE); - bufferevent_free(bev_); - if (ssl_) { SSL_free(ssl_); } @@ -245,21 +529,22 @@ ClientHandler::~ClientHandler() { Upstream *ClientHandler::get_upstream() { return upstream_.get(); } -bufferevent *ClientHandler::get_bev() const { return bev_; } - -event_base *ClientHandler::get_evbase() const { - return bufferevent_get_base(bev_); +struct ev_loop *ClientHandler::get_loop() const { + return loop_; } -void ClientHandler::set_bev_cb(bufferevent_data_cb readcb, - bufferevent_data_cb writecb, - bufferevent_event_cb eventcb) { - bufferevent_setcb(bev_, readcb, writecb, eventcb, this); +void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) { + ev_timer_set(&rt_, 0., t); + if (ev_is_active(&rt_)) { + ev_timer_again(loop_, &rt_); + } } -void ClientHandler::set_upstream_timeouts(const timeval *read_timeout, - const timeval *write_timeout) { - bufferevent_set_timeouts(bev_, read_timeout, write_timeout); +void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) { + ev_timer_set(&wt_, 0., t); + if (ev_is_active(&wt_)) { + ev_timer_again(loop_, &wt_); + } } int ClientHandler::validate_next_proto() { @@ -268,7 +553,8 @@ int ClientHandler::validate_next_proto() { int rv; // First set callback for catch all cases - set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); + on_read_ = &ClientHandler::upstream_read; + SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len); for (int i = 0; i < 2; ++i) { if (next_proto) { @@ -284,8 +570,7 @@ int ClientHandler::validate_next_proto() { (next_proto_len == sizeof("h2-16") - 1 && memcmp("h2-16", next_proto, next_proto_len) == 0)) { - set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb, - upstream_eventcb); + on_read_ = &ClientHandler::upstream_http2_connhd_read; auto http2_upstream = util::make_unique(this); @@ -303,7 +588,7 @@ int ClientHandler::validate_next_proto() { // At this point, input buffer is already filled with some // bytes. The read callback is not called until new data // come. So consume input buffer here. - if (on_http2_connhd_read() != 0) { + if (on_read() != 0) { return -1; } @@ -331,7 +616,7 @@ int ClientHandler::validate_next_proto() { // At this point, input buffer is already filled with some // bytes. The read callback is not called until new data // come. So consume input buffer here. - if (upstream_->on_read() != 0) { + if (on_read() != 0) { return -1; } @@ -345,7 +630,7 @@ int ClientHandler::validate_next_proto() { // At this point, input buffer is already filled with some // bytes. The read callback is not called until new data // come. So consume input buffer here. - if (upstream_->on_read() != 0) { + if (on_read() != 0) { return -1; } @@ -370,7 +655,7 @@ int ClientHandler::validate_next_proto() { // At this point, input buffer is already filled with some bytes. // The read callback is not called until new data come. So consume // input buffer here. - if (upstream_->on_read() != 0) { + if (on_read() != 0) { return -1; } @@ -382,101 +667,11 @@ int ClientHandler::validate_next_proto() { return -1; } -int ClientHandler::on_read() { return upstream_->on_read(); } +int ClientHandler::do_read() { return read_(*this); } +int ClientHandler::do_write() { return write_(*this); } -int ClientHandler::on_event() { return upstream_->on_event(); } - -int ClientHandler::on_http2_connhd_read() { - // This callback assumes upstream is Http2Upstream. - uint8_t data[NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN]; - auto input = bufferevent_get_input(bev_); - auto readlen = evbuffer_remove(input, data, left_connhd_len_); - - if (readlen == -1) { - return -1; - } - - if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE + - NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_, - data, readlen) != 0) { - // There is no downgrade path here. Just drop the connection. - if (LOG_ENABLED(INFO)) { - CLOG(INFO, this) << "invalid client connection header"; - } - - return -1; - } - - left_connhd_len_ -= readlen; - - if (left_connhd_len_ > 0) { - return 0; - } - - set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); - - // Run on_read to process data left in buffer since they are not - // notified further - if (on_read() != 0) { - return -1; - } - - return 0; -} - -int ClientHandler::on_http1_connhd_read() { - uint8_t data[NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN]; - auto input = bufferevent_get_input(bev_); - auto readlen = evbuffer_copyout(input, data, left_connhd_len_); - - if (readlen == -1) { - return -1; - } - - if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE + - NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_, - data, readlen) != 0) { - if (LOG_ENABLED(INFO)) { - CLOG(INFO, this) << "This is HTTP/1.1 connection, " - << "but may be upgraded to HTTP/2 later."; - } - - // Reset header length for later HTTP/2 upgrade - left_connhd_len_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN; - set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); - - if (on_read() != 0) { - return -1; - } - - return 0; - } - - if (evbuffer_drain(input, readlen) == -1) { - return -1; - } - - left_connhd_len_ -= readlen; - - if (left_connhd_len_ > 0) { - return 0; - } - - if (LOG_ENABLED(INFO)) { - CLOG(INFO, this) << "direct HTTP/2 connection"; - } - - direct_http2_upgrade(); - set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); - - // Run on_read to process data left in buffer since they are not - // notified further - if (on_read() != 0) { - return -1; - } - - return 0; -} +int ClientHandler::on_read() { return on_read_(*this); } +int ClientHandler::on_write() { return on_write_(*this); } const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; } @@ -519,7 +714,7 @@ ClientHandler::get_downstream_connection() { dconn = util::make_unique(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 +730,6 @@ ClientHandler::get_downstream_connection() { return dconn; } -size_t ClientHandler::get_outbuf_length() { - return evbuffer_get_length(bufferevent_get_output(bev_)); -} - SSL *ClientHandler::get_ssl() const { return ssl_; } void ClientHandler::set_http2_session(Http2Session *http2session) { @@ -561,11 +752,10 @@ void ClientHandler::direct_http2_upgrade() { // TODO We don't know exact h2 draft version in direct upgrade. We // just use library default for now. alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID; - set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb); + on_read_ = &ClientHandler::upstream_read; } int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) { - int rv; auto upstream = util::make_unique(this); if (upstream->upgrade_upstream(http) != 0) { return -1; @@ -576,16 +766,13 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) { // TODO We might get other version id in HTTP2-settings, if we // support aliasing for h2, but we just use library default for now. alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID; - set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb, upstream_eventcb); + on_read_ = &ClientHandler::upstream_http2_connhd_read; + static char res[] = "HTTP/1.1 101 Switching Protocols\r\n" "Connection: Upgrade\r\n" "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n" "\r\n"; - rv = bufferevent_write(bev_, res, sizeof(res) - 1); - if (rv != 0) { - CLOG(FATAL, this) << "bufferevent_write() faild"; - return -1; - } + wb_.write(res, sizeof(res) - 1); return 0; } @@ -603,18 +790,6 @@ void ClientHandler::set_tls_handshake(bool f) { tls_handshake_ = f; } bool ClientHandler::get_tls_handshake() const { return tls_handshake_; } -namespace { -void shutdown_cb(evutil_socket_t fd, short what, void *arg) { - auto handler = static_cast(arg); - - if (LOG_ENABLED(INFO)) { - CLOG(INFO, handler) << "Close connection due to TLS renegotiation"; - } - - delete handler; -} -} // namespace - void ClientHandler::set_tls_renegotiation(bool f) { if (tls_renegotiation_ == false) { if (LOG_ENABLED(INFO)) { @@ -622,13 +797,7 @@ void ClientHandler::set_tls_renegotiation(bool f) { << "Start shutdown timer now."; } - reneg_shutdown_timerev_ = evtimer_new(get_evbase(), shutdown_cb, this); - event_priority_set(reneg_shutdown_timerev_, 0); - - timeval timeout = {0, 0}; - - // TODO What to do if this failed? - evtimer_add(reneg_shutdown_timerev_, &timeout); + ev_timer_start(loop_, &reneg_shutdown_timer_); } tls_renegotiation_ = f; } @@ -645,14 +814,12 @@ ssize_t ClientHandler::get_write_limit() { return -1; } - timeval tv; - if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) { - auto now = util::to_time64(tv); - if (now - last_write_time_ > 1000000) { - // Time out, use small record size - warmup_writelen_ = 0; - return SHRPX_SMALL_WRITE_LIMIT; - } + auto t = ev_now(loop_); + + if (t - last_write_time_ > 1.0) { + // Time out, use small record size + warmup_writelen_ = 0; + return SHRPX_SMALL_WRITE_LIMIT; } // If event_base_gettimeofday_cached() failed, we just skip timer @@ -672,10 +839,7 @@ void ClientHandler::update_warmup_writelen(size_t n) { } void ClientHandler::update_last_write_time() { - timeval tv; - if (event_base_gettimeofday_cached(get_evbase(), &tv) == 0) { - last_write_time_ = util::to_time64(tv); - } + last_write_time_ = ev_now(loop_); } void ClientHandler::write_accesslog(Downstream *downstream) { @@ -721,4 +885,13 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status, WorkerStat *ClientHandler::get_worker_stat() const { return worker_stat_; } +UpstreamBuf *ClientHandler::get_wb() { return &wb_; } + +UpstreamBuf *ClientHandler::get_rb() { return &rb_; } + +void ClientHandler::signal_write() { wlimit_.startw(); } + +RateLimit *ClientHandler::get_rlimit() { return &rlimit_; } +RateLimit *ClientHandler::get_wlimit() { return &wlimit_; } + } // namespace shrpx 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..2c102d8b 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -134,6 +134,8 @@ const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[] = const char SHRPX_OPT_NO_LOCATION_REWRITE[] = "no-location-rewrite"; const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] = "backend-http1-connections-per-host"; +const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[] = + "backend-http1-connections-per-frontend"; const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout"; namespace { @@ -198,7 +200,7 @@ FILE *open_file_for_write(const char *filename) { return nullptr; } - evutil_make_socket_closeonexec(fileno(f)); + util::make_socket_closeonexec(fileno(f)); return f; } @@ -421,15 +423,14 @@ std::vector parse_log_format(const char *optarg) { } namespace { -int parse_timeval(timeval *dest, const char *opt, const char *optarg) { +int parse_timeval(ev_tstamp *dest, const char *opt, const char *optarg) { time_t sec; if (parse_uint(&sec, opt, optarg) != 0) { return -1; } - dest->tv_sec = sec; - dest->tv_usec = 0; + *dest = sec; return 0; } @@ -856,18 +857,22 @@ int parse_config(const char *opt, const char *optarg) { } if (util::strieq(opt, SHRPX_OPT_WORKER_READ_RATE)) { + LOG(WARN) << opt << ": not implemented yet"; return parse_uint(&mod_config()->worker_read_rate, opt, optarg); } if (util::strieq(opt, SHRPX_OPT_WORKER_READ_BURST)) { + LOG(WARN) << opt << ": not implemented yet"; return parse_uint(&mod_config()->worker_read_burst, opt, optarg); } if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_RATE)) { + LOG(WARN) << opt << ": not implemented yet"; return parse_uint(&mod_config()->worker_write_rate, opt, optarg); } if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_BURST)) { + LOG(WARN) << opt << ": not implemented yet"; return parse_uint(&mod_config()->worker_write_burst, opt, optarg); } @@ -1027,6 +1032,24 @@ int parse_config(const char *opt, const char *optarg) { return 0; } + if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND)) { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 0) { + LOG(ERROR) << opt << ": specify the integer more than or equal to 0"; + + return -1; + } + + mod_config()->downstream_connections_per_frontend = n; + + return 0; + } + if (util::strieq(opt, SHRPX_OPT_LISTENER_DISABLE_TIMEOUT)) { return parse_timeval(&mod_config()->listener_disable_timeout, opt, optarg); } diff --git a/src/shrpx_config.h b/src/shrpx_config.h index abe46695..3ab7dcd1 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -37,9 +37,10 @@ #include #include -#include #include +#include + #include namespace shrpx { @@ -124,6 +125,7 @@ extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[]; extern const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[]; extern const char SHRPX_OPT_NO_LOCATION_REWRITE[]; extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[]; +extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[]; extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[]; union sockaddr_union { @@ -171,15 +173,15 @@ struct Config { std::vector 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 +201,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. @@ -229,6 +231,7 @@ struct Config { size_t http2_upstream_connection_window_bits; size_t http2_downstream_connection_window_bits; size_t downstream_connections_per_host; + size_t downstream_connections_per_frontend; // actual size of downstream_http_proxy_addr size_t downstream_http_proxy_addrlen; size_t read_rate; 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..0e0deafb 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -38,45 +38,123 @@ 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 + +// upstream could be nullptr for unittests Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority) - : request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0), - upstream_(upstream), response_body_buf_(nullptr), - upstream_rtimerev_(nullptr), upstream_wtimerev_(nullptr), - downstream_rtimerev_(nullptr), downstream_wtimerev_(nullptr), - request_headers_sum_(0), response_headers_sum_(0), request_datalen_(0), - response_datalen_(0), stream_id_(stream_id), priority_(priority), - downstream_stream_id_(-1), + : request_buf_(upstream ? upstream->get_mcpool() : nullptr), + response_buf_(upstream ? upstream->get_mcpool() : nullptr), + request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0), + upstream_(upstream), request_headers_sum_(0), response_headers_sum_(0), + request_datalen_(0), response_datalen_(0), stream_id_(stream_id), + priority_(priority), downstream_stream_id_(-1), response_rst_stream_error_code_(NGHTTP2_NO_ERROR), request_state_(INITIAL), request_major_(1), request_minor_(1), response_state_(INITIAL), response_http_status_(0), response_major_(1), response_minor_(1), upgrade_request_(false), upgraded_(false), - http2_upgrade_seen_(false), http2_settings_seen_(false), - chunked_request_(false), request_connection_close_(false), - request_header_key_prev_(false), request_http2_expect_body_(false), - chunked_response_(false), response_connection_close_(false), - response_header_key_prev_(false), expect_final_response_(false), - request_headers_normalized_(false) {} + http2_upgrade_seen_(false), chunked_request_(false), + request_connection_close_(false), request_header_key_prev_(false), + request_http2_expect_body_(false), chunked_response_(false), + response_connection_close_(false), response_header_key_prev_(false), + expect_final_response_(false) { + + ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0., + get_config()->stream_read_timeout); + ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0., + get_config()->stream_write_timeout); + ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0., + get_config()->stream_read_timeout); + ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0., + get_config()->stream_write_timeout); + + upstream_rtimer_.data = this; + upstream_wtimer_.data = this; + downstream_rtimer_.data = this; + downstream_wtimer_.data = this; + + http2::init_hdidx(request_hdidx_); + http2::init_hdidx(response_hdidx_); +} Downstream::~Downstream() { if (LOG_ENABLED(INFO)) { DLOG(INFO, this) << "Deleting"; } - if (response_body_buf_) { - // Passing NULL to evbuffer_free() causes segmentation fault. - evbuffer_free(response_body_buf_); - } - if (upstream_rtimerev_) { - event_free(upstream_rtimerev_); - } - if (upstream_wtimerev_) { - event_free(upstream_wtimerev_); - } - if (downstream_rtimerev_) { - event_free(downstream_rtimerev_); - } - if (downstream_wtimerev_) { - event_free(downstream_wtimerev_); + + // check nullptr for unittest + if (upstream_) { + auto loop = upstream_->get_client_handler()->get_loop(); + + ev_timer_stop(loop, &upstream_rtimer_); + ev_timer_stop(loop, &upstream_wtimer_); + ev_timer_stop(loop, &downstream_rtimer_); + ev_timer_stop(loop, &downstream_wtimer_); } + if (LOG_ENABLED(INFO)) { DLOG(INFO, this) << "Deleted"; } @@ -137,35 +215,15 @@ void Downstream::force_resume_read() { } namespace { -Headers::const_iterator get_norm_header(const Headers &headers, - const std::string &name) { - auto i = std::lower_bound(std::begin(headers), std::end(headers), - Header(name, ""), http2::name_less); - if (i != std::end(headers) && (*i).name == name) { - return i; +const Headers::value_type *get_header_linear(const Headers &headers, + const std::string &name) { + const Headers::value_type *res = nullptr; + for (auto &kv : headers) { + if (kv.name == name) { + res = &kv; + } } - return std::end(headers); -} -} // namespace - -namespace { -Headers::iterator get_norm_header(Headers &headers, const std::string &name) { - auto i = std::lower_bound(std::begin(headers), std::end(headers), - Header(name, ""), http2::name_less); - if (i != std::end(headers) && (*i).name == name) { - return i; - } - return std::end(headers); -} -} // namespace - -namespace { -Headers::const_iterator get_header_linear(const Headers &headers, - const std::string &name) { - auto i = std::find_if( - std::begin(headers), std::end(headers), - [&name](const Header &header) { return header.name == name; }); - return i; + return res; } } // namespace @@ -177,90 +235,70 @@ void Downstream::assemble_request_cookie() { std::string &cookie = assembled_request_cookie_; cookie = ""; for (auto &kv : request_headers_) { - if (util::strieq("cookie", kv.name.c_str())) { - auto end = kv.value.find_last_not_of(" ;"); - if (end == std::string::npos) { - cookie += kv.value; - } else { - cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1); - } - cookie += "; "; + if (kv.name.size() != 6 || kv.name[5] != 'e' || + !util::streq("cooki", kv.name.c_str(), 5)) { + continue; } + + auto end = kv.value.find_last_not_of(" ;"); + if (end == std::string::npos) { + cookie += kv.value; + } else { + cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1); + } + cookie += "; "; } if (cookie.size() >= 2) { cookie.erase(cookie.size() - 2); } } -void Downstream::crumble_request_cookie() { +Headers Downstream::crumble_request_cookie() { Headers cookie_hdrs; for (auto &kv : request_headers_) { - if (util::strieq("cookie", kv.name.c_str())) { - size_t last = kv.value.size(); - size_t num = 0; - std::string rep_cookie; + if (kv.name.size() != 6 || kv.name[5] != 'e' || + !util::streq("cooki", kv.name.c_str(), 5)) { + continue; + } + size_t last = kv.value.size(); - for (size_t j = 0; j < last;) { - j = kv.value.find_first_not_of("\t ;", j); - if (j == std::string::npos) { - break; - } - auto first = j; - - j = kv.value.find(';', j); - if (j == std::string::npos) { - j = last; - } - - if (num == 0) { - if (first == 0 && j == last) { - break; - } - rep_cookie = kv.value.substr(first, j - first); - } else { - cookie_hdrs.push_back( - Header("cookie", kv.value.substr(first, j - first), kv.no_index)); - } - ++num; + for (size_t j = 0; j < last;) { + j = kv.value.find_first_not_of("\t ;", j); + if (j == std::string::npos) { + break; } - if (num > 0) { - kv.value = std::move(rep_cookie); + auto first = j; + + j = kv.value.find(';', j); + if (j == std::string::npos) { + j = last; } + + cookie_hdrs.push_back( + Header("cookie", kv.value.substr(first, j - first), kv.no_index)); } } - request_headers_.insert(std::end(request_headers_), - std::make_move_iterator(std::begin(cookie_hdrs)), - std::make_move_iterator(std::end(cookie_hdrs))); - if (request_headers_normalized_) { - normalize_request_headers(); - } + return cookie_hdrs; } const std::string &Downstream::get_assembled_request_cookie() const { return assembled_request_cookie_; } -void Downstream::normalize_request_headers() { - http2::normalize_headers(request_headers_); - request_headers_normalized_ = true; -} - -Headers::const_iterator -Downstream::get_norm_request_header(const std::string &name) const { - return get_norm_header(request_headers_, name); -} - -Headers::const_iterator -Downstream::get_request_header(const std::string &name) const { - if (request_headers_normalized_) { - return get_norm_request_header(name); +void Downstream::index_request_headers() { + for (auto &kv : request_headers_) { + util::inp_strlower(kv.name); } - - return get_header_linear(request_headers_, name); + http2::index_headers(request_hdidx_, request_headers_); } -bool Downstream::get_request_headers_normalized() const { - return request_headers_normalized_; +const Headers::value_type *Downstream::get_request_header(int token) const { + return http2::get_header(request_hdidx_, token, request_headers_); +} + +const Headers::value_type * +Downstream::get_request_header(const std::string &name) const { + return get_header_linear(request_headers_, name); } void Downstream::add_request_header(std::string name, std::string value) { @@ -276,9 +314,10 @@ void Downstream::set_last_request_header_value(std::string value) { item.value = std::move(value); } -void Downstream::split_add_request_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index) { +void Downstream::add_request_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int token) { + http2::index_header(request_hdidx_, token, request_headers_.size()); request_headers_sum_ += namelen + valuelen; http2::add_header(request_headers_, name, namelen, value, valuelen, no_index); } @@ -302,7 +341,10 @@ void Downstream::append_last_request_header_value(const char *data, item.value.append(data, len); } -void Downstream::clear_request_headers() { Headers().swap(request_headers_); } +void Downstream::clear_request_headers() { + Headers().swap(request_headers_); + http2::init_hdidx(request_hdidx_); +} size_t Downstream::get_request_headers_sum() const { return request_headers_sum_; @@ -399,14 +441,16 @@ void Downstream::set_request_http2_expect_body(bool f) { request_http2_expect_body_ = f; } -bool Downstream::get_output_buffer_full() { +bool Downstream::request_buf_full() { if (dconn_) { - return dconn_->get_output_buffer_full(); + return request_buf_.rleft() >= 16384; } else { return false; } } +Memchunks4K *Downstream::get_request_buf() { return &request_buf_; } + // Call this function after this object is attached to // Downstream. Otherwise, the program will crash. int Downstream::push_request_headers() { @@ -446,19 +490,23 @@ const Headers &Downstream::get_response_headers() const { return response_headers_; } -void Downstream::normalize_response_headers() { - http2::normalize_headers(response_headers_); +void Downstream::index_response_headers() { + for (auto &kv : response_headers_) { + util::inp_strlower(kv.name); + } + http2::index_headers(response_hdidx_, response_headers_); } -Headers::const_iterator -Downstream::get_norm_response_header(const std::string &name) const { - return get_norm_header(response_headers_, name); +const Headers::value_type *Downstream::get_response_header(int token) const { + return http2::get_header(response_hdidx_, token, response_headers_); } -void Downstream::rewrite_norm_location_response_header( - const std::string &upstream_scheme, uint16_t upstream_port) { - auto hd = get_norm_header(response_headers_, "location"); - if (hd == std::end(response_headers_)) { +void +Downstream::rewrite_location_response_header(const std::string &upstream_scheme, + uint16_t upstream_port) { + auto hd = + http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_); + if (!hd) { return; } http_parser_url u; @@ -475,15 +523,16 @@ void Downstream::rewrite_norm_location_response_header( upstream_scheme, upstream_port); } if (new_uri.empty()) { - auto host = get_norm_request_header("host"); - if (host == std::end(request_headers_)) { + auto host = get_request_header(http2::HD_HOST); + if (!host) { return; } new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value, upstream_scheme, upstream_port); } if (!new_uri.empty()) { - (*hd).value = std::move(new_uri); + auto idx = response_hdidx_[http2::HD_LOCATION]; + response_headers_[idx].value = std::move(new_uri); } } @@ -500,9 +549,10 @@ void Downstream::set_last_response_header_value(std::string value) { item.value = std::move(value); } -void Downstream::split_add_response_header(const uint8_t *name, size_t namelen, - const uint8_t *value, - size_t valuelen, bool no_index) { +void Downstream::add_response_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + bool no_index, int token) { + http2::index_header(response_hdidx_, token, response_headers_.size()); response_headers_sum_ += namelen + valuelen; http2::add_header(response_headers_, name, namelen, value, valuelen, no_index); @@ -527,7 +577,10 @@ void Downstream::append_last_response_header_value(const char *data, item.value.append(data, len); } -void Downstream::clear_response_headers() { Headers().swap(response_headers_); } +void Downstream::clear_response_headers() { + Headers().swap(response_headers_); + http2::init_hdidx(response_hdidx_); +} size_t Downstream::get_response_headers_sum() const { return response_headers_sum_; @@ -585,17 +638,15 @@ void Downstream::set_response_state(int state) { response_state_ = state; } int Downstream::get_response_state() const { return response_state_; } -int Downstream::init_response_body_buf() { - if (!response_body_buf_) { - response_body_buf_ = evbuffer_new(); - if (response_body_buf_ == nullptr) { - DIE(); - } - } - return 0; -} +Memchunks4K *Downstream::get_response_buf() { return &response_buf_; } -evbuffer *Downstream::get_response_body_buf() { return response_body_buf_; } +bool Downstream::response_buf_full() { + if (dconn_) { + return response_buf_.rleft() >= 64 * 1024; + } else { + return false; + } +} void Downstream::add_response_bodylen(size_t amount) { response_bodylen_ += amount; @@ -641,37 +692,31 @@ void Downstream::inspect_http1_request() { upgrade_request_ = true; } - for (auto &hd : request_headers_) { - if (!upgrade_request_ && util::strieq("upgrade", hd.name.c_str())) { - // TODO Perform more strict checking for upgrade headers + if (!upgrade_request_) { + auto idx = request_hdidx_[http2::HD_UPGRADE]; + if (idx != -1) { upgrade_request_ = true; - if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, hd.value.c_str(), - hd.value.size())) { + auto &val = request_headers_[idx].value; + // TODO Perform more strict checking for upgrade headers + if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(), + val.size())) { http2_upgrade_seen_ = true; } - } else if (!http2_settings_seen_ && - util::strieq(hd.name.c_str(), "http2-settings")) { - - http2_settings_seen_ = true; - http2_settings_ = hd.value; - } else if (!chunked_request_ && - util::strieq(hd.name.c_str(), "transfer-encoding")) { - if (util::strifind(hd.value.c_str(), "chunked")) { - chunked_request_ = true; - } } } + auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING]; + if (idx != -1 && + util::strifind(request_headers_[idx].value.c_str(), "chunked")) { + chunked_request_ = true; + } } void Downstream::inspect_http1_response() { - for (auto &hd : response_headers_) { - if (!chunked_response_ && - util::strieq(hd.name.c_str(), "transfer-encoding")) { - if (util::strifind(hd.value.c_str(), "chunked")) { - chunked_response_ = true; - } - } + auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING]; + if (idx != -1 && + util::strifind(response_headers_[idx].value.c_str(), "chunked")) { + chunked_response_ = true; } } @@ -690,11 +735,20 @@ bool Downstream::get_upgraded() const { return upgraded_; } bool Downstream::get_upgrade_request() const { return upgrade_request_; } bool Downstream::get_http2_upgrade_request() const { - return request_bodylen_ == 0 && http2_upgrade_seen_ && http2_settings_seen_; + return request_bodylen_ == 0 && http2_upgrade_seen_ && + request_hdidx_[http2::HD_HTTP2_SETTINGS] != -1; } +namespace { +const std::string EMPTY; +} // namespace + const std::string &Downstream::get_http2_settings() const { - return http2_settings_; + auto idx = request_hdidx_[http2::HD_HTTP2_SETTINGS]; + if (idx == -1) { + return EMPTY; + } + return request_headers_[idx].value; } void Downstream::set_downstream_stream_id(int32_t stream_id) { @@ -756,207 +810,130 @@ bool pseudo_header_allowed(const Headers &headers) { } } // namespace -bool Downstream::request_pseudo_header_allowed() const { - return pseudo_header_allowed(request_headers_); +bool Downstream::request_pseudo_header_allowed(int token) const { + if (!pseudo_header_allowed(request_headers_)) { + return false; + } + return http2::check_http2_request_pseudo_header(request_hdidx_, token); } -bool Downstream::response_pseudo_header_allowed() const { - return pseudo_header_allowed(response_headers_); +bool Downstream::response_pseudo_header_allowed(int token) const { + if (!pseudo_header_allowed(response_headers_)) { + return false; + } + return http2::check_http2_response_pseudo_header(response_hdidx_, token); } namespace { -void upstream_timeoutcb(evutil_socket_t fd, short event, void *arg) { - auto downstream = static_cast(arg); - auto upstream = downstream->get_upstream(); - - auto which = event == EV_READ ? "read" : "write"; - - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "upstream timeout stream_id=" - << downstream->get_stream_id() << " event=" << which; - } - - downstream->disable_upstream_rtimer(); - downstream->disable_upstream_wtimer(); - - upstream->on_timeout(downstream); -} +void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); } } // namespace namespace { -void upstream_rtimeoutcb(evutil_socket_t fd, short event, void *arg) { - upstream_timeoutcb(fd, EV_READ, arg); -} -} // namespace - -namespace { -void upstream_wtimeoutcb(evutil_socket_t fd, short event, void *arg) { - upstream_timeoutcb(fd, EV_WRITE, arg); -} -} // namespace - -namespace { -event *init_timer(event_base *evbase, event_callback_fn cb, void *arg) { - auto timerev = evtimer_new(evbase, cb, arg); - - if (timerev == nullptr) { - LOG(WARN) << "timer initialization failed"; - return nullptr; - } - - return timerev; -} -} // namespace - -void Downstream::init_upstream_timer() { - auto evbase = upstream_->get_client_handler()->get_evbase(); - - if (get_config()->stream_read_timeout.tv_sec > 0) { - upstream_rtimerev_ = init_timer(evbase, upstream_rtimeoutcb, this); - } - - if (get_config()->stream_write_timeout.tv_sec > 0) { - upstream_wtimerev_ = init_timer(evbase, upstream_wtimeoutcb, this); - } -} - -namespace { -void reset_timer(event *timer, const timeval *timeout) { - if (!timer) { +void try_reset_timer(struct ev_loop *loop, ev_timer *w) { + if (!ev_is_active(w)) { return; } - - event_add(timer, timeout); + ev_timer_again(loop, w); } } // namespace namespace { -void try_reset_timer(event *timer, const timeval *timeout) { - if (!timer) { +void ensure_timer(struct ev_loop *loop, ev_timer *w) { + if (ev_is_active(w)) { return; } - - if (!evtimer_pending(timer, nullptr)) { - return; - } - - event_add(timer, timeout); + ev_timer_again(loop, w); } } // namespace namespace { -void ensure_timer(event *timer, const timeval *timeout) { - if (!timer) { - return; - } - - if (evtimer_pending(timer, nullptr)) { - return; - } - - event_add(timer, timeout); -} -} // namespace - -namespace { -void disable_timer(event *timer) { - if (!timer) { - return; - } - - event_del(timer); +void disable_timer(struct ev_loop *loop, ev_timer *w) { + ev_timer_stop(loop, w); } } // namespace void Downstream::reset_upstream_rtimer() { - reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout); - try_reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout); + if (get_config()->stream_read_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + reset_timer(loop, &upstream_rtimer_); } void Downstream::reset_upstream_wtimer() { - reset_timer(upstream_wtimerev_, &get_config()->stream_write_timeout); - try_reset_timer(upstream_rtimerev_, &get_config()->stream_read_timeout); + auto loop = upstream_->get_client_handler()->get_loop(); + if (get_config()->stream_write_timeout != 0.) { + reset_timer(loop, &upstream_wtimer_); + } + if (get_config()->stream_read_timeout != 0.) { + try_reset_timer(loop, &upstream_rtimer_); + } } void Downstream::ensure_upstream_wtimer() { - ensure_timer(upstream_wtimerev_, &get_config()->stream_write_timeout); + if (get_config()->stream_write_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + ensure_timer(loop, &upstream_wtimer_); } void Downstream::disable_upstream_rtimer() { - disable_timer(upstream_rtimerev_); + if (get_config()->stream_read_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &upstream_rtimer_); } void Downstream::disable_upstream_wtimer() { - disable_timer(upstream_wtimerev_); -} - -namespace { -void downstream_timeoutcb(evutil_socket_t fd, short event, void *arg) { - auto downstream = static_cast(arg); - - auto which = event == EV_READ ? "read" : "write"; - - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "downstream timeout stream_id=" - << downstream->get_downstream_stream_id() - << " event=" << which; - } - - downstream->disable_downstream_rtimer(); - downstream->disable_downstream_wtimer(); - - auto dconn = downstream->get_downstream_connection(); - - if (dconn) { - dconn->on_timeout(); - } -} -} // namespace - -namespace { -void downstream_rtimeoutcb(evutil_socket_t fd, short event, void *arg) { - downstream_timeoutcb(fd, EV_READ, arg); -} -} // namespace - -namespace { -void downstream_wtimeoutcb(evutil_socket_t fd, short event, void *arg) { - downstream_timeoutcb(fd, EV_WRITE, arg); -} -} // namespace - -void Downstream::init_downstream_timer() { - auto evbase = upstream_->get_client_handler()->get_evbase(); - - if (get_config()->stream_read_timeout.tv_sec > 0) { - downstream_rtimerev_ = init_timer(evbase, downstream_rtimeoutcb, this); - } - - if (get_config()->stream_write_timeout.tv_sec > 0) { - downstream_wtimerev_ = init_timer(evbase, downstream_wtimeoutcb, this); + if (get_config()->stream_write_timeout == 0.) { + return; } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &upstream_wtimer_); } void Downstream::reset_downstream_rtimer() { - reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout); - try_reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout); + if (get_config()->stream_read_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + reset_timer(loop, &downstream_rtimer_); } void Downstream::reset_downstream_wtimer() { - reset_timer(downstream_wtimerev_, &get_config()->stream_write_timeout); - try_reset_timer(downstream_rtimerev_, &get_config()->stream_read_timeout); + auto loop = upstream_->get_client_handler()->get_loop(); + if (get_config()->stream_write_timeout != 0.) { + reset_timer(loop, &downstream_wtimer_); + } + if (get_config()->stream_read_timeout != 0.) { + try_reset_timer(loop, &downstream_rtimer_); + } } void Downstream::ensure_downstream_wtimer() { - ensure_timer(downstream_wtimerev_, &get_config()->stream_write_timeout); + if (get_config()->stream_write_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + ensure_timer(loop, &downstream_wtimer_); } void Downstream::disable_downstream_rtimer() { - disable_timer(downstream_rtimerev_); + if (get_config()->stream_read_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &downstream_rtimer_); } void Downstream::disable_downstream_wtimer() { - disable_timer(downstream_wtimerev_); + if (get_config()->stream_write_timeout == 0.) { + return; + } + auto loop = upstream_->get_client_handler()->get_loop(); + disable_timer(loop, &downstream_wtimer_); } bool Downstream::accesslog_ready() const { return response_http_status_ > 0; } diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index a929c9c2..49a37cfd 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. @@ -95,30 +95,27 @@ public: const std::string &get_http2_settings() const; // downstream request API const Headers &get_request_headers() const; - void crumble_request_cookie(); + // Crumbles (split cookie by ";") in request_headers_ and returns + // them. Headers::no_index is inherited. + Headers crumble_request_cookie(); void assemble_request_cookie(); const std::string &get_assembled_request_cookie() const; - // Makes key lowercase and sort headers by name using < - void normalize_request_headers(); - // Returns iterator pointing to the request header with the name - // |name|. If multiple header have |name| as name, return first - // occurrence from the beginning. If no such header is found, - // returns std::end(get_request_headers()). This function must be - // called after calling normalize_request_headers(). - Headers::const_iterator - get_norm_request_header(const std::string &name) const; - // Returns iterator pointing to the request header with the name - // |name|. This function acts like get_norm_request_header(), but - // if request_headers_ was not normalized, use linear search to find - // the header. Otherwise, get_norm_request_header() is used. - Headers::const_iterator get_request_header(const std::string &name) const; - bool get_request_headers_normalized() const; + // Lower the request header field names and indexes request headers + void index_request_headers(); + // Returns pointer to the request header with the name |name|. If + // multiple header have |name| as name, return last occurrence from + // the beginning. If no such header is found, returns nullptr. + // This function must be called after headers are indexed + const Headers::value_type *get_request_header(int token) const; + // Returns pointer to the request header with the name |name|. If + // no such header is found, returns nullptr. + const Headers::value_type *get_request_header(const std::string &name) const; void add_request_header(std::string name, std::string value); void set_last_request_header_value(std::string value); - void split_add_request_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index); + void add_request_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int token); bool get_request_header_key_prev() const; void append_last_request_header_key(const char *data, size_t len); @@ -161,7 +158,7 @@ public: size_t get_request_datalen() const; void dec_request_datalen(size_t len); void reset_request_datalen(); - bool request_pseudo_header_allowed() const; + bool request_pseudo_header_allowed(int token) const; bool expect_response_body() const; enum { INITIAL, @@ -174,28 +171,25 @@ public: }; void set_request_state(int state); int get_request_state() const; + Memchunks4K *get_request_buf(); // downstream response API const Headers &get_response_headers() const; - // Makes key lowercase and sort headers by name using < - void normalize_response_headers(); - // Returns iterator pointing to the response header with the name - // |name|. If multiple header have |name| as name, return first - // occurrence from the beginning. If no such header is found, - // returns std::end(get_response_headers()). This function must be - // called after calling normalize_response_headers(). - Headers::const_iterator - get_norm_response_header(const std::string &name) const; - // Rewrites the location response header field. This function must - // be called after calling normalize_response_headers() and - // normalize_request_headers(). - void rewrite_norm_location_response_header(const std::string &upstream_scheme, - uint16_t upstream_port); + // Lower the response header field names and indexes response headers + void index_response_headers(); + // Returns pointer to the response header with the name |name|. If + // multiple header have |name| as name, return last occurrence from + // the beginning. If no such header is found, returns nullptr. + // This function must be called after response headers are indexed. + const Headers::value_type *get_response_header(int token) const; + // Rewrites the location response header field. + void rewrite_location_response_header(const std::string &upstream_scheme, + uint16_t upstream_port); void add_response_header(std::string name, std::string value); void set_last_response_header_value(std::string value); - void split_add_response_header(const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, - bool no_index); + void add_response_header(const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, bool no_index, + int token); bool get_response_header_key_prev() const; void append_last_response_header_key(const char *data, size_t len); @@ -218,8 +212,8 @@ public: void set_response_connection_close(bool f); void set_response_state(int state); int get_response_state() const; - int init_response_body_buf(); - evbuffer *get_response_body_buf(); + Memchunks4K *get_response_buf(); + bool response_buf_full(); void add_response_bodylen(size_t amount); int64_t get_response_bodylen() const; void add_response_sent_bodylen(size_t amount); @@ -237,7 +231,7 @@ public: void dec_response_datalen(size_t len); size_t get_response_datalen() const; void reset_response_datalen(); - bool response_pseudo_header_allowed() const; + bool response_pseudo_header_allowed(int token) const; // Call this method when there is incoming data in downstream // connection. @@ -252,18 +246,15 @@ public: bool get_rst_stream_after_end_stream() const; void set_rst_stream_after_end_stream(bool f); - // Initializes upstream timers, but they are not pending. - void init_upstream_timer(); - // Makes upstream read timer pending. If it is already pending, - // timeout value is reset. This function also resets write timer if - // it is already pending. + // Resets upstream read timer. If it is active, timeout value is + // reset. If it is not active, timer will be started. void reset_upstream_rtimer(); - // Makes upstream write timer pending. If it is already pending, - // timeout value is reset. This function also resets read timer if - // it is already pending. + // Resets upstream write timer. If it is active, timeout value is + // reset. If it is not active, timer will be started. This + // function also resets read timer if it has been started. void reset_upstream_wtimer(); - // Makes upstream write timer pending. If it is already pending, do - // nothing. + // Makes sure that upstream write timer is started. If it has been + // started, do nothing. Otherwise, write timer will be started. void ensure_upstream_wtimer(); // Disables upstream read timer. void disable_upstream_rtimer(); @@ -272,7 +263,6 @@ public: // Downstream timer functions. They works in a similar way just // like the upstream timer function. - void init_downstream_timer(); void reset_downstream_rtimer(); void reset_downstream_wtimer(); void ensure_downstream_wtimer(); @@ -282,6 +272,11 @@ public: // Returns true if accesslog can be written for this downstream. bool accesslog_ready() const; + enum { + EVENT_ERROR = 0x1, + EVENT_TIMEOUT = 0x2, + }; + private: Headers request_headers_; Headers response_headers_; @@ -292,7 +287,15 @@ private: std::string request_http2_authority_; std::chrono::high_resolution_clock::time_point request_start_time_; std::string assembled_request_cookie_; - std::string http2_settings_; + + Memchunks4K request_buf_; + Memchunks4K response_buf_; + + ev_timer upstream_rtimer_; + ev_timer upstream_wtimer_; + + ev_timer downstream_rtimer_; + ev_timer downstream_wtimer_; // the length of request body int64_t request_bodylen_; @@ -303,15 +306,6 @@ private: Upstream *upstream_; std::unique_ptr dconn_; - // This buffer is used to temporarily store downstream response - // body. nghttp2 library reads data from this in the callback. - evbuffer *response_body_buf_; - - event *upstream_rtimerev_; - event *upstream_wtimerev_; - - event *downstream_rtimerev_; - event *downstream_wtimerev_; size_t request_headers_sum_; size_t response_headers_sum_; @@ -337,6 +331,9 @@ private: int response_major_; int response_minor_; + int request_hdidx_[http2::HD_MAXIDX]; + int response_hdidx_[http2::HD_MAXIDX]; + // true if the request contains upgrade token (HTTP Upgrade or // CONNECT) bool upgrade_request_; @@ -344,7 +341,6 @@ private: bool upgraded_; bool http2_upgrade_seen_; - bool http2_settings_seen_; bool chunked_request_; bool request_connection_close_; @@ -355,9 +351,6 @@ private: bool response_connection_close_; bool response_header_key_prev_; bool expect_final_response_; - - // true if request_headers_ is normalized - bool request_headers_normalized_; }; } // namespace shrpx 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_downstream_queue.cc b/src/shrpx_downstream_queue.cc index b61605c8..e89c57b9 100644 --- a/src/shrpx_downstream_queue.cc +++ b/src/shrpx_downstream_queue.cc @@ -33,10 +33,11 @@ namespace shrpx { DownstreamQueue::HostEntry::HostEntry() : num_active(0) {} -DownstreamQueue::DownstreamQueue(size_t conn_max_per_host) +DownstreamQueue::DownstreamQueue(size_t conn_max_per_host, bool unified_host) : conn_max_per_host_(conn_max_per_host == 0 ? std::numeric_limits::max() - : conn_max_per_host) {} + : conn_max_per_host), + unified_host_(unified_host) {} DownstreamQueue::~DownstreamQueue() {} @@ -59,8 +60,19 @@ DownstreamQueue::find_host_entry(const std::string &host) { return (*itr).second; } +const std::string & +DownstreamQueue::make_host_key(const std::string &host) const { + static std::string empty_key; + return unified_host_ ? empty_key : host; +} + +const std::string & +DownstreamQueue::make_host_key(Downstream *downstream) const { + return make_host_key(downstream->get_request_http2_authority()); +} + void DownstreamQueue::add_active(std::unique_ptr downstream) { - auto &ent = find_host_entry(downstream->get_request_http2_authority()); + auto &ent = find_host_entry(make_host_key(downstream.get())); ++ent.num_active; auto stream_id = downstream->get_stream_id(); @@ -68,14 +80,14 @@ void DownstreamQueue::add_active(std::unique_ptr downstream) { } void DownstreamQueue::add_blocked(std::unique_ptr downstream) { - auto &ent = find_host_entry(downstream->get_request_http2_authority()); + auto &ent = find_host_entry(make_host_key(downstream.get())); auto stream_id = downstream->get_stream_id(); ent.blocked.insert(stream_id); blocked_downstreams_[stream_id] = std::move(downstream); } bool DownstreamQueue::can_activate(const std::string &host) const { - auto itr = host_entries_.find(host); + auto itr = host_entries_.find(make_host_key(host)); if (itr == std::end(host_entries_)) { return true; } @@ -119,7 +131,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) { if (kv != std::end(active_downstreams_)) { auto downstream = pop_downstream(kv, active_downstreams_); - auto &host = downstream->get_request_http2_authority(); + auto &host = make_host_key(downstream.get()); auto &ent = find_host_entry(host); --ent.num_active; @@ -148,7 +160,7 @@ DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) { if (kv != std::end(blocked_downstreams_)) { auto downstream = pop_downstream(kv, blocked_downstreams_); - auto &host = downstream->get_request_http2_authority(); + auto &host = make_host_key(downstream.get()); auto &ent = find_host_entry(host); ent.blocked.erase(stream_id); diff --git a/src/shrpx_downstream_queue.h b/src/shrpx_downstream_queue.h index 3b7364e7..17b5bce0 100644 --- a/src/shrpx_downstream_queue.h +++ b/src/shrpx_downstream_queue.h @@ -52,7 +52,7 @@ public: typedef std::map HostEntryMap; // conn_max_per_host == 0 means no limit for downstream connection. - DownstreamQueue(size_t conn_max_per_host = 0); + DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true); ~DownstreamQueue(); void add_pending(std::unique_ptr downstream); void add_failure(std::unique_ptr downstream); @@ -82,6 +82,8 @@ public: Downstream *find(int32_t stream_id); const DownstreamMap &get_active_downstreams() const; HostEntry &find_host_entry(const std::string &host); + const std::string &make_host_key(const std::string &host) const; + const std::string &make_host_key(Downstream *downstream) const; // Maximum number of concurrent connections to the same host. size_t conn_max_per_host_; @@ -98,6 +100,9 @@ private: DownstreamMap active_downstreams_; // Downstream objects, blocked by conn_max_per_host_ DownstreamMap blocked_downstreams_; + // true if downstream host is treated as the same. Used for reverse + // proxying. + bool unified_host_; }; } // namespace shrpx diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc index b2924034..aab7ba85 100644 --- a/src/shrpx_downstream_test.cc +++ b/src/shrpx_downstream_test.cc @@ -32,7 +32,7 @@ namespace shrpx { -void test_downstream_normalize_request_headers(void) { +void test_downstream_index_request_headers(void) { Downstream d(nullptr, 0, 0); d.add_request_header("1", "0"); d.add_request_header("2", "1"); @@ -42,88 +42,83 @@ void test_downstream_normalize_request_headers(void) { d.add_request_header("BravO", "5"); d.add_request_header(":method", "6"); d.add_request_header(":authority", "7"); - d.normalize_request_headers(); + d.index_request_headers(); - auto ans = Headers{{":authority", "7"}, - {":method", "6"}, - {"1", "0"}, + auto ans = Headers{{"1", "0"}, {"2", "1"}, - {"alpha", "3"}, - {"bravo", "5"}, {"charlie", "2"}, - {"delta", "4"}}; + {"alpha", "3"}, + {"delta", "4"}, + {"bravo", "5"}, + {":method", "6"}, + {":authority", "7"}}; CU_ASSERT(ans == d.get_request_headers()); } -void test_downstream_normalize_response_headers(void) { +void test_downstream_index_response_headers(void) { Downstream d(nullptr, 0, 0); d.add_response_header("Charlie", "0"); d.add_response_header("Alpha", "1"); d.add_response_header("Delta", "2"); d.add_response_header("BravO", "3"); - d.normalize_response_headers(); + d.index_response_headers(); auto ans = - Headers{{"alpha", "1"}, {"bravo", "3"}, {"charlie", "0"}, {"delta", "2"}}; + Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}}; CU_ASSERT(ans == d.get_response_headers()); } -void test_downstream_get_norm_request_header(void) { +void test_downstream_get_request_header(void) { Downstream d(nullptr, 0, 0); d.add_request_header("alpha", "0"); - d.add_request_header("bravo", "1"); - d.add_request_header("bravo", "2"); - d.add_request_header("charlie", "3"); - d.add_request_header("delta", "4"); - d.add_request_header("echo", "5"); - auto i = d.get_norm_request_header("alpha"); - CU_ASSERT(Header("alpha", "0") == *i); - i = d.get_norm_request_header("bravo"); - CU_ASSERT(Header("bravo", "1") == *i); - i = d.get_norm_request_header("delta"); - CU_ASSERT(Header("delta", "4") == *i); - i = d.get_norm_request_header("echo"); - CU_ASSERT(Header("echo", "5") == *i); - i = d.get_norm_request_header("foxtrot"); - CU_ASSERT(i == std::end(d.get_request_headers())); + d.add_request_header(":authority", "1"); + d.add_request_header("content-length", "2"); + d.index_request_headers(); + + // By token + CU_ASSERT(Header(":authority", "1") == + *d.get_request_header(http2::HD__AUTHORITY)); + CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD)); + + // By name + CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha")); + CU_ASSERT(nullptr == d.get_request_header("bravo")); } -void test_downstream_get_norm_response_header(void) { +void test_downstream_get_response_header(void) { Downstream d(nullptr, 0, 0); d.add_response_header("alpha", "0"); - d.add_response_header("bravo", "1"); - d.add_response_header("bravo", "2"); - d.add_response_header("charlie", "3"); - d.add_response_header("delta", "4"); - d.add_response_header("echo", "5"); - auto i = d.get_norm_response_header("alpha"); - CU_ASSERT(Header("alpha", "0") == *i); - i = d.get_norm_response_header("bravo"); - CU_ASSERT(Header("bravo", "1") == *i); - i = d.get_norm_response_header("delta"); - CU_ASSERT(Header("delta", "4") == *i); - i = d.get_norm_response_header("echo"); - CU_ASSERT(Header("echo", "5") == *i); - i = d.get_norm_response_header("foxtrot"); - CU_ASSERT(i == std::end(d.get_response_headers())); + d.add_response_header(":status", "1"); + d.add_response_header("content-length", "2"); + d.index_response_headers(); + + // By token + CU_ASSERT(Header(":status", "1") == + *d.get_response_header(http2::HD__STATUS)); + CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD)); } void test_downstream_crumble_request_cookie(void) { Downstream d(nullptr, 0, 0); d.add_request_header(":method", "get"); d.add_request_header(":path", "/"); - d.add_request_header("cookie", "alpha; bravo; ; ;; charlie;;"); + auto val = "alpha; bravo; ; ;; charlie;;"; + d.add_request_header( + reinterpret_cast("cookie"), sizeof("cookie") - 1, + reinterpret_cast(val), strlen(val), true, -1); d.add_request_header("cookie", ";delta"); d.add_request_header("cookie", "echo"); - d.crumble_request_cookie(); - Headers ans = {{":method", "get"}, - {":path", "/"}, - {"cookie", "alpha"}, - {"cookie", "delta"}, - {"cookie", "echo"}, + auto cookies = d.crumble_request_cookie(); + + Headers ans = {{"cookie", "alpha"}, {"cookie", "bravo"}, - {"cookie", "charlie"}}; - CU_ASSERT(ans == d.get_request_headers()); + {"cookie", "charlie"}, + {"cookie", "delta"}, + {"cookie", "echo"}}; + CU_ASSERT(ans == cookies); + CU_ASSERT(cookies[0].no_index); + CU_ASSERT(cookies[1].no_index); + CU_ASSERT(cookies[2].no_index); } void test_downstream_assemble_request_cookie(void) { @@ -138,21 +133,24 @@ void test_downstream_assemble_request_cookie(void) { CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie()); } -void test_downstream_rewrite_norm_location_response_header(void) { +void test_downstream_rewrite_location_response_header(void) { { Downstream d(nullptr, 0, 0); d.add_request_header("host", "localhost:3000"); d.add_response_header("location", "http://localhost:3000/"); - d.rewrite_norm_location_response_header("https", 443); - auto location = d.get_norm_response_header("location"); + d.index_request_headers(); + d.index_response_headers(); + d.rewrite_location_response_header("https", 443); + auto location = d.get_response_header(http2::HD_LOCATION); CU_ASSERT("https://localhost/" == (*location).value); } { Downstream d(nullptr, 0, 0); d.set_request_http2_authority("localhost"); d.add_response_header("location", "http://localhost/"); - d.rewrite_norm_location_response_header("https", 443); - auto location = d.get_norm_response_header("location"); + d.index_response_headers(); + d.rewrite_location_response_header("https", 443); + auto location = d.get_response_header(http2::HD_LOCATION); CU_ASSERT("https://localhost/" == (*location).value); } } diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h index 19effd91..b1e1aee5 100644 --- a/src/shrpx_downstream_test.h +++ b/src/shrpx_downstream_test.h @@ -27,13 +27,13 @@ namespace shrpx { -void test_downstream_normalize_request_headers(void); -void test_downstream_normalize_response_headers(void); -void test_downstream_get_norm_request_header(void); -void test_downstream_get_norm_response_header(void); +void test_downstream_index_request_headers(void); +void test_downstream_index_response_headers(void); +void test_downstream_get_request_header(void); +void test_downstream_get_response_header(void); void test_downstream_crumble_request_cookie(void); void test_downstream_assemble_request_cookie(void); -void test_downstream_rewrite_norm_location_response_header(void); +void test_downstream_rewrite_location_response_header(void); } // namespace shrpx diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc index 56409832..09080293 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,38 +95,17 @@ Http2DownstreamConnection::~Http2DownstreamConnection() { } } -int Http2DownstreamConnection::init_request_body_buf() { - int rv; - if (request_body_buf_) { - rv = evbuffer_drain(request_body_buf_, - evbuffer_get_length(request_body_buf_)); - if (rv != 0) { - return -1; - } - } else { - request_body_buf_ = evbuffer_new(); - if (request_body_buf_ == nullptr) { - return -1; - } - } - return 0; -} - int Http2DownstreamConnection::attach_downstream(Downstream *downstream) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; } - if (init_request_body_buf() == -1) { - return -1; - } http2session_->add_downstream_connection(this); if (http2session_->get_state() == Http2Session::DISCONNECTED) { - http2session_->notify(); + http2session_->signal_write(); } downstream_ = downstream; - - downstream_->init_downstream_timer(); + downstream_->reset_downstream_rtimer(); return 0; } @@ -145,7 +115,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) { DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; } if (submit_rst_stream(downstream) == 0) { - http2session_->notify(); + http2session_->signal_write(); } if (downstream_->get_downstream_stream_id() != -1) { @@ -154,7 +124,7 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) { downstream_->reset_response_datalen(); - http2session_->notify(); + http2session_->signal_write(); } downstream->disable_downstream_rtimer(); @@ -201,13 +171,9 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, // on the priority, DATA frame may come first. return NGHTTP2_ERR_DEFERRED; } - auto body = dconn->get_request_body_buf(); - - auto nread = evbuffer_remove(body, buf, length); - if (nread == -1) { - DCLOG(FATAL, dconn) << "evbuffer_remove() failed"; - return NGHTTP2_ERR_CALLBACK_FAILURE; - } + auto input = downstream->get_request_buf(); + auto nread = input->remove(buf, length); + auto input_empty = input->rleft() == 0; if (nread > 0) { // This is important because it will handle flow control @@ -225,7 +191,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, } } - if (evbuffer_get_length(body) == 0 && + if (input_empty && downstream->get_request_state() == Downstream::MSG_COMPLETE && // If connection is upgraded, don't set EOF flag, since HTTP/1 // will set MSG_COMPLETE to request state after upgrade response @@ -237,7 +203,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id, *data_flags |= NGHTTP2_DATA_FLAG_EOF; } - if (evbuffer_get_length(body) > 0) { + if (!input_empty) { downstream->reset_downstream_wtimer(); } else { downstream->disable_downstream_wtimer(); @@ -266,14 +232,12 @@ int Http2DownstreamConnection::push_request_headers() { return 0; } size_t nheader = downstream_->get_request_headers().size(); + + Headers cookies; if (!get_config()->http2_no_cookie_crumbling) { - downstream_->crumble_request_cookie(); + cookies = downstream_->crumble_request_cookie(); } - assert(downstream_->get_request_headers_normalized()); - - auto end_headers = std::end(downstream_->get_request_headers()); - // 7 means: // 1. :method // 2. :scheme @@ -283,10 +247,12 @@ int Http2DownstreamConnection::push_request_headers() { // 6. x-forwarded-for (optional) // 7. x-forwarded-proto (optional) auto nva = std::vector(); - nva.reserve(nheader + 7); + nva.reserve(nheader + 7 + cookies.size()); + std::string via_value; std::string xff_value; std::string scheme, authority, path, query; + // To reconstruct HTTP/1 status line and headers, proxy should // preserve host header field. See draft-09 section 8.1.3.1. if (downstream_->get_request_method() == "CONNECT") { @@ -306,7 +272,7 @@ int Http2DownstreamConnection::push_request_headers() { if (!downstream_->get_request_http2_authority().empty()) { nva.push_back(http2::make_nv_ls( ":authority", downstream_->get_request_http2_authority())); - } else if (downstream_->get_norm_request_header("host") == end_headers) { + } else if (!downstream_->get_request_header(http2::HD_HOST)) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "host header field missing"; } @@ -363,7 +329,7 @@ int Http2DownstreamConnection::push_request_headers() { authority += util::utos(u.port); } nva.push_back(http2::make_nv_ls(":authority", authority)); - } else if (downstream_->get_norm_request_header("host") == end_headers) { + } else if (!downstream_->get_request_header(http2::HD_HOST)) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "host header field missing"; } @@ -374,27 +340,30 @@ int Http2DownstreamConnection::push_request_headers() { nva.push_back( http2::make_nv_ls(":method", downstream_->get_request_method())); - http2::copy_norm_headers_to_nva(nva, downstream_->get_request_headers()); + http2::copy_headers_to_nva(nva, downstream_->get_request_headers()); bool chunked_encoding = false; auto transfer_encoding = - downstream_->get_norm_request_header("transfer-encoding"); - if (transfer_encoding != end_headers && + downstream_->get_request_header(http2::HD_TRANSFER_ENCODING); + if (transfer_encoding && util::strieq((*transfer_encoding).value.c_str(), "chunked")) { chunked_encoding = true; } - auto xff = downstream_->get_norm_request_header("x-forwarded-for"); + for (auto &nv : cookies) { + nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index)); + } + + auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR); if (get_config()->add_x_forwarded_for) { - if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) { + if (xff && !get_config()->strip_incoming_x_forwarded_for) { xff_value = (*xff).value; xff_value += ", "; } xff_value += downstream_->get_upstream()->get_client_handler()->get_ipaddr(); nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value)); - } else if (xff != end_headers && - !get_config()->strip_incoming_x_forwarded_for) { + } else if (xff && !get_config()->strip_incoming_x_forwarded_for) { nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value)); } @@ -412,13 +381,13 @@ int Http2DownstreamConnection::push_request_headers() { } } - auto via = downstream_->get_norm_request_header("via"); + auto via = downstream_->get_request_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { nva.push_back(http2::make_nv_ls("via", (*via).value)); } } else { - if (via != end_headers) { + if (via) { via_value = (*via).value; via_value += ", "; } @@ -440,7 +409,8 @@ int Http2DownstreamConnection::push_request_headers() { } auto content_length = - downstream_->get_norm_request_header("content-length") != end_headers; + downstream_->get_request_header(http2::HD_CONTENT_LENGTH); + // TODO check content-length: 0 case if (downstream_->get_request_method() == "CONNECT" || chunked_encoding || content_length || downstream_->get_request_http2_expect_body()) { @@ -461,17 +431,15 @@ int Http2DownstreamConnection::push_request_headers() { downstream_->reset_downstream_wtimer(); - http2session_->notify(); + http2session_->signal_write(); return 0; } int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data, size_t datalen) { - int rv = evbuffer_add(request_body_buf_, data, datalen); - if (rv != 0) { - DCLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + int rv; + auto output = downstream_->get_request_buf(); + output->append(data, datalen); if (downstream_->get_downstream_stream_id() != -1) { rv = http2session_->resume_data(this); if (rv != 0) { @@ -480,7 +448,7 @@ int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data, downstream_->ensure_downstream_wtimer(); - http2session_->notify(); + http2session_->signal_write(); } return 0; } @@ -495,7 +463,7 @@ int Http2DownstreamConnection::end_upload_data() { downstream_->ensure_downstream_wtimer(); - http2session_->notify(); + http2session_->signal_write(); } return 0; } @@ -525,7 +493,7 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason, downstream_->dec_response_datalen(consumed); - http2session_->notify(); + http2session_->signal_write(); } return 0; @@ -535,10 +503,6 @@ int Http2DownstreamConnection::on_read() { return 0; } int Http2DownstreamConnection::on_write() { return 0; } -evbuffer *Http2DownstreamConnection::get_request_body_buf() const { - return request_body_buf_; -} - void Http2DownstreamConnection::attach_stream_data(StreamData *sd) { // It is possible sd->dconn is not NULL. sd is detached when // on_stream_close_callback. Before that, after MSG_COMPLETE is set @@ -556,18 +520,8 @@ StreamData *Http2DownstreamConnection::detach_stream_data() { sd_ = nullptr; sd->dconn = nullptr; return sd; - } else { - return nullptr; - } -} - -bool Http2DownstreamConnection::get_output_buffer_full() { - if (request_body_buf_) { - return evbuffer_get_length(request_body_buf_) >= - Http2Session::OUTBUF_MAX_THRES; - } else { - return false; } + return nullptr; } int Http2DownstreamConnection::on_priority_change(int32_t pri) { @@ -584,7 +538,7 @@ int Http2DownstreamConnection::on_priority_change(int32_t pri) { DLOG(FATAL, this) << "nghttp2_submit_priority() failed"; return -1; } - http2session_->notify(); + http2session_->signal_write(); return 0; } 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..11875d71 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,132 @@ #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); + http2session->stop_settings_timer(); + SSLOG(INFO, http2session) << "SETTINGS timeout"; + if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { + http2session->disconnect(); + return; + } + http2session->signal_write(); +} +} // namespace + +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 +178,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 +213,6 @@ int Http2Session::disconnect(bool hard) { proxy_htp_.reset(); } - notified_ = false; connection_check_state_ = CONNECTION_CHECK_NONE; state_ = DISCONNECTED; @@ -156,249 +254,6 @@ 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 - -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 - int Http2Session::check_cert() { return ssl::check_cert(ssl_); } int Http2Session::initiate_connection() { @@ -410,36 +265,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 +335,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 +412,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 +425,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 +445,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 +616,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 +669,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_again(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 { @@ -859,18 +710,24 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { - if (!downstream->response_pseudo_header_allowed() || - !http2::check_http2_response_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + if (name[0] == ':') { + if (!downstream->response_pseudo_header_allowed(token)) { http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } - downstream->split_add_response_header(name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + if (!http2::http2_header_allowed(token)) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + downstream->add_response_header(name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); return 0; } } // namespace @@ -913,20 +770,11 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream, auto upstream = downstream->get_upstream(); - downstream->normalize_response_headers(); auto &nva = downstream->get_response_headers(); downstream->set_expect_final_response(false); - if (!http2::check_http2_response_headers(nva)) { - http2session->submit_rst_stream(frame->hd.stream_id, - NGHTTP2_PROTOCOL_ERROR); - downstream->set_response_state(Downstream::MSG_RESET); - call_downstream_readcb(http2session, downstream); - return 0; - } - - auto status = http2::get_unique_header(nva, ":status"); + auto status = downstream->get_response_header(http2::HD__STATUS); int status_code; if (!http2::non_empty_value(status) || @@ -974,7 +822,8 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream, return 0; } - auto content_length = http2::get_header(nva, "content-length"); + auto content_length = + downstream->get_response_header(http2::HD_CONTENT_LENGTH); if (!content_length && downstream->get_request_method() != "HEAD" && downstream->get_request_method() != "CONNECT") { unsigned int status; @@ -1090,9 +939,7 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, if (rv != 0) { return 0; } - } - - if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { + } else if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { if (downstream->get_expect_final_response()) { rv = on_response_headers(http2session, downstream, session, frame); @@ -1265,9 +1112,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 +1151,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 +1181,7 @@ int Http2Session::on_connect() { return -1; } } + nghttp2_session_callbacks *callbacks; rv = nghttp2_session_callbacks_new(&callbacks); @@ -1412,10 +1252,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 +1270,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 +1299,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 +1329,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_pending_ += n; + data_pendinglen_ -= n; + return 0; } + data_pending_ = nullptr; + data_pendinglen_ = 0; + } + + for (;;) { const uint8_t *data; auto datalen = nghttp2_session_mem_send(session_, &data); 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 +1414,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 +1435,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 +1452,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 +1492,268 @@ 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 (!util::check_socket_connected(fd_)) { + return -1; + } + + 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; + } + + wb_.reset(); + if (on_write() != 0) { + return -1; + } + if (wb_.rleft() == 0) { + 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; + } + + rb_.reset(); + 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; + } + wb_.reset(); + 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..0b9eaa2f 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) { @@ -127,9 +123,7 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) { auto downstream = http->pop_downstream(); downstream->reset_upstream(this); downstream->set_stream_id(1); - downstream->init_upstream_timer(); downstream->reset_upstream_rtimer(); - downstream->init_response_body_buf(); downstream->set_stream_id(1); downstream->set_priority(0); @@ -142,46 +136,12 @@ int Http2Upstream::upgrade_upstream(HttpsUpstream *http) { return 0; } -namespace { -void settings_timeout_cb(evutil_socket_t fd, short what, void *arg) { - auto upstream = static_cast(arg); - ULOG(INFO, upstream) << "SETTINGS timeout"; - if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { - delete upstream->get_client_handler(); - return; - } - if (upstream->send() != 0) { - delete upstream->get_client_handler(); - } -} -} // namespace - -int Http2Upstream::start_settings_timer() { - int rv; - // We submit SETTINGS only once - if (settings_timerev_) { - return 0; - } - settings_timerev_ = - evtimer_new(handler_->get_evbase(), settings_timeout_cb, this); - if (settings_timerev_ == nullptr) { - return -1; - } - // SETTINGS ACK timeout is 10 seconds for now - timeval settings_timeout = {10, 0}; - rv = evtimer_add(settings_timerev_, &settings_timeout); - if (rv == -1) { - return -1; - } - return 0; +void Http2Upstream::start_settings_timer() { + ev_timer_start(handler_->get_loop(), &settings_timer_); } void Http2Upstream::stop_settings_timer() { - if (settings_timerev_ == nullptr) { - return; - } - event_free(settings_timerev_); - settings_timerev_ = nullptr; + ev_timer_stop(handler_->get_loop(), &settings_timer_); } namespace { @@ -224,17 +184,22 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (namelen > 0 && name[0] == ':') { - if (!downstream->request_pseudo_header_allowed() || - !http2::check_http2_request_pseudo_header(name, namelen)) { + auto token = http2::lookup_token(name, namelen); + if (name[0] == ':') { + if (!downstream->request_pseudo_header_allowed(token)) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } } - downstream->split_add_request_header(name, namelen, value, valuelen, - flags & NGHTTP2_NV_FLAG_NO_INDEX); + if (!http2::http2_header_allowed(token)) { + upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + downstream->add_request_header(name, namelen, value, valuelen, + flags & NGHTTP2_NV_FLAG_NO_INDEX, token); return 0; } } // namespace @@ -256,9 +221,7 @@ int on_begin_headers_callback(nghttp2_session *session, auto downstream = util::make_unique(upstream, frame->hd.stream_id, 0); - downstream->init_upstream_timer(); downstream->reset_upstream_rtimer(); - downstream->init_response_body_buf(); // Although, we deprecated minor version from HTTP/2, we supply // minor version 0 to use via header field in a conventional way. @@ -278,7 +241,6 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, return 0; } - downstream->normalize_request_headers(); auto &nva = downstream->get_request_headers(); if (LOG_ENABLED(INFO)) { @@ -294,17 +256,11 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream, http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva); } - if (!http2::check_http2_request_headers(nva)) { - upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); - - return 0; - } - - auto host = http2::get_unique_header(nva, "host"); - auto authority = http2::get_unique_header(nva, ":authority"); - auto path = http2::get_unique_header(nva, ":path"); - auto method = http2::get_unique_header(nva, ":method"); - auto scheme = http2::get_unique_header(nva, ":scheme"); + auto host = downstream->get_request_header(http2::HD_HOST); + auto authority = downstream->get_request_header(http2::HD__AUTHORITY); + auto path = downstream->get_request_header(http2::HD__PATH); + auto method = downstream->get_request_header(http2::HD__METHOD); + auto scheme = downstream->get_request_header(http2::HD__SCHEME); bool is_connect = method && "CONNECT" == method->value; bool having_host = http2::non_empty_value(host); @@ -540,10 +496,8 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, switch (frame->hd.type) { case NGHTTP2_SETTINGS: - if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 && - upstream->start_settings_timer() != 0) { - - return NGHTTP2_ERR_CALLBACK_FAILURE; + if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { + upstream->start_settings_timer(); } break; case NGHTTP2_GOAWAY: @@ -598,19 +552,28 @@ uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) { } // namespace namespace { -void write_notify_cb(evutil_socket_t fd, short what, void *arg) { - auto upstream = static_cast(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 Http2Upstream::Http2Upstream(ClientHandler *handler) - : downstream_queue_(get_config()->http2_proxy - ? get_config()->downstream_connections_per_host - : 0), - handler_(handler), session_(nullptr), settings_timerev_(nullptr), - write_notifyev_(nullptr), deferred_(false) { - reset_timeouts(); + : downstream_queue_( + get_config()->http2_proxy + ? get_config()->downstream_connections_per_host + : get_config()->downstream_proto == PROTO_HTTP + ? get_config()->downstream_connections_per_frontend + : 0, + !get_config()->http2_proxy), + handler_(handler), session_(nullptr), data_pending_(nullptr), + data_pendinglen_(0), deferred_(false) { int rv; @@ -699,74 +662,76 @@ Http2Upstream::Http2Upstream(ClientHandler *handler) } } - write_notifyev_ = evtimer_new(handler_->get_evbase(), write_notify_cb, this); + // We wait for SETTINGS ACK at least 10 seconds. + ev_timer_init(&settings_timer_, settings_timeout_cb, 10., 0.); + + settings_timer_.data = this; + + handler_->reset_upstream_read_timeout( + get_config()->http2_upstream_read_timeout); + + handler_->signal_write(); } Http2Upstream::~Http2Upstream() { nghttp2_session_del(session_); - if (settings_timerev_) { - event_free(settings_timerev_); - } - if (write_notifyev_) { - event_free(write_notifyev_); - } + ev_timer_stop(handler_->get_loop(), &settings_timer_); } int Http2Upstream::on_read() { ssize_t rv = 0; - auto bev = handler_->get_bev(); - auto input = bufferevent_get_input(bev); + auto rb = handler_->get_rb(); for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); - - if (inputlen == 0) { - assert(evbuffer_get_length(input) == 0); - - return send(); + const void *data; + size_t nread; + std::tie(data, nread) = rb->get(); + if (nread == 0) { + break; } - auto mem = evbuffer_pullup(input, inputlen); - - rv = nghttp2_session_mem_recv(session_, mem, inputlen); + rv = nghttp2_session_mem_recv( + session_, reinterpret_cast(data), nread); if (rv < 0) { ULOG(ERROR, this) << "nghttp2_session_recv() returned error: " << nghttp2_strerror(rv); return -1; } - if (evbuffer_drain(input, rv) != 0) { - DCLOG(FATAL, this) << "evbuffer_drain() failed"; - return -1; - } + rb->drain(nread); } -} -int Http2Upstream::on_write() { return send(); } - -int Http2Upstream::send() { - if (write_notifyev_ == nullptr) { + auto wb = handler_->get_wb(); + if (nghttp2_session_want_read(session_) == 0 && + nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, this) << "No more read/write for this HTTP2 session"; + } return -1; } - event_active(write_notifyev_, 0, 0); + handler_->signal_write(); return 0; } // After this function call, downstream may be deleted. -int Http2Upstream::perform_send() { - int rv; - uint8_t buf[16384]; - auto bev = handler_->get_bev(); - auto output = bufferevent_get_output(bev); +int Http2Upstream::on_write() { + auto wb = handler_->get_wb(); - sendbuf.reset(output, buf, sizeof(buf), handler_->get_write_limit()); - for (;;) { - // Check buffer length and break if it is large enough. - if (handler_->get_outbuf_length() > 0) { - break; + if (data_pending_) { + auto n = std::min(wb->wleft(), data_pendinglen_); + wb->write(data_pending_, n); + if (n < data_pendinglen_) { + data_pending_ += n; + data_pendinglen_ -= n; + return 0; } + data_pending_ = nullptr; + data_pendinglen_ = 0; + } + + for (;;) { const uint8_t *data; auto datalen = nghttp2_session_mem_send(session_, &data); @@ -778,55 +743,38 @@ int Http2Upstream::perform_send() { if (datalen == 0) { break; } - rv = sendbuf.add(data, datalen); - if (rv != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } - if (deferred_) { - break; + auto n = wb->write(data, datalen); + if (n < static_cast(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) { @@ -1203,14 +1097,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { } } - downstream->normalize_response_headers(); if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } - auto end_headers = std::end(downstream->get_response_headers()); size_t nheader = downstream->get_response_headers().size(); auto nva = std::vector(); // 3 means :status and possible server and via header field. @@ -1219,7 +1111,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { auto response_status = util::utos(downstream->get_response_http_status()); nva.push_back(http2::make_nv_ls(":status", response_status)); - http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers()); + http2::copy_headers_to_nva(nva, downstream->get_response_headers()); if (downstream->get_non_final_response()) { if (LOG_ENABLED(INFO)) { @@ -1243,19 +1135,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { if (!get_config()->http2_proxy && !get_config()->client_proxy) { nva.push_back(http2::make_nv_lc("server", get_config()->server_name)); } else { - auto server = downstream->get_norm_response_header("server"); - if (server != end_headers) { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { nva.push_back(http2::make_nv_ls("server", (*server).value)); } } - auto via = downstream->get_norm_response_header("via"); + auto via = downstream->get_response_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { nva.push_back(http2::make_nv_ls("via", (*via).value)); } } else { - if (via != end_headers) { + if (via) { via_value = (*via).value; via_value += ", "; } @@ -1304,12 +1196,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { int Http2Upstream::on_downstream_body(Downstream *downstream, const uint8_t *data, size_t len, bool flush) { - auto body = downstream->get_response_body_buf(); - int rv = evbuffer_add(body, data, len); - if (rv != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + auto body = downstream->get_response_buf(); + body->append(data, len); if (flush) { nghttp2_session_resume_data(session_, downstream->get_stream_id()); @@ -1317,16 +1205,6 @@ int Http2Upstream::on_downstream_body(Downstream *downstream, downstream->ensure_upstream_wtimer(); } - if (evbuffer_get_length(body) >= INBUF_MAX_THRES) { - if (!flush) { - nghttp2_session_resume_data(session_, downstream->get_stream_id()); - - downstream->ensure_upstream_wtimer(); - } - - downstream->pause_read(SHRPX_NO_BUFFER); - } - return 0; } @@ -1358,7 +1236,8 @@ int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream, downstream->dec_request_datalen(consumed); } - return send(); + handler_->signal_write(); + return 0; } int Http2Upstream::on_downstream_abort_request(Downstream *downstream, @@ -1371,7 +1250,8 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream, return -1; } - return send(); + handler_->signal_write(); + return 0; } int Http2Upstream::consume(int32_t stream_id, size_t len) { @@ -1414,11 +1294,6 @@ int Http2Upstream::on_timeout(Downstream *downstream) { return 0; } -void Http2Upstream::reset_timeouts() { - handler_->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout, - &get_config()->upstream_write_timeout); -} - void Http2Upstream::on_handler_delete() { for (auto &ent : downstream_queue_.get_active_downstreams()) { if (ent.second->accesslog_ready()) { @@ -1453,14 +1328,13 @@ int Http2Upstream::on_downstream_reset() { } } - rv = send(); - if (rv != 0) { - return -1; - } + handler_->signal_write(); return 0; } void Http2Upstream::set_deferred(bool f) { deferred_ = f; } +MemchunkPool4K *Http2Upstream::get_mcpool() { return &mcpool_; } + } // namespace shrpx 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..1daf02a6 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -36,25 +36,99 @@ #include "shrpx_worker.h" #include "http2.h" #include "util.h" -#include "libevent_util.h" using namespace nghttp2; namespace shrpx { namespace { -const size_t OUTBUF_MAX_THRES = 64 * 1024; +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto dconn = static_cast(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); + auto downstream = dconn->get_downstream(); + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + if (dconn->on_connect() != 0) { + delete handler; + return; + } + writecb(loop, w, revents); +} } // namespace HttpDownstreamConnection::HttpDownstreamConnection( - DownstreamConnectionPool *dconn_pool) - : DownstreamConnection(dconn_pool), bev_(nullptr), ioctrl_(nullptr), - response_htp_{0} {} + DownstreamConnectionPool *dconn_pool, struct ev_loop *loop) + : DownstreamConnection(dconn_pool), rlimit_(loop, &rev_, 0, 0), + ioctrl_(&rlimit_), response_htp_{0}, loop_(loop), fd_(-1) { + // We do not know fd yet, so just set dummy fd 0 + ev_io_init(&wev_, connectcb, 0, EV_WRITE); + ev_io_init(&rev_, readcb, 0, EV_READ); + + wev_.data = this; + rev_.data = this; + + ev_timer_init(&wt_, timeoutcb, 0., get_config()->downstream_write_timeout); + ev_timer_init(&rt_, timeoutcb, 0., get_config()->downstream_read_timeout); + + wt_.data = this; + rt_.data = this; +} HttpDownstreamConnection::~HttpDownstreamConnection() { - if (bev_) { - util::bev_disable_unless(bev_, EV_READ | EV_WRITE); - bufferevent_free(bev_); + ev_timer_stop(loop_, &rt_); + ev_timer_stop(loop_, &wt_); + ev_io_stop(loop_, &rev_); + ev_io_stop(loop_, &wev_); + + if (fd_ != -1) { + shutdown(fd_, SHUT_WR); + close(fd_); } // Downstream and DownstreamConnection may be deleted // asynchronously. @@ -67,25 +141,25 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { if (LOG_ENABLED(INFO)) { DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; } - auto upstream = downstream->get_upstream(); - if (!bev_) { + + if (fd_ == -1) { auto connect_blocker = client_handler_->get_http1_connect_blocker(); if (connect_blocker->blocked()) { return -1; } - auto evbase = client_handler_->get_evbase(); auto worker_stat = client_handler_->get_worker_stat(); + auto end = worker_stat->next_downstream; for (;;) { auto i = worker_stat->next_downstream; ++worker_stat->next_downstream; worker_stat->next_downstream %= get_config()->downstream_addrs.size(); - auto fd = socket(get_config()->downstream_addrs[i].addr.storage.ss_family, - SOCK_STREAM | SOCK_CLOEXEC, 0); + fd_ = util::create_nonblock_socket( + get_config()->downstream_addrs[i].addr.storage.ss_family); - if (fd == -1) { + if (fd_ == -1) { auto error = errno; DCLOG(WARN, this) << "socket() failed; errno=" << error; @@ -94,32 +168,19 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { return SHRPX_ERR_NETWORK; } - bev_ = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE | - BEV_OPT_DEFER_CALLBACKS); - if (!bev_) { + int rv; + rv = connect(fd_, const_cast( + &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,34 +194,33 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) { DCLOG(INFO, this) << "Connecting to downstream server"; } + ev_io_set(&wev_, fd_, EV_WRITE); + ev_io_set(&rev_, fd_, EV_READ); + + ev_io_start(loop_, &wev_); + break; } } downstream_ = downstream; - ioctrl_.set_bev(bev_); - http_parser_init(&response_htp_, HTTP_RESPONSE); response_htp_.data = downstream_; - bufferevent_setwatermark(bev_, EV_READ, 0, SHRPX_READ_WATERMARK); - util::bev_enable_unless(bev_, EV_READ); - bufferevent_setcb(bev_, upstream->get_downstream_readcb(), - upstream->get_downstream_writecb(), - upstream->get_downstream_eventcb(), this); + ev_set_cb(&rev_, readcb); - reset_timeouts(); + ev_timer_set(&rt_, 0., get_config()->downstream_read_timeout); + + // TODO we should have timeout for connection establishment + ev_timer_again(loop_, &wt_); return 0; } int HttpDownstreamConnection::push_request_headers() { - assert(downstream_->get_request_headers_normalized()); - downstream_->assemble_request_cookie(); - auto end_headers = std::end(downstream_->get_request_headers()); // Assume that method and request path do not contain \r\n. std::string hdrs = downstream_->get_request_method(); hdrs += " "; @@ -196,14 +256,14 @@ int HttpDownstreamConnection::push_request_headers() { hdrs += downstream_->get_request_path(); } hdrs += " HTTP/1.1\r\n"; - if (downstream_->get_norm_request_header("host") == end_headers && + if (!downstream_->get_request_header(http2::HD_HOST) && !downstream_->get_request_http2_authority().empty()) { hdrs += "Host: "; hdrs += downstream_->get_request_http2_authority(); hdrs += "\r\n"; } - http2::build_http1_headers_from_norm_headers( - hdrs, downstream_->get_request_headers()); + http2::build_http1_headers_from_headers(hdrs, + downstream_->get_request_headers()); if (!downstream_->get_assembled_request_cookie().empty()) { hdrs += "Cookie: "; @@ -213,7 +273,7 @@ int HttpDownstreamConnection::push_request_headers() { if (downstream_->get_request_method() != "CONNECT" && downstream_->get_request_http2_expect_body() && - downstream_->get_norm_request_header("content-length") == end_headers) { + !downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) { downstream_->set_chunked_request(true); hdrs += "Transfer-Encoding: chunked\r\n"; @@ -222,18 +282,17 @@ int HttpDownstreamConnection::push_request_headers() { if (downstream_->get_request_connection_close()) { hdrs += "Connection: close\r\n"; } - auto xff = downstream_->get_norm_request_header("x-forwarded-for"); + auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR); if (get_config()->add_x_forwarded_for) { hdrs += "X-Forwarded-For: "; - if (xff != end_headers && !get_config()->strip_incoming_x_forwarded_for) { + if (xff && !get_config()->strip_incoming_x_forwarded_for) { hdrs += (*xff).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); hdrs += ", "; } hdrs += client_handler_->get_ipaddr(); hdrs += "\r\n"; - } else if (xff != end_headers && - !get_config()->strip_incoming_x_forwarded_for) { + } else if (xff && !get_config()->strip_incoming_x_forwarded_for) { hdrs += "X-Forwarded-For: "; hdrs += (*xff).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*xff).value.size()); @@ -251,17 +310,16 @@ int HttpDownstreamConnection::push_request_headers() { hdrs += "http\r\n"; } } - auto expect = downstream_->get_norm_request_header("expect"); - if (expect != end_headers && - !util::strifind((*expect).value.c_str(), "100-continue")) { + auto expect = downstream_->get_request_header(http2::HD_EXPECT); + if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) { hdrs += "Expect: "; hdrs += (*expect).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*expect).value.size()); hdrs += "\r\n"; } - auto via = downstream_->get_norm_request_header("via"); + auto via = downstream_->get_request_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { hdrs += "Via: "; hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); @@ -269,7 +327,7 @@ int HttpDownstreamConnection::push_request_headers() { } } else { hdrs += "Via: "; - if (via != end_headers) { + if (via) { hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); hdrs += ", "; @@ -292,65 +350,55 @@ int HttpDownstreamConnection::push_request_headers() { DCLOG(INFO, this) << "HTTP request headers. stream_id=" << downstream_->get_stream_id() << "\n" << hdrp; } - auto output = bufferevent_get_output(bev_); - int rv; - rv = evbuffer_add(output, hdrs.c_str(), hdrs.size()); - if (rv != 0) { - return -1; - } + auto output = downstream_->get_request_buf(); + output->append(hdrs.c_str(), hdrs.size()); + + signal_write(); return 0; } int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data, size_t datalen) { - int rv; - int chunked = downstream_->get_chunked_request(); - auto output = bufferevent_get_output(bev_); + auto chunked = downstream_->get_chunked_request(); + auto output = downstream_->get_request_buf(); if (chunked) { auto chunk_size_hex = util::utox(datalen); - chunk_size_hex += "\r\n"; - - rv = evbuffer_add(output, chunk_size_hex.c_str(), chunk_size_hex.size()); - if (rv == -1) { - DCLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append(chunk_size_hex.c_str(), chunk_size_hex.size()); + output->append_cstr("\r\n"); } - rv = evbuffer_add(output, data, datalen); - - if (rv == -1) { - DCLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append(data, datalen); if (chunked) { - rv = evbuffer_add(output, "\r\n", 2); - if (rv == -1) { - DCLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append_cstr("\r\n"); } + signal_write(); + return 0; } int HttpDownstreamConnection::end_upload_data() { - if (downstream_->get_chunked_request()) { - auto output = bufferevent_get_output(bev_); - if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) { - DCLOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + if (!downstream_->get_chunked_request()) { + return 0; } + + auto output = downstream_->get_request_buf(); + output->append_cstr("0\r\n\r\n"); + + signal_write(); + return 0; } namespace { -void idle_readcb(bufferevent *bev, void *arg) { - auto dconn = static_cast(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 +406,10 @@ void idle_readcb(bufferevent *bev, void *arg) { } // namespace namespace { -// Gets called when DownstreamConnection is pooled in ClientHandler. -void idle_eventcb(bufferevent *bev, short events, void *arg) { - auto dconn = static_cast(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 +423,17 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) { } downstream_ = nullptr; ioctrl_.force_resume_read(); - util::bev_enable_unless(bev_, EV_READ); - bufferevent_setcb(bev_, idle_readcb, nullptr, idle_eventcb, this); - // On idle state, just enable read timeout. Normally idle downstream - // connection will get EOF from the downstream server and closed. - bufferevent_set_timeouts(bev_, &get_config()->downstream_idle_read_timeout, - &get_config()->downstream_write_timeout); -} -bufferevent *HttpDownstreamConnection::get_bev() { return bev_; } + ev_io_start(loop_, &rev_); + ev_io_stop(loop_, &wev_); + + ev_timer_stop(loop_, &wt_); + + ev_set_cb(&rev_, idle_readcb); + ev_timer_set(&rt_, 0., get_config()->downstream_idle_read_timeout); + ev_set_cb(&rt_, idle_timeoutcb); + ev_timer_again(loop_, &rt_); +} void HttpDownstreamConnection::pause_read(IOCtrlReason reason) { ioctrl_.pause_read(reason); @@ -407,7 +441,10 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) { int HttpDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) { - ioctrl_.resume_read(reason); + if (!downstream_->response_buf_full()) { + ioctrl_.resume_read(reason); + } + return 0; } @@ -415,11 +452,6 @@ void HttpDownstreamConnection::force_resume_read() { ioctrl_.force_resume_read(); } -bool HttpDownstreamConnection::get_output_buffer_full() { - auto output = bufferevent_get_output(bev_); - return evbuffer_get_length(output) >= OUTBUF_MAX_THRES; -} - namespace { int htp_msg_begincb(http_parser *htp) { auto downstream = static_cast(htp->data); @@ -442,6 +474,8 @@ int htp_hdrs_completecb(http_parser *htp) { downstream->set_response_major(htp->http_major); downstream->set_response_minor(htp->http_minor); + downstream->index_response_headers(); + if (downstream->get_non_final_response()) { // For non-final response code, we just call // on_downstream_header_complete() without changing response @@ -577,52 +611,62 @@ http_parser_settings htp_hooks = { } // namespace int HttpDownstreamConnection::on_read() { - reset_timeouts(); - - auto input = bufferevent_get_input(bev_); + ev_timer_again(loop_, &rt_); + uint8_t buf[8192]; + int rv; if (downstream_->get_upgraded()) { // For upgraded connection, just pass data to the upstream. for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); - - if (inputlen == 0) { - assert(evbuffer_get_length(input) == 0); - - return 0; + ssize_t nread; + while ((nread = read(fd_, buf, sizeof(buf))) == -1 && errno == EINTR) + ; + if (nread == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + return DownstreamConnection::ERR_NET; } - auto mem = evbuffer_pullup(input, inputlen); + if (nread == 0) { + return DownstreamConnection::ERR_EOF; + } - int rv; - rv = downstream_->get_upstream()->on_downstream_body( - downstream_, reinterpret_cast(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 != static_cast(nread)) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "nproc != nread"; + } return -1; } @@ -635,29 +679,70 @@ int HttpDownstreamConnection::on_read() { << http_errno_description(htperr); } - return SHRPX_ERR_HTTP_PARSE; + return -1; + } + + if (downstream_->response_buf_full()) { + downstream_->pause_read(SHRPX_NO_BUFFER); + return 0; } } } int HttpDownstreamConnection::on_write() { - reset_timeouts(); + ev_timer_again(loop_, &rt_); auto upstream = downstream_->get_upstream(); - upstream->resume_read(SHRPX_NO_BUFFER, downstream_, - downstream_->get_request_datalen()); + auto input = downstream_->get_request_buf(); + + while (input->rleft() > 0) { + struct iovec iov[2]; + auto iovcnt = input->riovec(iov, util::array_size(iov)); + + ssize_t nwrite; + while ((nwrite = writev(fd_, iov, iovcnt)) == -1 && errno == EINTR) + ; + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + ev_io_start(loop_, &wev_); + ev_timer_again(loop_, &wt_); + goto end; + } + return DownstreamConnection::ERR_NET; + } + input->drain(nwrite); + } + + if (input->rleft() == 0) { + ev_io_stop(loop_, &wev_); + ev_timer_stop(loop_, &wt_); + } else { + ev_io_start(loop_, &wev_); + ev_timer_again(loop_, &wt_); + } + +end: + if (input->rleft() == 0) { + upstream->resume_read(SHRPX_NO_BUFFER, downstream_, + downstream_->get_request_datalen()); + } + return 0; } -void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) { - bufferevent_setcb(bev_, upstream->get_downstream_readcb(), - upstream->get_downstream_writecb(), - upstream->get_downstream_eventcb(), this); +int HttpDownstreamConnection::on_connect() { + if (!util::check_socket_connected(fd_)) { + return -1; + } + + ev_io_start(loop_, &rev_); + ev_set_cb(&wev_, writecb); + + return 0; } -void HttpDownstreamConnection::reset_timeouts() { - bufferevent_set_timeouts(bev_, &get_config()->downstream_read_timeout, - &get_config()->downstream_write_timeout); -} +void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {} + +void HttpDownstreamConnection::signal_write() { ev_io_start(loop_, &wev_); } } // namespace shrpx diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h index c0b40633..d61de22b 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(); + int on_connect(); + void signal_write(); private: - bufferevent *bev_; + ev_io wev_; + ev_io rev_; + ev_timer wt_; + ev_timer rt_; + RateLimit rlimit_; IOControl ioctrl_; http_parser response_htp_; + struct ev_loop *loop_; + int fd_; }; } // namespace shrpx diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 93fd8b57..0cd29b36 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; } @@ -153,7 +149,7 @@ int htp_hdrs_completecb(http_parser *htp) { ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str(); } - downstream->normalize_request_headers(); + downstream->index_request_headers(); downstream->inspect_http1_request(); @@ -241,37 +237,32 @@ http_parser_settings htp_hooks = { // on_read() does not consume all available data in input buffer if // one http request is fully received. int HttpsUpstream::on_read() { - auto bev = handler_->get_bev(); - auto input = bufferevent_get_input(bev); + auto rb = handler_->get_rb(); auto downstream = get_downstream(); + const void *data; + size_t datalen; // downstream can be nullptr here, because it is initialized in the // callback chain called by http_parser_execute() if (downstream && downstream->get_upgraded()) { for (;;) { - auto inputlen = evbuffer_get_contiguous_space(input); - - if (inputlen == 0) { + std::tie(data, datalen) = rb->get(); + if (datalen == 0) { return 0; } - auto mem = evbuffer_pullup(input, inputlen); - auto rv = downstream->push_upload_data_chunk( - reinterpret_cast(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) { @@ -737,15 +620,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += " "; hdrs += http2::get_status_string(downstream->get_response_http_status()); hdrs += "\r\n"; - downstream->normalize_response_headers(); + if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } - auto end_headers = std::end(downstream->get_response_headers()); - http2::build_http1_headers_from_norm_headers( - hdrs, downstream->get_response_headers()); + + http2::build_http1_headers_from_headers(hdrs, + downstream->get_response_headers()); + + auto output = downstream->get_response_buf(); if (downstream->get_non_final_response()) { hdrs += "\r\n"; @@ -754,11 +639,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { log_response_headers(hdrs); } - auto output = bufferevent_get_output(handler_->get_bev()); - if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append(hdrs.c_str(), hdrs.size()); downstream->clear_response_headers(); @@ -779,8 +660,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += "Connection: close\r\n"; } - if (downstream->get_norm_response_header("alt-svc") == end_headers) { - // We won't change or alter alt-svc from backend at the moment. + if (!downstream->get_response_header(http2::HD_ALT_SVC)) { + // We won't change or alter alt-svc from backend for now if (!get_config()->altsvcs.empty()) { hdrs += "Alt-Svc: "; @@ -803,17 +684,17 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { hdrs += get_config()->server_name; hdrs += "\r\n"; } else { - auto server = downstream->get_norm_response_header("server"); - if (server != end_headers) { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { hdrs += "Server: "; hdrs += (*server).value; hdrs += "\r\n"; } } - auto via = downstream->get_norm_response_header("via"); + auto via = downstream->get_response_header(http2::HD_VIA); if (get_config()->no_via) { - if (via != end_headers) { + if (via) { hdrs += "Via: "; hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); @@ -821,7 +702,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { } } else { hdrs += "Via: "; - if (via != end_headers) { + if (via) { hdrs += (*via).value; http2::sanitize_header_value(hdrs, hdrs.size() - (*via).value.size()); hdrs += ", "; @@ -844,11 +725,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { log_response_headers(hdrs); } - auto output = bufferevent_get_output(handler_->get_bev()); - if (evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append(hdrs.c_str(), hdrs.size()); return 0; } @@ -856,45 +733,30 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { int HttpsUpstream::on_downstream_body(Downstream *downstream, const uint8_t *data, size_t len, bool flush) { - int rv; if (len == 0) { return 0; } - auto output = bufferevent_get_output(handler_->get_bev()); + auto output = downstream->get_response_buf(); if (downstream->get_chunked_response()) { auto chunk_size_hex = util::utox(len); chunk_size_hex += "\r\n"; - rv = evbuffer_add(output, chunk_size_hex.c_str(), chunk_size_hex.size()); - - if (rv != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } - } - if (evbuffer_add(output, data, len) != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; + output->append(chunk_size_hex.c_str(), chunk_size_hex.size()); } + output->append(data, len); downstream->add_response_sent_bodylen(len); if (downstream->get_chunked_response()) { - if (evbuffer_add(output, "\r\n", 2) != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + output->append_cstr("\r\n"); } return 0; } int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) { if (downstream->get_chunked_response()) { - auto output = bufferevent_get_output(handler_->get_bev()); - if (evbuffer_add(output, "0\r\n\r\n", 5) != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + auto output = downstream->get_response_buf(); + output->append_cstr("0\r\n\r\n"); } if (LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response completed"; @@ -925,11 +787,6 @@ void HttpsUpstream::log_response_headers(const std::string &hdrs) const { ULOG(INFO, this) << "HTTP response headers\n" << hdrp; } -void HttpsUpstream::reset_timeouts() { - handler_->set_upstream_timeouts(&get_config()->upstream_read_timeout, - &get_config()->upstream_write_timeout); -} - void HttpsUpstream::on_handler_delete() { if (downstream_ && downstream_->accesslog_ready()) { handler_->write_accesslog(downstream_.get()); @@ -956,4 +813,6 @@ int HttpsUpstream::on_downstream_reset() { return 0; } +MemchunkPool4K *HttpsUpstream::get_mcpool() { return &mcpool_; } + } // namespace shrpx 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..f43a2576 100644 --- a/src/shrpx_io_control.cc +++ b/src/shrpx_io_control.cc @@ -26,42 +26,40 @@ #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::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..9baf95aa 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,8 @@ enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 }; class IOControl { public: - IOControl(bufferevent *bev); + IOControl(RateLimit *lim); ~IOControl(); - void set_bev(bufferevent *bev); void pause_read(IOCtrlReason reason); // Returns true if read operation is enabled after this call bool resume_read(IOCtrlReason reason); @@ -48,7 +48,7 @@ public: void force_resume_read(); private: - bufferevent *bev_; + RateLimit *lim_; uint32_t rdbits_; }; 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.cc b/src/shrpx_log.cc index 9a74609b..8e5a0c26 100644 --- a/src/shrpx_log.cc +++ b/src/shrpx_log.cc @@ -206,7 +206,7 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { case SHRPX_LOGF_HTTP: if (downstream) { auto hd = downstream->get_request_header(lf.value.get()); - if (hd != std::end(downstream->get_request_headers())) { + if (hd) { std::tie(p, avail) = copy((*hd).value.c_str(), avail, p); break; } 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..44867018 --- /dev/null +++ b/src/shrpx_rate_limit.cc @@ -0,0 +1,98 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shrpx_rate_limit.h" + +#include + +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 std::numeric_limits::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..51a6532f 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 @@ -155,47 +152,36 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, auto downstream = upstream->add_pending_downstream( frame->syn_stream.stream_id, frame->syn_stream.pri); - downstream->init_upstream_timer(); downstream->reset_upstream_rtimer(); - downstream->init_response_body_buf(); auto nv = frame->syn_stream.nv; - const char *path = nullptr; - const char *scheme = nullptr; - const char *host = nullptr; - const char *method = nullptr; for (size_t i = 0; nv[i]; i += 2) { - if (strcmp(nv[i], ":path") == 0) { - path = nv[i + 1]; - } else if (strcmp(nv[i], ":scheme") == 0) { - scheme = nv[i + 1]; - } else if (strcmp(nv[i], ":method") == 0) { - method = nv[i + 1]; - } else if (strcmp(nv[i], ":host") == 0) { - host = nv[i + 1]; - } else if (nv[i][0] != ':') { - downstream->add_request_header(nv[i], nv[i + 1]); - } + downstream->add_request_header(nv[i], nv[i + 1]); } - downstream->normalize_request_headers(); + downstream->index_request_headers(); - bool is_connect = method && strcmp("CONNECT", method) == 0; - if (!path || !host || !method || http2::lws(host) || http2::lws(path) || - http2::lws(method) || - (!is_connect && (!scheme || http2::lws(scheme)))) { + auto path = downstream->get_request_header(http2::HD__PATH); + auto scheme = downstream->get_request_header(http2::HD__SCHEME); + auto host = downstream->get_request_header(http2::HD__HOST); + auto method = downstream->get_request_header(http2::HD__METHOD); + + bool is_connect = method && "CONNECT" == method->value; + if (!path || !host || !method || !http2::non_empty_value(host) || + !http2::non_empty_value(path) || !http2::non_empty_value(method) || + (!is_connect && (!scheme || !http2::non_empty_value(scheme)))) { upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); return; } - downstream->set_request_method(method); + downstream->set_request_method(method->value); if (is_connect) { - downstream->set_request_http2_authority(path); + downstream->set_request_http2_authority(path->value); } else { - downstream->set_request_http2_scheme(scheme); - downstream->set_request_http2_authority(host); - downstream->set_request_path(path); + downstream->set_request_http2_scheme(scheme->value); + downstream->set_request_http2_authority(host->value); + downstream->set_request_path(path->value); } downstream->set_request_start_time( @@ -404,13 +390,14 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) { } // namespace SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler) - : downstream_queue_(get_config()->http2_proxy - ? get_config()->downstream_connections_per_host - : 0), + : downstream_queue_( + get_config()->http2_proxy + ? get_config()->downstream_connections_per_host + : get_config()->downstream_proto == PROTO_HTTP + ? get_config()->downstream_connections_per_frontend + : 0, + !get_config()->http2_proxy), handler_(handler), session_(nullptr) { - // handler->set_bev_cb(spdy_readcb, 0, spdy_eventcb); - reset_timeouts(); - spdylay_session_callbacks callbacks; memset(&callbacks, 0, sizeof(callbacks)); callbacks.send_callback = send_callback; @@ -461,8 +448,10 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler) assert(rv == 0); } - // TODO Maybe call from outside? - send(); + handler_->reset_upstream_read_timeout( + get_config()->http2_upstream_read_timeout); + + handler_->signal_write(); } SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); } @@ -478,18 +467,15 @@ int SpdyUpstream::on_read() { } return rv; } - return send(); + + handler_->signal_write(); + + return 0; } -int SpdyUpstream::on_write() { return send(); } - // After this function call, downstream may be deleted. -int SpdyUpstream::send() { +int SpdyUpstream::on_write() { int rv = 0; - uint8_t buf[16384]; - - sendbuf.reset(bufferevent_get_output(handler_->get_bev()), buf, sizeof(buf), - handler_->get_write_limit()); rv = spdylay_session_send(session_); if (rv != 0) { @@ -498,17 +484,9 @@ int SpdyUpstream::send() { return rv; } - rv = sendbuf.flush(); - if (rv != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } - - handler_->update_warmup_writelen(sendbuf.get_writelen()); - if (spdylay_session_want_read(session_) == 0 && spdylay_session_want_write(session_) == 0 && - handler_->get_outbuf_length() == 0) { + handler_->get_wb()->rleft() == 0) { if (LOG_ENABLED(INFO)) { ULOG(INFO, this) << "No more read/write for this SPDY session"; } @@ -517,23 +495,19 @@ int SpdyUpstream::send() { return 0; } -int SpdyUpstream::on_event() { return 0; } - ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; } -namespace { -void spdy_downstream_readcb(bufferevent *bev, void *ptr) { - auto dconn = static_cast(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 +515,148 @@ void spdy_downstream_readcb(bufferevent *bev, void *ptr) { // RST_STREAM to the upstream and delete downstream connection // here. Deleting downstream will be taken place at // on_stream_close_callback. - upstream->rst_stream(downstream, - infer_upstream_rst_stream_status_code( - downstream->get_response_rst_stream_error_code())); + rst_stream(downstream, + infer_upstream_rst_stream_status_code( + downstream->get_response_rst_stream_error_code())); downstream->pop_downstream_connection(); dconn = nullptr; } else { auto rv = downstream->on_read(); + if (rv == DownstreamConnection::ERR_EOF) { + return downstream_eof(dconn); + } if (rv != 0) { - if (LOG_ENABLED(INFO)) { - DCLOG(INFO, dconn) << "HTTP parser failure"; - } - if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { - // If response was completed, then don't issue RST_STREAM - if (upstream->error_reply(downstream, 502) != 0) { - delete upstream->get_client_handler(); - return; + if (rv != DownstreamConnection::ERR_NET) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, dconn) << "HTTP parser failure"; } } - downstream->set_response_state(Downstream::MSG_COMPLETE); - // Clearly, we have to close downstream connection on http parser - // failure. - downstream->pop_downstream_connection(); - dconn = nullptr; + return downstream_error(dconn, Downstream::EVENT_ERROR); } } - if (upstream->send() != 0) { - delete upstream->get_client_handler(); - return; - } + + handler_->signal_write(); // At this point, downstream may be deleted. -} -} // namespace -namespace { -void spdy_downstream_writecb(bufferevent *bev, void *ptr) { - if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { - return; + return 0; +} + +int SpdyUpstream::downstream_write(DownstreamConnection *dconn) { + int rv; + rv = dconn->on_write(); + if (rv == DownstreamConnection::ERR_NET) { + return downstream_error(dconn, Downstream::EVENT_ERROR); } - auto dconn = static_cast(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 +679,7 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id, spdylay_data_source *source, void *user_data) { auto downstream = static_cast(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,11 +693,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); - if (nread == -1) { - ULOG(FATAL, upstream) << "evbuffer_remove() failed"; - return SPDYLAY_ERR_CALLBACK_FAILURE; - } + auto nread = body->remove(buf, length); + auto body_empty = body->rleft() == 0; + if (nread == 0 && downstream->get_response_state() == Downstream::MSG_COMPLETE) { if (!downstream->get_upgraded()) { @@ -770,10 +712,10 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id, } } - if (evbuffer_get_length(body) > 0) { - downstream->reset_upstream_wtimer(); - } else { + if (body_empty) { downstream->disable_upstream_wtimer(); + } else { + downstream->reset_upstream_wtimer(); } if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) { @@ -797,13 +739,8 @@ int SpdyUpstream::error_reply(Downstream *downstream, int rv; auto html = http::create_error_html(status_code); downstream->set_response_http_status(status_code); - downstream->init_response_body_buf(); - auto body = downstream->get_response_body_buf(); - rv = evbuffer_add(body, html.c_str(), html.size()); - if (rv == -1) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + auto body = downstream->get_response_buf(); + body->append(html.c_str(), html.size()); downstream->set_response_state(Downstream::MSG_COMPLETE); spdylay_data_provider data_prd; @@ -830,18 +767,6 @@ int SpdyUpstream::error_reply(Downstream *downstream, return 0; } -bufferevent_data_cb SpdyUpstream::get_downstream_readcb() { - return spdy_downstream_readcb; -} - -bufferevent_data_cb SpdyUpstream::get_downstream_writecb() { - return spdy_downstream_writecb; -} - -bufferevent_event_cb SpdyUpstream::get_downstream_eventcb() { - return spdy_downstream_eventcb; -} - Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id, int32_t priority) { auto downstream = util::make_unique(this, stream_id, priority); @@ -863,6 +788,9 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) { if (next_downstream) { initiate_downstream(std::move(next_downstream)); } + + mcpool_.shrink((downstream_queue_.get_active_downstreams().size() + 1) * + 65536); } Downstream *SpdyUpstream::find_downstream(int32_t stream_id) { @@ -886,10 +814,10 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { if (LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; } - downstream->normalize_response_headers(); + if (!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->no_location_rewrite) { - downstream->rewrite_norm_location_response_header( + downstream->rewrite_location_response_header( get_client_handler()->get_upstream_scheme(), get_config()->port); } size_t nheader = downstream->get_response_headers().size(); @@ -906,30 +834,39 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { nv[hdidx++] = ":version"; nv[hdidx++] = "HTTP/1.1"; for (auto &hd : downstream->get_response_headers()) { - if (hd.name.empty() || hd.name.c_str()[0] == ':' || - util::strieq(hd.name.c_str(), "transfer-encoding") || - util::strieq(hd.name.c_str(), "keep-alive") || // HTTP/1.0? - util::strieq(hd.name.c_str(), "connection") || - util::strieq(hd.name.c_str(), "proxy-connection")) { - // These are ignored - } else if (!get_config()->no_via && util::strieq(hd.name.c_str(), "via")) { - via_value = hd.value; - } else if (!get_config()->http2_proxy && !get_config()->client_proxy && - util::strieq(hd.name.c_str(), "server")) { - // Rewrite server header field later - } else { - nv[hdidx++] = hd.name.c_str(); - nv[hdidx++] = hd.value.c_str(); + if (hd.name.empty() || hd.name.c_str()[0] == ':') { + continue; } + auto token = http2::lookup_token(hd.name); + switch (token) { + case http2::HD_CONNECTION: + case http2::HD_KEEP_ALIVE: + case http2::HD_PROXY_CONNECTION: + case http2::HD_TRANSFER_ENCODING: + case http2::HD_VIA: + case http2::HD_SERVER: + continue; + } + + nv[hdidx++] = hd.name.c_str(); + nv[hdidx++] = hd.value.c_str(); } if (!get_config()->http2_proxy && !get_config()->client_proxy) { nv[hdidx++] = "server"; nv[hdidx++] = get_config()->server_name; + } else { + auto server = downstream->get_response_header(http2::HD_SERVER); + if (server) { + nv[hdidx++] = "server"; + nv[hdidx++] = server->value.c_str(); + } } if (!get_config()->no_via) { - if (!via_value.empty()) { + auto via = downstream->get_response_header(http2::HD_VIA); + if (via) { + via_value = via->value; via_value += ", "; } via_value += http::create_via_header_value( @@ -972,12 +909,8 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { int SpdyUpstream::on_downstream_body(Downstream *downstream, const uint8_t *data, size_t len, bool flush) { - auto body = downstream->get_response_body_buf(); - int rv = evbuffer_add(body, data, len); - if (rv != 0) { - ULOG(FATAL, this) << "evbuffer_add() failed"; - return -1; - } + auto body = downstream->get_response_buf(); + body->append(data, len); if (flush) { spdylay_session_resume_data(session_, downstream->get_stream_id()); @@ -985,16 +918,6 @@ int SpdyUpstream::on_downstream_body(Downstream *downstream, downstream->ensure_upstream_wtimer(); } - if (evbuffer_get_length(body) >= INBUF_MAX_THRES) { - if (!flush) { - spdylay_session_resume_data(session_, downstream->get_stream_id()); - - downstream->ensure_upstream_wtimer(); - } - - downstream->pause_read(SHRPX_NO_BUFFER); - } - return 0; } @@ -1027,7 +950,8 @@ int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream, downstream->dec_request_datalen(consumed); } - return send(); + handler_->signal_write(); + return 0; } int SpdyUpstream::on_downstream_abort_request(Downstream *downstream, @@ -1040,7 +964,8 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream, return -1; } - return send(); + handler_->signal_write(); + return 0; } int SpdyUpstream::consume(int32_t stream_id, size_t len) { @@ -1068,11 +993,6 @@ int SpdyUpstream::on_timeout(Downstream *downstream) { return 0; } -void SpdyUpstream::reset_timeouts() { - handler_->set_upstream_timeouts(&get_config()->http2_upstream_read_timeout, - &get_config()->upstream_write_timeout); -} - void SpdyUpstream::on_handler_delete() { for (auto &ent : downstream_queue_.get_active_downstreams()) { if (ent.second->accesslog_ready()) { @@ -1107,12 +1027,11 @@ int SpdyUpstream::on_downstream_reset() { } } - rv = send(); - if (rv != 0) { - return -1; - } + handler_->signal_write(); return 0; } +MemchunkPool4K *SpdyUpstream::get_mcpool() { return &mcpool_; } + } // namespace shrpx 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..1c8036d5 100644 --- a/src/util.cc +++ b/src/util.cc @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -827,6 +829,58 @@ 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_nonblocking(int fd) { + int flags; + int rv; + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; + return rv; +} + +int make_socket_nodelay(int fd) { + int val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&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 diff --git a/third-party/http-parser/README.md b/third-party/http-parser/README.md index f9972ae5..7c54dd42 100644 --- a/third-party/http-parser/README.md +++ b/third-party/http-parser/README.md @@ -131,7 +131,7 @@ There are two types of callbacks: * notification `typedef int (*http_cb) (http_parser*);` Callbacks: on_message_begin, on_headers_complete, on_message_complete. * data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` - Callbacks: (requests only) on_uri, + Callbacks: (requests only) on_url, (common) on_header_field, on_header_value, on_body; Callbacks must return 0 on success. Returning a non-zero value indicates diff --git a/third-party/http-parser/bench.c b/third-party/http-parser/bench.c new file mode 100644 index 00000000..5b452fa1 --- /dev/null +++ b/third-party/http-parser/bench.c @@ -0,0 +1,111 @@ +/* Copyright Fedor Indutny. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include + +static const char data[] = + "POST /joyent/http-parser HTTP/1.1\r\n" + "Host: github.com\r\n" + "DNT: 1\r\n" + "Accept-Encoding: gzip, deflate, sdch\r\n" + "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/39.0.2171.65 Safari/537.36\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," + "image/webp,*/*;q=0.8\r\n" + "Referer: https://github.com/joyent/http-parser\r\n" + "Connection: keep-alive\r\n" + "Transfer-Encoding: chunked\r\n" + "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n"; +static const size_t data_len = sizeof(data) - 1; + +static int on_info(http_parser* p) { + return 0; +} + + +static int on_data(http_parser* p, const char *at, size_t length) { + return 0; +} + +static http_parser_settings settings = { + .on_message_begin = on_info, + .on_headers_complete = on_info, + .on_message_complete = on_info, + .on_header_field = on_data, + .on_header_value = on_data, + .on_url = on_data, + .on_status = on_data, + .on_body = on_data +}; + +int bench(int iter_count, int silent) { + struct http_parser parser; + int i; + int err; + struct timeval start; + struct timeval end; + float rps; + + if (!silent) { + err = gettimeofday(&start, NULL); + assert(err == 0); + } + + for (i = 0; i < iter_count; i++) { + size_t parsed; + http_parser_init(&parser, HTTP_REQUEST); + + parsed = http_parser_execute(&parser, &settings, data, data_len); + assert(parsed == data_len); + } + + if (!silent) { + err = gettimeofday(&end, NULL); + assert(err == 0); + + fprintf(stdout, "Benchmark result:\n"); + + rps = (float) (end.tv_sec - start.tv_sec) + + (end.tv_usec - start.tv_usec) * 1e-6f; + fprintf(stdout, "Took %f seconds to run\n", rps); + + rps = (float) iter_count / rps; + fprintf(stdout, "%f req/sec\n", rps); + fflush(stdout); + } + + return 0; +} + +int main(int argc, char** argv) { + if (argc == 2 && strcmp(argv[1], "infinite") == 0) { + for (;;) + bench(5000000, 1); + return 0; + } else { + return bench(5000000, 0); + } +} diff --git a/third-party/http-parser/http_parser.c b/third-party/http-parser/http_parser.c index 749d1bb6..23077d1d 100644 --- a/third-party/http-parser/http_parser.c +++ b/third-party/http-parser/http_parser.c @@ -56,19 +56,41 @@ do { \ parser->http_errno = (e); \ } while(0) +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + --p; \ + break; + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + /* Run the notify callback FOR, returning ER if it fails */ #define CALLBACK_NOTIFY_(FOR, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser)) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ + UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ @@ -86,13 +108,16 @@ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (FOR##_mark) { \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ + UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ @@ -116,6 +141,26 @@ do { \ } \ } while (0) +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" @@ -334,12 +379,16 @@ enum header_states , h_upgrade , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token , h_transfer_encoding_chunked , h_connection_keep_alive , h_connection_close + , h_connection_upgrade }; enum http_host_state @@ -371,6 +420,8 @@ enum http_host_state (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + #if HTTP_PARSER_STRICT #define TOKEN(c) (tokens[(unsigned char)c]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) @@ -586,6 +637,7 @@ size_t http_parser_execute (http_parser *parser, const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; + enum state p_state = parser->state; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { @@ -593,7 +645,7 @@ size_t http_parser_execute (http_parser *parser, } if (len == 0) { - switch (parser->state) { + switch (CURRENT_STATE()) { case s_body_identity_eof: /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if * we got paused. @@ -614,11 +666,11 @@ size_t http_parser_execute (http_parser *parser, } - if (parser->state == s_header_field) + if (CURRENT_STATE() == s_header_field) header_field_mark = data; - if (parser->state == s_header_value) + if (CURRENT_STATE() == s_header_value) header_value_mark = data; - switch (parser->state) { + switch (CURRENT_STATE()) { case s_req_path: case s_req_schema: case s_req_schema_slash: @@ -635,38 +687,23 @@ size_t http_parser_execute (http_parser *parser, case s_res_status: status_mark = data; break; + default: + break; } for (p=data; p != data + len; p++) { ch = *p; - if (PARSING_HEADER(parser->state)) { - ++parser->nread; - /* Don't allow the total size of the HTTP headers (including the status - * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect - * embedders against denial-of-service attacks where the attacker feeds - * us a never-ending header that the embedder keeps buffering. - * - * This check is arguably the responsibility of embedders but we're doing - * it on the embedder's behalf because most won't bother and this way we - * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger - * than any reasonable request or response so this should never affect - * day-to-day operation. - */ - if (parser->nread > (HTTP_MAX_HEADER_SIZE)) { - SET_ERRNO(HPE_HEADER_OVERFLOW); - goto error; - } - } + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); - reexecute_byte: - switch (parser->state) { + switch (CURRENT_STATE()) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ - if (ch == CR || ch == LF) + if (LIKELY(ch == CR || ch == LF)) break; SET_ERRNO(HPE_CLOSED_CONNECTION); @@ -680,13 +717,13 @@ size_t http_parser_execute (http_parser *parser, parser->content_length = ULLONG_MAX; if (ch == 'H') { - parser->state = s_res_or_resp_H; + UPDATE_STATE(s_res_or_resp_H); CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; - parser->state = s_start_req; - goto reexecute_byte; + UPDATE_STATE(s_start_req); + REEXECUTE(); } break; @@ -695,9 +732,9 @@ size_t http_parser_execute (http_parser *parser, case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; - parser->state = s_res_HT; + UPDATE_STATE(s_res_HT); } else { - if (ch != 'E') { + if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } @@ -705,7 +742,7 @@ size_t http_parser_execute (http_parser *parser, parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; - parser->state = s_req_method; + UPDATE_STATE(s_req_method); } break; @@ -716,7 +753,7 @@ size_t http_parser_execute (http_parser *parser, switch (ch) { case 'H': - parser->state = s_res_H; + UPDATE_STATE(s_res_H); break; case CR: @@ -734,39 +771,39 @@ size_t http_parser_execute (http_parser *parser, case s_res_H: STRICT_CHECK(ch != 'T'); - parser->state = s_res_HT; + UPDATE_STATE(s_res_HT); break; case s_res_HT: STRICT_CHECK(ch != 'T'); - parser->state = s_res_HTT; + UPDATE_STATE(s_res_HTT); break; case s_res_HTT: STRICT_CHECK(ch != 'P'); - parser->state = s_res_HTTP; + UPDATE_STATE(s_res_HTTP); break; case s_res_HTTP: STRICT_CHECK(ch != '/'); - parser->state = s_res_first_http_major; + UPDATE_STATE(s_res_first_http_major); break; case s_res_first_http_major: - if (ch < '0' || ch > '9') { + if (UNLIKELY(ch < '0' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - parser->state = s_res_http_major; + UPDATE_STATE(s_res_http_major); break; /* major HTTP version or dot */ case s_res_http_major: { if (ch == '.') { - parser->state = s_res_first_http_minor; + UPDATE_STATE(s_res_first_http_minor); break; } @@ -778,7 +815,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) { + if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -788,24 +825,24 @@ size_t http_parser_execute (http_parser *parser, /* first digit of minor HTTP version */ case s_res_first_http_minor: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - parser->state = s_res_http_minor; + UPDATE_STATE(s_res_http_minor); break; /* minor HTTP version or end of request line */ case s_res_http_minor: { if (ch == ' ') { - parser->state = s_res_first_status_code; + UPDATE_STATE(s_res_first_status_code); break; } - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -813,7 +850,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) { + if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -832,7 +869,7 @@ size_t http_parser_execute (http_parser *parser, goto error; } parser->status_code = ch - '0'; - parser->state = s_res_status_code; + UPDATE_STATE(s_res_status_code); break; } @@ -841,13 +878,13 @@ size_t http_parser_execute (http_parser *parser, if (!IS_NUM(ch)) { switch (ch) { case ' ': - parser->state = s_res_status_start; + UPDATE_STATE(s_res_status_start); break; case CR: - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); break; case LF: - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; default: SET_ERRNO(HPE_INVALID_STATUS); @@ -859,7 +896,7 @@ size_t http_parser_execute (http_parser *parser, parser->status_code *= 10; parser->status_code += ch - '0'; - if (parser->status_code > 999) { + if (UNLIKELY(parser->status_code > 999)) { SET_ERRNO(HPE_INVALID_STATUS); goto error; } @@ -870,30 +907,30 @@ size_t http_parser_execute (http_parser *parser, case s_res_status_start: { if (ch == CR) { - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } MARK(status); - parser->state = s_res_status; + UPDATE_STATE(s_res_status); parser->index = 0; break; } case s_res_status: if (ch == CR) { - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); CALLBACK_DATA(status); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); CALLBACK_DATA(status); break; } @@ -902,7 +939,7 @@ size_t http_parser_execute (http_parser *parser, case s_res_line_almost_done: STRICT_CHECK(ch != LF); - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; case s_start_req: @@ -912,7 +949,7 @@ size_t http_parser_execute (http_parser *parser, parser->flags = 0; parser->content_length = ULLONG_MAX; - if (!IS_ALPHA(ch)) { + if (UNLIKELY(!IS_ALPHA(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } @@ -939,7 +976,7 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_INVALID_METHOD); goto error; } - parser->state = s_req_method; + UPDATE_STATE(s_req_method); CALLBACK_NOTIFY(message_begin); @@ -949,14 +986,14 @@ size_t http_parser_execute (http_parser *parser, case s_req_method: { const char *matcher; - if (ch == '\0') { + if (UNLIKELY(ch == '\0')) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; if (ch == ' ' && matcher[parser->index] == '\0') { - parser->state = s_req_spaces_before_url; + UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (parser->method == HTTP_CONNECT) { @@ -1037,11 +1074,11 @@ size_t http_parser_execute (http_parser *parser, MARK(url); if (parser->method == HTTP_CONNECT) { - parser->state = s_req_server_start; + UPDATE_STATE(s_req_server_start); } - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1062,8 +1099,8 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_INVALID_URL); goto error; default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1082,21 +1119,21 @@ size_t http_parser_execute (http_parser *parser, { switch (ch) { case ' ': - parser->state = s_req_http_start; + UPDATE_STATE(s_req_http_start); CALLBACK_DATA(url); break; case CR: case LF: parser->http_major = 0; parser->http_minor = 9; - parser->state = (ch == CR) ? + UPDATE_STATE((ch == CR) ? s_req_line_almost_done : - s_header_field_start; + s_header_field_start); CALLBACK_DATA(url); break; default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1107,7 +1144,7 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_start: switch (ch) { case 'H': - parser->state = s_req_http_H; + UPDATE_STATE(s_req_http_H); break; case ' ': break; @@ -1119,44 +1156,44 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_H: STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HT; + UPDATE_STATE(s_req_http_HT); break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HTT; + UPDATE_STATE(s_req_http_HTT); break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); - parser->state = s_req_http_HTTP; + UPDATE_STATE(s_req_http_HTTP); break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); - parser->state = s_req_first_http_major; + UPDATE_STATE(s_req_first_http_major); break; /* first digit of major HTTP version */ case s_req_first_http_major: - if (ch < '1' || ch > '9') { + if (UNLIKELY(ch < '1' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - parser->state = s_req_http_major; + UPDATE_STATE(s_req_http_major); break; /* major HTTP version or dot */ case s_req_http_major: { if (ch == '.') { - parser->state = s_req_first_http_minor; + UPDATE_STATE(s_req_first_http_minor); break; } - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1164,7 +1201,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) { + if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1174,31 +1211,31 @@ size_t http_parser_execute (http_parser *parser, /* first digit of minor HTTP version */ case s_req_first_http_minor: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - parser->state = s_req_http_minor; + UPDATE_STATE(s_req_http_minor); break; /* minor HTTP version or end of request line */ case s_req_http_minor: { if (ch == CR) { - parser->state = s_req_line_almost_done; + UPDATE_STATE(s_req_line_almost_done); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } /* XXX allow spaces after digit? */ - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1206,7 +1243,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) { + if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1217,32 +1254,32 @@ size_t http_parser_execute (http_parser *parser, /* end of request line */ case s_req_line_almost_done: { - if (ch != LF) { + if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } case s_header_field_start: { if (ch == CR) { - parser->state = s_headers_almost_done; + UPDATE_STATE(s_headers_almost_done); break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ - parser->state = s_headers_almost_done; - goto reexecute_byte; + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); } c = TOKEN(ch); - if (!c) { + if (UNLIKELY(!c)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } @@ -1250,7 +1287,7 @@ size_t http_parser_execute (http_parser *parser, MARK(header_field); parser->index = 0; - parser->state = s_header_field; + UPDATE_STATE(s_header_field); switch (c) { case 'c': @@ -1278,9 +1315,14 @@ size_t http_parser_execute (http_parser *parser, case s_header_field: { - c = TOKEN(ch); + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; - if (c) { switch (parser->header_state) { case h_general: break; @@ -1381,11 +1423,17 @@ size_t http_parser_execute (http_parser *parser, assert(0 && "Unknown header_state"); break; } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; break; } if (ch == ':') { - parser->state = s_header_value_discard_ws; + UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); break; } @@ -1398,12 +1446,12 @@ size_t http_parser_execute (http_parser *parser, if (ch == ' ' || ch == '\t') break; if (ch == CR) { - parser->state = s_header_value_discard_ws_almost_done; + UPDATE_STATE(s_header_value_discard_ws_almost_done); break; } if (ch == LF) { - parser->state = s_header_value_discard_lws; + UPDATE_STATE(s_header_value_discard_lws); break; } @@ -1413,7 +1461,7 @@ size_t http_parser_execute (http_parser *parser, { MARK(header_value); - parser->state = s_header_value; + UPDATE_STATE(s_header_value); parser->index = 0; c = LOWER(ch); @@ -1434,7 +1482,7 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } @@ -1449,11 +1497,17 @@ size_t http_parser_execute (http_parser *parser, /* looking for 'Connection: close' */ } else if (c == 'c') { parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; } else { - parser->header_state = h_general; + parser->header_state = h_matching_connection_token; } break; + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + default: parser->header_state = h_general; break; @@ -1463,98 +1517,185 @@ size_t http_parser_execute (http_parser *parser, case s_header_value: { - - if (ch == CR) { - parser->state = s_header_almost_done; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - parser->state = s_header_almost_done; - CALLBACK_DATA_NOADVANCE(header_value); - goto reexecute_byte; - } - - c = LOWER(ch); - - switch (parser->header_state) { - case h_general: - break; - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here."); - break; - - case h_content_length: - { - uint64_t t; - - if (ch == ' ') break; - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - t = parser->content_length; - t *= 10; - t += ch - '0'; - - /* Overflow? Test against a conservative limit for simplicity. */ - if ((ULLONG_MAX - 10) / 10 < parser->content_length) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; + const char* start = p; + enum header_states h_state = parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); break; } - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - parser->index++; - if (parser->index > sizeof(CHUNKED)-1 - || c != CHUNKED[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CHUNKED)-2) { - parser->header_state = h_transfer_encoding_chunked; + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = memchr(p, CR, limit); + p_lf = memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; } - break; - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - parser->index++; - if (parser->index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(KEEP_ALIVE)-2) { - parser->header_state = h_connection_keep_alive; + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; } - break; - /* looking for 'Connection: close' */ - case h_matching_connection_close: - parser->index++; - if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CLOSE)-2) { - parser->header_state = h_connection_close; - } - break; + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; - case h_transfer_encoding_chunked: - case h_connection_keep_alive: - case h_connection_close: - if (ch != ' ') parser->header_state = h_general; - break; + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else { + h_state = h_general; + } + break; - default: - parser->state = s_header_value; - parser->header_state = h_general; - break; + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; break; } @@ -1562,15 +1703,15 @@ size_t http_parser_execute (http_parser *parser, { STRICT_CHECK(ch != LF); - parser->state = s_header_value_lws; + UPDATE_STATE(s_header_value_lws); break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') { - parser->state = s_header_value_start; - goto reexecute_byte; + UPDATE_STATE(s_header_value_start); + REEXECUTE(); } /* finished the header */ @@ -1584,32 +1725,52 @@ size_t http_parser_execute (http_parser *parser, case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; default: break; } - parser->state = s_header_field_start; - goto reexecute_byte; + UPDATE_STATE(s_header_field_start); + REEXECUTE(); } case s_header_value_discard_ws_almost_done: { STRICT_CHECK(ch != LF); - parser->state = s_header_value_discard_lws; + UPDATE_STATE(s_header_value_discard_lws); break; } case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { - parser->state = s_header_value_discard_ws; + UPDATE_STATE(s_header_value_discard_ws); break; } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + /* header value was empty */ MARK(header_value); - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); CALLBACK_DATA_NOADVANCE(header_value); - goto reexecute_byte; + REEXECUTE(); } } @@ -1619,16 +1780,18 @@ size_t http_parser_execute (http_parser *parser, if (parser->flags & F_TRAILING) { /* End of a chunked request */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; } - parser->state = s_headers_done; + UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ parser->upgrade = - (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we @@ -1650,15 +1813,15 @@ size_t http_parser_execute (http_parser *parser, default: SET_ERRNO(HPE_CB_headers_complete); - return p - data; /* Error */ + RETURN(p - data); /* Error */ } } if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return p - data; + RETURN(p - data); } - goto reexecute_byte; + REEXECUTE(); } case s_headers_done: @@ -1669,34 +1832,34 @@ size_t http_parser_execute (http_parser *parser, /* Exit, the rest of the connect is in a different protocol. */ if (parser->upgrade) { - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); - return (p - data) + 1; + RETURN((p - data) + 1); } if (parser->flags & F_SKIPBODY) { - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ - parser->state = s_chunk_size_start; + UPDATE_STATE(s_chunk_size_start); } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ - parser->state = s_body_identity; + UPDATE_STATE(s_body_identity); } else { if (parser->type == HTTP_REQUEST || !http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ - parser->state = s_body_identity_eof; + UPDATE_STATE(s_body_identity_eof); } } } @@ -1722,7 +1885,7 @@ size_t http_parser_execute (http_parser *parser, p += to_read - 1; if (parser->content_length == 0) { - parser->state = s_message_done; + UPDATE_STATE(s_message_done); /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * @@ -1734,7 +1897,7 @@ size_t http_parser_execute (http_parser *parser, * important for applications, but let's keep it for now. */ CALLBACK_DATA_(body, p - body_mark + 1, p - data); - goto reexecute_byte; + REEXECUTE(); } break; @@ -1748,7 +1911,7 @@ size_t http_parser_execute (http_parser *parser, break; case s_message_done: - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; @@ -1758,13 +1921,13 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; - if (unhex_val == -1) { + if (UNLIKELY(unhex_val == -1)) { SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length = unhex_val; - parser->state = s_chunk_size; + UPDATE_STATE(s_chunk_size); break; } @@ -1775,7 +1938,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); if (ch == CR) { - parser->state = s_chunk_size_almost_done; + UPDATE_STATE(s_chunk_size_almost_done); break; } @@ -1783,7 +1946,7 @@ size_t http_parser_execute (http_parser *parser, if (unhex_val == -1) { if (ch == ';' || ch == ' ') { - parser->state = s_chunk_parameters; + UPDATE_STATE(s_chunk_parameters); break; } @@ -1796,7 +1959,7 @@ size_t http_parser_execute (http_parser *parser, t += unhex_val; /* Overflow? Test against a conservative limit for simplicity. */ - if ((ULLONG_MAX - 16) / 16 < parser->content_length) { + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } @@ -1810,7 +1973,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { - parser->state = s_chunk_size_almost_done; + UPDATE_STATE(s_chunk_size_almost_done); break; } break; @@ -1825,9 +1988,9 @@ size_t http_parser_execute (http_parser *parser, if (parser->content_length == 0) { parser->flags |= F_TRAILING; - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); } else { - parser->state = s_chunk_data; + UPDATE_STATE(s_chunk_data); } break; } @@ -1849,7 +2012,7 @@ size_t http_parser_execute (http_parser *parser, p += to_read - 1; if (parser->content_length == 0) { - parser->state = s_chunk_data_almost_done; + UPDATE_STATE(s_chunk_data_almost_done); } break; @@ -1859,7 +2022,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); assert(parser->content_length == 0); STRICT_CHECK(ch != CR); - parser->state = s_chunk_data_done; + UPDATE_STATE(s_chunk_data_done); CALLBACK_DATA(body); break; @@ -1867,7 +2030,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; - parser->state = s_chunk_size_start; + UPDATE_STATE(s_chunk_size_start); break; default: @@ -1899,14 +2062,14 @@ size_t http_parser_execute (http_parser *parser, CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); - return len; + RETURN(len); error: if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { SET_ERRNO(HPE_UNKNOWN); } - return (p - data); + RETURN(p - data); } diff --git a/third-party/http-parser/http_parser.h b/third-party/http-parser/http_parser.h index 2f4ab9bd..936cddb1 100644 --- a/third-party/http-parser/http_parser.h +++ b/third-party/http-parser/http_parser.h @@ -136,9 +136,10 @@ enum flags { F_CHUNKED = 1 << 0 , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 - , F_TRAILING = 1 << 3 - , F_UPGRADE = 1 << 4 - , F_SKIPBODY = 1 << 5 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 }; diff --git a/third-party/http-parser/test.c b/third-party/http-parser/test.c index f09e1063..6c45d59d 100644 --- a/third-party/http-parser/test.c +++ b/third-party/http-parser/test.c @@ -950,6 +950,42 @@ const struct message requests[] = ,.body= "" } +#define CONNECTION_MULTI 35 +, {.name = "multiple connection header values with folding" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Something,\r\n" + " Upgrade, ,Keep-Alive\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Something, Upgrade, ,Keep-Alive" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ };