2012-11-20 17:29:39 +01:00
|
|
|
/*
|
2014-03-30 12:09:21 +02:00
|
|
|
* nghttp2 - HTTP/2 C Library
|
2012-11-20 17:29:39 +01:00
|
|
|
*
|
|
|
|
* Copyright (c) 2012 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.
|
|
|
|
*/
|
2013-11-04 09:53:57 +01:00
|
|
|
#include "shrpx_http2_session.h"
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2013-01-10 16:11:41 +01:00
|
|
|
#include <netinet/tcp.h>
|
2012-11-20 17:29:39 +01:00
|
|
|
#include <unistd.h>
|
2014-08-12 15:22:02 +02:00
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <openssl/err.h>
|
|
|
|
|
|
|
|
#include "shrpx_upstream.h"
|
|
|
|
#include "shrpx_downstream.h"
|
|
|
|
#include "shrpx_config.h"
|
|
|
|
#include "shrpx_error.h"
|
2013-11-04 09:53:57 +01:00
|
|
|
#include "shrpx_http2_downstream_connection.h"
|
2012-11-20 17:29:39 +01:00
|
|
|
#include "shrpx_client_handler.h"
|
2012-11-22 13:46:15 +01:00
|
|
|
#include "shrpx_ssl.h"
|
2013-08-25 18:25:31 +02:00
|
|
|
#include "shrpx_http.h"
|
2014-07-05 11:22:40 +02:00
|
|
|
#include "shrpx_worker_config.h"
|
2013-08-27 19:47:22 +02:00
|
|
|
#include "http2.h"
|
2012-11-20 17:29:39 +01:00
|
|
|
#include "util.h"
|
2013-02-09 08:42:01 +01:00
|
|
|
#include "base64.h"
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
using namespace nghttp2;
|
2012-11-20 17:29:39 +01:00
|
|
|
|
|
|
|
namespace shrpx {
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
namespace {
|
|
|
|
void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(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<Http2Session *>(w->data);
|
2015-01-04 16:27:38 +01:00
|
|
|
http2session->stop_settings_timer();
|
2014-12-27 18:59:06 +01:00
|
|
|
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<Http2Session *>(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<Http2Session *>(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<Http2Session *>(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<Http2Session *>(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),
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
state_(DISCONNECTED), connection_check_state_(CONNECTION_CHECK_NONE),
|
2014-12-27 18:59:06 +01:00
|
|
|
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_);
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
|
|
|
|
Http2Session::~Http2Session() { disconnect(); }
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-15 15:34:00 +01:00
|
|
|
int Http2Session::disconnect(bool hard) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2012-12-09 11:15:14 +01:00
|
|
|
SSLOG(INFO, this) << "Disconnecting";
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2013-07-12 17:19:03 +02:00
|
|
|
nghttp2_session_del(session_);
|
2013-09-24 14:34:04 +02:00
|
|
|
session_ = nullptr;
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rb_.reset();
|
|
|
|
wb_.reset();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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_);
|
2013-10-31 13:45:17 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_) {
|
2014-01-08 15:32:47 +01:00
|
|
|
SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN);
|
2012-11-20 17:29:39 +01:00
|
|
|
SSL_shutdown(ssl_);
|
|
|
|
SSL_free(ssl_);
|
2014-12-27 18:59:06 +01:00
|
|
|
ssl_ = nullptr;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (fd_ != -1) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-02-22 13:54:07 +01:00
|
|
|
SSLOG(INFO, this) << "Closing fd=" << fd_;
|
|
|
|
}
|
2013-02-09 08:42:01 +01:00
|
|
|
shutdown(fd_, SHUT_WR);
|
|
|
|
close(fd_);
|
|
|
|
fd_ = -1;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (proxy_htp_) {
|
2013-09-23 17:19:53 +02:00
|
|
|
proxy_htp_.reset();
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
connection_check_state_ = CONNECTION_CHECK_NONE;
|
2012-11-20 17:29:39 +01:00
|
|
|
state_ = DISCONNECTED;
|
|
|
|
|
|
|
|
// Delete all client handler associated to Downstream. When deleting
|
2013-11-04 09:53:57 +01:00
|
|
|
// Http2DownstreamConnection, it calls this object's
|
2013-02-08 13:46:58 +01:00
|
|
|
// remove_downstream_connection(). The multiple
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
// Http2DownstreamConnection objects belong to the same
|
|
|
|
// ClientHandler object. So first dump ClientHandler objects. We
|
|
|
|
// want to allow creating new pending Http2DownstreamConnection with
|
|
|
|
// this object. In order to achieve this, we first swap dconns_ and
|
|
|
|
// streams_. Upstream::on_downstream_reset() may add
|
|
|
|
// Http2DownstreamConnection.
|
|
|
|
std::set<Http2DownstreamConnection *> dconns;
|
|
|
|
dconns.swap(dconns_);
|
|
|
|
std::set<StreamData *> streams;
|
|
|
|
streams.swap(streams_);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
std::set<ClientHandler *> handlers;
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
for (auto dc : dconns) {
|
|
|
|
if (!dc->get_client_handler()) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-22 03:26:32 +01:00
|
|
|
handlers.insert(dc->get_client_handler());
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
for (auto h : handlers) {
|
2014-12-15 15:34:00 +01:00
|
|
|
if (hard) {
|
|
|
|
delete h;
|
|
|
|
continue;
|
|
|
|
}
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
if (h->get_upstream()->on_downstream_reset() != 0) {
|
|
|
|
delete h;
|
|
|
|
}
|
2013-02-08 13:46:58 +01:00
|
|
|
}
|
|
|
|
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
for (auto &s : streams) {
|
2013-09-24 14:34:04 +02:00
|
|
|
delete s;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::check_cert() { return ssl::check_cert(ssl_); }
|
2012-11-22 13:46:15 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::initiate_connection() {
|
2013-02-22 13:54:07 +01:00
|
|
|
int rv = 0;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-02-09 08:42:01 +01:00
|
|
|
SSLOG(INFO, this) << "Connecting to the proxy "
|
2014-11-27 15:39:04 +01:00
|
|
|
<< get_config()->downstream_http_proxy_host.get() << ":"
|
2013-02-09 08:42:01 +01:00
|
|
|
<< get_config()->downstream_http_proxy_port;
|
|
|
|
}
|
2014-08-19 14:22:53 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
fd_ = util::create_nonblock_socket(
|
|
|
|
get_config()->downstream_http_proxy_addr.storage.ss_family);
|
2014-08-19 14:22:53 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
if (fd_ == -1) {
|
|
|
|
return -1;
|
2013-09-24 16:17:53 +02:00
|
|
|
}
|
2013-02-09 08:42:01 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rv = connect(fd_, const_cast<sockaddr *>(
|
|
|
|
&get_config()->downstream_http_proxy_addr.sa),
|
|
|
|
get_config()->downstream_http_proxy_addrlen);
|
|
|
|
if (rv != 0 && errno != EINPROGRESS) {
|
2013-02-09 08:42:01 +01:00
|
|
|
SSLOG(ERROR, this) << "Failed to connect to the proxy "
|
2014-06-08 14:02:40 +02:00
|
|
|
<< get_config()->downstream_http_proxy_host.get()
|
2014-11-27 15:39:04 +01:00
|
|
|
<< ":" << get_config()->downstream_http_proxy_port;
|
2014-12-27 18:59:06 +01:00
|
|
|
return -1;
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2013-09-23 17:19:53 +02:00
|
|
|
proxy_htp_ = util::make_unique<http_parser>();
|
|
|
|
http_parser_init(proxy_htp_.get(), HTTP_RESPONSE);
|
2013-02-09 08:42:01 +01:00
|
|
|
proxy_htp_->data = this;
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2013-02-09 08:42:01 +01:00
|
|
|
state_ = PROXY_CONNECTING;
|
2014-06-01 16:44:32 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-02-09 08:42:01 +01:00
|
|
|
SSLOG(INFO, this) << "Connecting to downstream server";
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_ctx_) {
|
2013-02-22 13:54:07 +01:00
|
|
|
// We are establishing TLS connection.
|
|
|
|
ssl_ = SSL_new(ssl_ctx_);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!ssl_) {
|
2013-02-22 13:54:07 +01:00
|
|
|
SSLOG(ERROR, this) << "SSL_new() failed: "
|
|
|
|
<< ERR_error_string(ERR_get_error(), NULL);
|
|
|
|
return -1;
|
|
|
|
}
|
2012-11-22 13:51:11 +01:00
|
|
|
|
2013-09-24 14:34:04 +02:00
|
|
|
const char *sni_name = nullptr;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (get_config()->backend_tls_sni_name) {
|
2014-06-08 14:02:40 +02:00
|
|
|
sni_name = get_config()->backend_tls_sni_name.get();
|
2014-11-27 15:39:04 +01:00
|
|
|
} else {
|
2014-12-06 10:31:46 +01:00
|
|
|
sni_name = get_config()->downstream_addrs[0].host.get();
|
2013-03-29 14:06:33 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (sni_name && !util::numeric_host(sni_name)) {
|
2013-02-22 13:54:07 +01:00
|
|
|
// TLS extensions: SNI. There is no documentation about the return
|
|
|
|
// code for this function (actually this is macro wrapping SSL_ctrl
|
|
|
|
// at the time of this writing).
|
2013-03-29 14:06:33 +01:00
|
|
|
SSL_set_tlsext_host_name(ssl_, sni_name);
|
2013-02-22 13:54:07 +01:00
|
|
|
}
|
|
|
|
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
|
|
|
// using fd_ and tunnel has been established.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (state_ == DISCONNECTED) {
|
2014-08-12 15:22:02 +02:00
|
|
|
assert(fd_ == -1);
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
fd_ = util::create_nonblock_socket(
|
|
|
|
get_config()->downstream_addrs[0].addr.storage.ss_family);
|
|
|
|
if (fd_ == -1) {
|
|
|
|
return -1;
|
|
|
|
}
|
2014-08-12 15:22:02 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rv = connect(
|
|
|
|
fd_,
|
2014-12-01 19:03:14 +01:00
|
|
|
// TODO maybe not thread-safe?
|
2014-12-06 10:31:46 +01:00
|
|
|
const_cast<sockaddr *>(&get_config()->downstream_addrs[0].addr.sa),
|
|
|
|
get_config()->downstream_addrs[0].addrlen);
|
2014-12-27 18:59:06 +01:00
|
|
|
if (rv != 0 && errno != EINPROGRESS) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (SSL_set_fd(ssl_, fd_) == 0) {
|
|
|
|
return -1;
|
2014-12-01 19:03:14 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
SSL_set_connect_state(ssl_);
|
2013-02-22 13:54:07 +01:00
|
|
|
} else {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (state_ == DISCONNECTED) {
|
2014-08-12 15:22:02 +02:00
|
|
|
// Without TLS and proxy.
|
|
|
|
assert(fd_ == -1);
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
fd_ = util::create_nonblock_socket(
|
|
|
|
get_config()->downstream_addrs[0].addr.storage.ss_family);
|
2014-08-19 14:22:53 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (fd_ == -1) {
|
2014-12-27 18:59:06 +01:00
|
|
|
return -1;
|
2014-08-19 14:22:53 +02:00
|
|
|
}
|
2014-08-12 15:22:02 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rv = connect(fd_, const_cast<sockaddr *>(
|
|
|
|
&get_config()->downstream_addrs[0].addr.sa),
|
|
|
|
get_config()->downstream_addrs[0].addrlen);
|
|
|
|
if (rv != 0 && errno != EINPROGRESS) {
|
|
|
|
return -1;
|
|
|
|
}
|
2014-08-12 15:22:02 +02:00
|
|
|
} else {
|
2014-12-27 18:59:06 +01:00
|
|
|
// Without TLS but with proxy. Connection already
|
|
|
|
// established.
|
|
|
|
if (on_connect() != -1) {
|
|
|
|
state_ = CONNECT_FAILING;
|
2014-08-12 15:22:02 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2013-02-22 13:54:07 +01:00
|
|
|
}
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2014-08-12 15:22:02 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_io_set(&rev_, fd_, EV_READ);
|
|
|
|
ev_io_set(&wev_, fd_, EV_WRITE);
|
|
|
|
|
|
|
|
ev_io_start(loop_, &wev_);
|
|
|
|
|
|
|
|
write_ = &Http2Session::connected;
|
2013-02-09 08:42:01 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
on_write_ = &Http2Session::downstream_write;
|
|
|
|
on_read_ = &Http2Session::downstream_read;
|
2013-02-09 08:42:01 +01:00
|
|
|
|
2013-02-22 13:54:07 +01:00
|
|
|
// We have been already connected when no TLS and proxy is used.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (state_ != CONNECTED) {
|
2013-02-22 13:54:07 +01:00
|
|
|
state_ = CONNECTING;
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_io_start(loop_, &wev_);
|
|
|
|
// TODO we should have timeout for connection establishment
|
|
|
|
ev_timer_again(loop_, &wt_);
|
|
|
|
} else {
|
|
|
|
ev_timer_again(loop_, &rt_);
|
2013-02-22 13:54:07 +01:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
|
|
|
// Unreachable
|
|
|
|
DIE();
|
2014-07-06 16:32:08 +02:00
|
|
|
return 0;
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2013-02-09 08:42:01 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int htp_hdrs_completecb(http_parser *htp) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(htp->data);
|
2013-02-09 08:42:01 +01:00
|
|
|
// We just check status code here
|
2014-11-27 15:39:04 +01:00
|
|
|
if (htp->status_code == 200) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-11-04 09:53:57 +01:00
|
|
|
SSLOG(INFO, http2session) << "Tunneling success";
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->set_state(Http2Session::PROXY_CONNECTED);
|
2014-06-01 16:44:32 +02:00
|
|
|
|
|
|
|
return 0;
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code;
|
2014-06-01 16:44:32 +02:00
|
|
|
http2session->set_state(Http2Session::PROXY_FAILED);
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2013-02-09 08:42:01 +01:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
http_parser_settings htp_hooks = {
|
2014-11-27 15:39:04 +01:00
|
|
|
nullptr, // http_cb on_message_begin;
|
|
|
|
nullptr, // http_data_cb on_url;
|
|
|
|
nullptr, // http_data_cb on_status;
|
|
|
|
nullptr, // http_data_cb on_header_field;
|
|
|
|
nullptr, // http_data_cb on_header_value;
|
|
|
|
htp_hdrs_completecb, // http_cb on_headers_complete;
|
|
|
|
nullptr, // http_data_cb on_body;
|
|
|
|
nullptr // http_cb on_message_complete;
|
2013-02-09 08:42:01 +01:00
|
|
|
};
|
|
|
|
} // namespace
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::downstream_read_proxy() {
|
2014-11-27 15:39:04 +01:00
|
|
|
for (;;) {
|
2014-12-27 18:59:06 +01:00
|
|
|
const void *data;
|
|
|
|
size_t datalen;
|
|
|
|
std::tie(data, datalen) = rb_.get();
|
2013-02-09 08:42:01 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
if (datalen == 0) {
|
|
|
|
break;
|
2014-06-01 14:01:01 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
size_t nread =
|
|
|
|
http_parser_execute(proxy_htp_.get(), &htp_hooks,
|
2014-12-27 18:59:06 +01:00
|
|
|
reinterpret_cast<const char *>(data), datalen);
|
2014-06-01 14:01:01 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rb_.drain(nread);
|
2014-06-01 14:01:01 +02:00
|
|
|
|
|
|
|
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (htperr != HPE_OK) {
|
2014-06-01 14:01:01 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
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;
|
2013-02-09 08:42:01 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
on_write_ = &Http2Session::noop;
|
|
|
|
|
|
|
|
signal_write();
|
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
|
2012-11-20 17:29:39 +01:00
|
|
|
dconns_.insert(dconn);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void
|
|
|
|
Http2Session::remove_downstream_connection(Http2DownstreamConnection *dconn) {
|
2012-11-20 17:29:39 +01:00
|
|
|
dconns_.erase(dconn);
|
|
|
|
dconn->detach_stream_data();
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Session::remove_stream_data(StreamData *sd) {
|
2012-11-20 17:29:39 +01:00
|
|
|
streams_.erase(sd);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (sd->dconn) {
|
2012-11-20 17:29:39 +01:00
|
|
|
sd->dconn->detach_stream_data();
|
|
|
|
}
|
|
|
|
delete sd;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::submit_request(Http2DownstreamConnection *dconn, int32_t pri,
|
2013-11-28 13:36:04 +01:00
|
|
|
const nghttp2_nv *nva, size_t nvlen,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_data_provider *data_prd) {
|
2012-11-20 17:29:39 +01:00
|
|
|
assert(state_ == CONNECTED);
|
2013-09-24 14:34:04 +02:00
|
|
|
auto sd = util::make_unique<StreamData>();
|
2014-03-25 18:04:24 +01:00
|
|
|
// TODO Specify nullptr to pri_spec for now
|
2014-11-27 15:39:04 +01:00
|
|
|
auto stream_id =
|
|
|
|
nghttp2_submit_request(session_, nullptr, nva, nvlen, data_prd, sd.get());
|
|
|
|
if (stream_id < 0) {
|
2013-07-12 17:19:03 +02:00
|
|
|
SSLOG(FATAL, this) << "nghttp2_submit_request() failed: "
|
2014-05-07 16:24:07 +02:00
|
|
|
<< nghttp2_strerror(stream_id);
|
2012-11-20 17:29:39 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2014-05-07 16:24:07 +02:00
|
|
|
|
|
|
|
dconn->attach_stream_data(sd.get());
|
|
|
|
dconn->get_downstream()->set_downstream_stream_id(stream_id);
|
|
|
|
streams_.insert(sd.release());
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
|
2012-11-20 17:29:39 +01:00
|
|
|
assert(state_ == CONNECTED);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
SSLOG(INFO, this) << "RST_STREAM stream_id=" << stream_id
|
|
|
|
<< " with error_code=" << error_code;
|
|
|
|
}
|
|
|
|
int rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, stream_id,
|
|
|
|
error_code);
|
|
|
|
if (rv != 0) {
|
2013-07-12 17:19:03 +02:00
|
|
|
SSLOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2012-11-20 17:29:39 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-18 08:12:03 +01:00
|
|
|
int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
|
2014-11-27 15:39:04 +01:00
|
|
|
int32_t pri) {
|
2014-01-18 08:12:03 +01:00
|
|
|
assert(state_ == CONNECTED);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!dconn) {
|
2014-01-18 08:12:03 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
int rv;
|
2014-03-25 18:04:24 +01:00
|
|
|
|
|
|
|
// TODO Disabled temporarily
|
|
|
|
|
|
|
|
// rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE,
|
|
|
|
// dconn->get_downstream()->
|
|
|
|
// get_downstream_stream_id(), pri);
|
|
|
|
|
|
|
|
rv = 0;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv < NGHTTP2_ERR_FATAL) {
|
2014-01-18 08:12:03 +01:00
|
|
|
SSLOG(FATAL, this) << "nghttp2_submit_priority() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session *Http2Session::get_session() const { return session_; }
|
2013-10-29 16:07:35 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
bool Http2Session::get_flow_control() const { return flow_control_; }
|
2012-11-21 15:47:48 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::resume_data(Http2DownstreamConnection *dconn) {
|
2012-11-20 17:29:39 +01:00
|
|
|
assert(state_ == CONNECTED);
|
2013-09-24 14:34:04 +02:00
|
|
|
auto downstream = dconn->get_downstream();
|
2013-07-12 17:19:03 +02:00
|
|
|
int rv = nghttp2_session_resume_data(session_,
|
2012-11-20 17:29:39 +01:00
|
|
|
downstream->get_downstream_stream_id());
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (rv) {
|
2012-11-20 17:29:39 +01:00
|
|
|
case 0:
|
2013-07-12 17:19:03 +02:00
|
|
|
case NGHTTP2_ERR_INVALID_ARGUMENT:
|
2012-11-20 17:29:39 +01:00
|
|
|
return 0;
|
|
|
|
default:
|
2013-07-12 17:19:03 +02:00
|
|
|
SSLOG(FATAL, this) << "nghttp2_resume_session() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2012-11-20 17:29:39 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
void call_downstream_readcb(Http2Session *http2session,
|
|
|
|
Downstream *downstream) {
|
2013-09-24 14:34:04 +02:00
|
|
|
auto upstream = downstream->get_upstream();
|
2014-12-27 18:59:06 +01:00
|
|
|
if (!upstream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (upstream->downstream_read(downstream->get_downstream_connection()) != 0) {
|
|
|
|
delete upstream->get_client_handler();
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|
|
|
uint32_t error_code, void *user_data) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-11-04 09:53:57 +01:00
|
|
|
SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id
|
|
|
|
<< " is being closed";
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
|
|
|
if (sd == 0) {
|
2012-11-20 17:29:39 +01:00
|
|
|
// We might get this close callback when pushed streams are
|
|
|
|
// closed.
|
2013-08-29 15:58:05 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2013-07-26 12:33:25 +02:00
|
|
|
auto dconn = sd->dconn;
|
2014-11-27 15:39:04 +01:00
|
|
|
if (dconn) {
|
2013-07-26 12:33:25 +02:00
|
|
|
auto downstream = dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream && downstream->get_downstream_stream_id() == stream_id) {
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_upgraded() &&
|
|
|
|
downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
2014-08-14 05:48:30 +02:00
|
|
|
// For tunneled connection, we have to submit RST_STREAM to
|
|
|
|
// upstream *after* whole response body is sent. We just set
|
|
|
|
// MSG_COMPLETE here. Upstream will take care of that.
|
|
|
|
downstream->get_upstream()->on_downstream_body_complete(downstream);
|
|
|
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (error_code == NGHTTP2_NO_ERROR) {
|
|
|
|
if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
2012-11-20 17:29:39 +01:00
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
2013-11-04 09:53:57 +01:00
|
|
|
call_downstream_readcb(http2session, downstream);
|
2012-11-20 17:29:39 +01:00
|
|
|
// dconn may be deleted
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The life time of StreamData ends here
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->remove_stream_data(sd);
|
2013-08-29 15:58:05 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
void Http2Session::start_settings_timer() {
|
2015-01-04 16:27:38 +01:00
|
|
|
ev_timer_again(loop_, &settings_timer_);
|
2013-10-31 13:45:17 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Session::stop_settings_timer() {
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_timer_stop(loop_, &settings_timer_);
|
2013-10-31 13:45:17 +01:00
|
|
|
}
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
2014-01-16 15:41:13 +01:00
|
|
|
const uint8_t *name, size_t namelen,
|
2014-11-27 15:39:04 +01:00
|
|
|
const uint8_t *value, size_t valuelen, uint8_t flags,
|
|
|
|
void *user_data) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!sd || !sd->dconn) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
|
|
|
(frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
|
|
|
|
!downstream->get_expect_final_response())) {
|
2014-07-23 16:32:57 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-01-27 17:17:54 +01:00
|
|
|
DLOG(INFO, downstream) << "Too large header block size="
|
|
|
|
<< downstream->get_response_headers_sum();
|
2014-01-26 16:44:08 +01:00
|
|
|
}
|
2014-01-27 17:17:54 +01:00
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
2014-01-26 16:44:08 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!http2::check_nv(name, namelen, value, valuelen)) {
|
2014-01-16 18:16:53 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-08-08 13:52:32 +02:00
|
|
|
|
2015-01-04 15:22:39 +01:00
|
|
|
auto token = http2::lookup_token(name, namelen);
|
2014-08-08 13:52:32 +02:00
|
|
|
|
2015-01-04 15:22:39 +01:00
|
|
|
if (name[0] == ':') {
|
|
|
|
if (!downstream->response_pseudo_header_allowed(token)) {
|
2014-08-08 13:52:32 +02:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-04 15:22:39 +01:00
|
|
|
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);
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-01-29 13:23:13 +01:00
|
|
|
int on_begin_headers_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
2014-01-29 13:23:13 +01:00
|
|
|
// server sends request HEADERS
|
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_REFUSED_STREAM);
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
|
2014-01-29 13:23:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!sd || !sd->dconn) {
|
2014-01-29 13:23:13 +01:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_INTERNAL_ERROR);
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-01-29 13:23:13 +01:00
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream ||
|
|
|
|
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
2014-01-29 13:23:13 +01:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_INTERNAL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|
|
|
nghttp2_session *session, const nghttp2_frame *frame) {
|
2014-01-16 15:41:13 +01:00
|
|
|
int rv;
|
2014-07-23 16:32:57 +02:00
|
|
|
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
auto &nva = downstream->get_response_headers();
|
2014-01-16 15:41:13 +01:00
|
|
|
|
2014-07-31 14:46:50 +02:00
|
|
|
downstream->set_expect_final_response(false);
|
|
|
|
|
2015-01-04 15:22:39 +01:00
|
|
|
auto status = downstream->get_response_header(http2::HD__STATUS);
|
2014-08-08 16:08:24 +02:00
|
|
|
int status_code;
|
|
|
|
|
2014-12-15 15:14:07 +01:00
|
|
|
if (!http2::non_empty_value(status) ||
|
2014-11-27 15:39:04 +01:00
|
|
|
(status_code = http2::parse_http_status_code(status->value)) == -1) {
|
2014-08-08 16:08:24 +02:00
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
call_downstream_readcb(http2session, downstream);
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-08-08 16:08:24 +02:00
|
|
|
downstream->set_response_http_status(status_code);
|
2014-03-21 11:25:46 +01:00
|
|
|
downstream->set_response_major(2);
|
|
|
|
downstream->set_response_minor(0);
|
2014-01-16 15:41:13 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-25 17:40:06 +02:00
|
|
|
std::stringstream ss;
|
2014-11-27 15:39:04 +01:00
|
|
|
for (auto &nv : nva) {
|
2014-07-25 17:40:06 +02:00
|
|
|
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
SSLOG(INFO, http2session)
|
|
|
|
<< "HTTP response headers. stream_id=" << frame->hd.stream_id << "\n"
|
|
|
|
<< ss.str();
|
2014-07-25 17:40:06 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_non_final_response()) {
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-25 17:40:06 +02:00
|
|
|
SSLOG(INFO, http2session) << "This is non-final response.";
|
2014-07-23 16:32:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
downstream->set_expect_final_response(true);
|
|
|
|
rv = upstream->on_downstream_header_complete(downstream);
|
|
|
|
|
|
|
|
// Now Dowstream's response headers are erased.
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-07-23 16:32:57 +02:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-01-04 15:22:39 +01:00
|
|
|
auto content_length =
|
|
|
|
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!content_length && downstream->get_request_method() != "HEAD" &&
|
|
|
|
downstream->get_request_method() != "CONNECT") {
|
2014-01-16 15:41:13 +01:00
|
|
|
unsigned int status;
|
|
|
|
status = downstream->get_response_http_status();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!((100 <= status && status <= 199) || status == 204 || status == 304)) {
|
2014-01-16 15:41:13 +01:00
|
|
|
// Here we have response body but Content-Length is not known
|
|
|
|
// in advance.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_request_major() <= 0 ||
|
|
|
|
downstream->get_request_minor() <= 0) {
|
2014-01-16 15:41:13 +01:00
|
|
|
// We simply close connection for pre-HTTP/1.1 in this case.
|
|
|
|
downstream->set_response_connection_close(true);
|
|
|
|
} else {
|
|
|
|
// Otherwise, use chunked encoding to keep upstream
|
|
|
|
// connection open. In HTTP2, we are supporsed not to
|
|
|
|
// receive transfer-encoding.
|
|
|
|
downstream->add_response_header("transfer-encoding", "chunked");
|
2014-06-15 09:14:00 +02:00
|
|
|
downstream->set_chunked_response(true);
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
downstream->set_response_state(Downstream::HEADER_COMPLETE);
|
|
|
|
downstream->check_upgrade_fulfilled();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_upgraded()) {
|
2014-01-16 15:41:13 +01:00
|
|
|
downstream->set_response_connection_close(true);
|
|
|
|
// On upgrade sucess, both ends can send data
|
2014-11-27 15:39:04 +01:00
|
|
|
if (upstream->resume_read(SHRPX_MSG_BLOCK, downstream, 0) != 0) {
|
2014-01-16 15:41:13 +01:00
|
|
|
// If resume_read fails, just drop connection. Not ideal.
|
|
|
|
delete upstream->get_client_handler();
|
2014-08-09 11:47:45 +02:00
|
|
|
return -1;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
downstream->set_request_state(Downstream::HEADER_COMPLETE);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
SSLOG(INFO, http2session)
|
|
|
|
<< "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (downstream->get_request_method() == "CONNECT") {
|
2014-01-16 15:41:13 +01:00
|
|
|
// If request is CONNECT, terminate request body to avoid for
|
|
|
|
// stream to stall.
|
|
|
|
downstream->end_upload_data();
|
|
|
|
}
|
|
|
|
rv = upstream->on_downstream_header_complete(downstream);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-01-16 15:41:13 +01:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
void *user_data) {
|
2014-04-03 11:54:15 +02:00
|
|
|
int rv;
|
2014-11-27 15:39:04 +01:00
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
2014-04-03 11:54:15 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (frame->hd.type) {
|
2014-04-03 11:54:15 +02:00
|
|
|
case NGHTTP2_DATA: {
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!sd || !sd->dconn) {
|
2014-04-03 11:54:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream ||
|
|
|
|
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
2014-04-03 11:54:15 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
rv = upstream->on_downstream_body(downstream, nullptr, 0, true);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-04-03 11:54:15 +02:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_INTERNAL_ERROR);
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->disable_downstream_rtimer();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
2014-07-23 16:32:57 +02:00
|
|
|
|
|
|
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
|
|
|
|
|
|
|
rv = upstream->on_downstream_body_complete(downstream);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-07-23 16:32:57 +02:00
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
|
|
|
}
|
2014-04-03 11:54:15 +02:00
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-04-03 11:54:15 +02:00
|
|
|
call_downstream_readcb(http2session, downstream);
|
|
|
|
break;
|
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
case NGHTTP2_HEADERS: {
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!sd || !sd->dconn) {
|
2014-07-23 16:32:57 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
auto downstream = sd->dconn->get_downstream();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-07-23 16:32:57 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
|
2014-07-23 16:32:57 +02:00
|
|
|
rv = on_response_headers(http2session, downstream, session, frame);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
2014-07-23 16:32:57 +02:00
|
|
|
}
|
2015-01-05 16:51:52 +01:00
|
|
|
} else if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_expect_final_response()) {
|
2014-07-31 14:46:50 +02:00
|
|
|
rv = on_response_headers(http2session, downstream, session, frame);
|
2014-07-03 14:18:01 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
2014-07-31 14:46:50 +02:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
} else if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
2014-08-01 17:26:43 +02:00
|
|
|
http2session->submit_rst_stream(frame->hd.stream_id,
|
|
|
|
NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
2014-07-03 14:18:01 +02:00
|
|
|
}
|
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-08-09 11:47:45 +02:00
|
|
|
|
|
|
|
downstream->disable_downstream_rtimer();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
|
2014-07-23 16:32:57 +02:00
|
|
|
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
|
|
|
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
|
|
|
|
rv = upstream->on_downstream_body_complete(downstream);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-07-23 16:32:57 +02:00
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
} else {
|
|
|
|
downstream->reset_downstream_rtimer();
|
2014-07-23 16:32:57 +02:00
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
|
|
|
|
// This may delete downstream
|
|
|
|
call_downstream_readcb(http2session, downstream);
|
|
|
|
|
2014-07-03 14:18:01 +02:00
|
|
|
break;
|
2014-07-23 16:32:57 +02:00
|
|
|
}
|
2013-07-26 12:33:25 +02:00
|
|
|
case NGHTTP2_RST_STREAM: {
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (sd && sd->dconn) {
|
2013-07-26 12:33:25 +02:00
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream &&
|
|
|
|
downstream->get_downstream_stream_id() == frame->hd.stream_id) {
|
2014-08-14 05:48:30 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
downstream->set_response_rst_stream_error_code(
|
|
|
|
frame->rst_stream.error_code);
|
2013-11-04 09:53:57 +01:00
|
|
|
call_downstream_readcb(http2session, downstream);
|
2013-07-26 12:33:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2013-10-31 13:45:17 +01:00
|
|
|
case NGHTTP2_SETTINGS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
2013-10-31 13:45:17 +01:00
|
|
|
break;
|
|
|
|
}
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->stop_settings_timer();
|
2013-10-31 13:45:17 +01:00
|
|
|
break;
|
2013-07-26 12:33:25 +02:00
|
|
|
case NGHTTP2_PUSH_PROMISE:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2013-11-04 09:53:57 +01:00
|
|
|
SSLOG(INFO, http2session)
|
2014-11-27 15:39:04 +01:00
|
|
|
<< "Received downstream PUSH_PROMISE stream_id="
|
|
|
|
<< frame->hd.stream_id
|
|
|
|
<< ", promised_stream_id=" << frame->push_promise.promised_stream_id;
|
2013-07-26 12:33:25 +02:00
|
|
|
}
|
|
|
|
// We just respond with RST_STREAM.
|
2014-01-09 15:47:21 +01:00
|
|
|
http2session->submit_rst_stream(frame->push_promise.promised_stream_id,
|
2013-11-04 09:53:57 +01:00
|
|
|
NGHTTP2_REFUSED_STREAM);
|
2013-07-26 12:33:25 +02:00
|
|
|
break;
|
2012-11-20 17:29:39 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-08-29 14:03:39 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
|
|
|
int32_t stream_id, const uint8_t *data,
|
|
|
|
size_t len, void *user_data) {
|
2012-11-20 17:29:39 +01:00
|
|
|
int rv;
|
2014-11-27 15:39:04 +01:00
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
|
|
|
if (!sd || !sd->dconn) {
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (http2session->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2013-08-29 14:18:40 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2013-07-26 12:33:25 +02:00
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream || downstream->get_downstream_stream_id() != stream_id ||
|
|
|
|
!downstream->expect_response_body()) {
|
2014-07-25 16:13:27 +02:00
|
|
|
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (http2session->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2013-08-29 14:18:40 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2012-11-21 15:47:48 +01:00
|
|
|
|
2014-07-23 16:32:57 +02:00
|
|
|
// We don't want DATA after non-final response, which is illegal in
|
|
|
|
// HTTP.
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_non_final_response()) {
|
2014-07-23 16:32:57 +02:00
|
|
|
http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (http2session->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2014-07-23 16:32:57 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->reset_downstream_rtimer();
|
|
|
|
|
2014-07-05 11:22:40 +02:00
|
|
|
downstream->add_response_bodylen(len);
|
|
|
|
|
2013-07-26 12:33:25 +02:00
|
|
|
auto upstream = downstream->get_upstream();
|
2014-04-03 11:54:15 +02:00
|
|
|
rv = upstream->on_downstream_body(downstream, data, len, false);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (http2session->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
|
|
|
}
|
2014-07-25 14:26:03 +02:00
|
|
|
|
|
|
|
downstream->add_response_datalen(len);
|
|
|
|
|
2013-11-04 09:53:57 +01:00
|
|
|
call_downstream_readcb(http2session, downstream);
|
2013-08-29 14:18:40 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-10-31 13:45:17 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
void *user_data) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) {
|
|
|
|
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!sd || !sd->dconn) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto downstream = sd->dconn->get_downstream();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream ||
|
|
|
|
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
downstream->reset_downstream_rtimer();
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type == NGHTTP2_SETTINGS &&
|
|
|
|
(frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
2014-12-27 18:59:06 +01:00
|
|
|
http2session->start_settings_timer();
|
2013-10-31 13:45:17 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
namespace {
|
2013-08-29 14:51:58 +02:00
|
|
|
int on_frame_not_send_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, int lib_error_code,
|
|
|
|
void *user_data) {
|
|
|
|
auto http2session = static_cast<Http2Session *>(user_data);
|
2014-11-08 02:51:56 +01:00
|
|
|
SSLOG(WARN, http2session) << "Failed to send control frame type="
|
|
|
|
<< static_cast<uint32_t>(frame->hd.type)
|
|
|
|
<< "lib_error_code=" << lib_error_code << ":"
|
|
|
|
<< nghttp2_strerror(lib_error_code);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type == NGHTTP2_HEADERS &&
|
|
|
|
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
2012-11-20 17:29:39 +01:00
|
|
|
// To avoid stream hanging around, flag Downstream::MSG_RESET and
|
|
|
|
// terminate the upstream and downstream connections.
|
2014-11-27 15:39:04 +01:00
|
|
|
auto sd = static_cast<StreamData *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!sd) {
|
2013-08-29 14:51:58 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (sd->dconn) {
|
2013-07-26 12:33:25 +02:00
|
|
|
auto downstream = sd->dconn->get_downstream();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream ||
|
|
|
|
downstream->get_downstream_stream_id() != frame->hd.stream_id) {
|
2013-08-29 14:51:58 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
downstream->set_response_state(Downstream::MSG_RESET);
|
2013-11-04 09:53:57 +01:00
|
|
|
call_downstream_readcb(http2session, downstream);
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2013-11-04 09:53:57 +01:00
|
|
|
http2session->remove_stream_data(sd);
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2013-08-29 14:51:58 +02:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::on_connect() {
|
2012-11-20 17:29:39 +01:00
|
|
|
int rv;
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
state_ = Http2Session::CONNECTED;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (ssl_ctx_) {
|
2014-01-01 15:54:28 +01:00
|
|
|
const unsigned char *next_proto = nullptr;
|
|
|
|
unsigned int next_proto_len;
|
2013-02-22 13:54:07 +01:00
|
|
|
SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len);
|
2014-11-27 15:39:04 +01:00
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
if (next_proto) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
std::string proto(next_proto, next_proto + next_proto_len);
|
2014-01-01 15:54:28 +01:00
|
|
|
SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
|
2014-01-01 15:54:28 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
|
|
|
SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
|
2014-11-27 15:39:04 +01:00
|
|
|
#else // OPENSSL_VERSION_NUMBER < 0x10002000L
|
2014-01-01 15:54:28 +01:00
|
|
|
break;
|
|
|
|
#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
|
2013-02-22 13:54:07 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!next_proto) {
|
2013-02-22 13:54:07 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
2014-08-22 13:59:50 +02:00
|
|
|
nghttp2_session_callbacks *callbacks;
|
|
|
|
rv = nghttp2_session_callbacks_new(&callbacks);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-08-22 13:59:50 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-09-16 16:39:38 +02:00
|
|
|
auto callbacks_deleter =
|
2014-11-27 15:39:04 +01:00
|
|
|
util::defer(callbacks, nghttp2_session_callbacks_del);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_stream_close_callback(
|
|
|
|
callbacks, on_stream_close_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
|
|
|
|
on_frame_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
|
|
|
|
callbacks, on_data_chunk_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
|
|
|
|
on_frame_send_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_not_send_callback(
|
|
|
|
callbacks, on_frame_not_send_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_header_callback(callbacks,
|
|
|
|
on_header_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_begin_headers_callback(
|
|
|
|
callbacks, on_begin_headers_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (get_config()->padding) {
|
|
|
|
nghttp2_session_callbacks_set_select_padding_callback(
|
|
|
|
callbacks, http::select_padding_callback);
|
2014-02-11 09:23:22 +01:00
|
|
|
}
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-08-22 13:59:50 +02:00
|
|
|
rv = nghttp2_session_client_new2(&session_, callbacks, this,
|
2014-04-04 14:57:47 +02:00
|
|
|
get_config()->http2_option);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2012-11-20 17:29:39 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2013-07-26 12:33:25 +02:00
|
|
|
flow_control_ = true;
|
2012-11-21 15:47:48 +01:00
|
|
|
|
2013-11-02 08:59:59 +01:00
|
|
|
nghttp2_settings_entry entry[3];
|
|
|
|
entry[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
|
|
|
entry[0].value = 0;
|
|
|
|
entry[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
2013-11-04 10:14:05 +01:00
|
|
|
entry[1].value = get_config()->http2_max_concurrent_streams;
|
2013-11-02 08:59:59 +01:00
|
|
|
|
|
|
|
entry[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
2013-11-12 03:08:43 +01:00
|
|
|
entry[2].value = (1 << get_config()->http2_downstream_window_bits) - 1;
|
2012-11-21 15:47:48 +01:00
|
|
|
|
2013-10-25 15:50:24 +02:00
|
|
|
rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry,
|
2014-08-27 17:45:12 +02:00
|
|
|
util::array_size(entry));
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2012-11-20 17:29:39 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2013-07-26 12:33:25 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (get_config()->http2_downstream_connection_window_bits > 16) {
|
2013-11-20 16:15:17 +01:00
|
|
|
int32_t delta =
|
2014-11-27 15:39:04 +01:00
|
|
|
(1 << get_config()->http2_downstream_connection_window_bits) - 1 -
|
|
|
|
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
|
2013-11-20 16:15:17 +01:00
|
|
|
rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, delta);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-11-20 16:15:17 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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";
|
2013-12-20 15:28:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2013-07-26 14:35:14 +02:00
|
|
|
|
2014-11-30 14:49:45 +01:00
|
|
|
auto must_terminate =
|
|
|
|
!get_config()->downstream_no_tls && !ssl::check_http2_requirement(ssl_);
|
2014-06-10 17:19:54 +02:00
|
|
|
|
2014-11-30 14:49:45 +01:00
|
|
|
if (must_terminate) {
|
2014-06-10 17:19:54 +02:00
|
|
|
rv = terminate_session(NGHTTP2_INADEQUATE_SECURITY);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-06-10 17:19:54 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-30 14:49:45 +01:00
|
|
|
if (must_terminate) {
|
2014-06-10 17:19:54 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
reset_connection_check_timer();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
// submit pending request
|
2014-11-27 15:39:04 +01:00
|
|
|
for (auto dconn : dconns_) {
|
|
|
|
if (dconn->push_request_headers() == 0) {
|
2014-09-17 15:36:42 +02:00
|
|
|
auto downstream = dconn->get_downstream();
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
|
2014-06-27 15:34:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-06-27 15:34:54 +02:00
|
|
|
SSLOG(INFO, this) << "backend request failed";
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-06-27 15:34:54 +02:00
|
|
|
|
|
|
|
auto downstream = dconn->get_downstream();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-06-27 15:34:54 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
|
|
|
|
upstream->on_downstream_abort_request(downstream, 400);
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
signal_write();
|
2012-11-20 17:29:39 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::do_read() { return read_(*this); }
|
|
|
|
int Http2Session::do_write() { return write_(*this); }
|
2014-01-18 08:50:52 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::on_read() { return on_read_(*this); }
|
|
|
|
int Http2Session::on_write() { return on_write_(*this); }
|
2014-06-01 14:01:01 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::downstream_read() {
|
|
|
|
ssize_t rv = 0;
|
2014-06-01 14:01:01 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
for (;;) {
|
|
|
|
const void *data;
|
|
|
|
size_t nread;
|
|
|
|
std::tie(data, nread) = rb_.get();
|
|
|
|
if (nread == 0) {
|
|
|
|
break;
|
2014-06-01 14:01:01 +02:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
rv = nghttp2_session_mem_recv(
|
|
|
|
session_, reinterpret_cast<const uint8_t *>(data), nread);
|
2014-06-01 14:01:01 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv < 0) {
|
2014-06-01 14:01:01 +02:00
|
|
|
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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";
|
2014-06-01 14:01:01 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
return -1;
|
2014-01-18 08:50:52 +01:00
|
|
|
}
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
signal_write();
|
|
|
|
return 0;
|
|
|
|
}
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::downstream_write() {
|
|
|
|
if (data_pending_) {
|
|
|
|
auto n = std::min(wb_.wleft(), data_pendinglen_);
|
|
|
|
wb_.write(data_pending_, n);
|
|
|
|
if (n < data_pendinglen_) {
|
2015-01-02 05:42:32 +01:00
|
|
|
data_pending_ += n;
|
2014-12-27 18:59:06 +01:00
|
|
|
data_pendinglen_ -= n;
|
|
|
|
return 0;
|
2014-02-18 15:23:11 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
data_pending_ = nullptr;
|
|
|
|
data_pendinglen_ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (;;) {
|
2014-02-18 15:23:11 +01:00
|
|
|
const uint8_t *data;
|
|
|
|
auto datalen = nghttp2_session_mem_send(session_, &data);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (datalen < 0) {
|
2014-02-18 15:23:11 +01:00
|
|
|
SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
|
|
|
|
<< nghttp2_strerror(datalen);
|
2014-12-27 18:59:06 +01:00
|
|
|
return -1;
|
2014-02-18 15:23:11 +01:00
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (datalen == 0) {
|
2014-02-18 15:23:11 +01:00
|
|
|
break;
|
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
auto n = wb_.write(data, datalen);
|
|
|
|
if (n < static_cast<decltype(n)>(datalen)) {
|
|
|
|
data_pending_ = data + n;
|
|
|
|
data_pendinglen_ = datalen - n;
|
|
|
|
return 0;
|
2014-02-18 15:23:11 +01:00
|
|
|
}
|
2014-03-03 13:18:24 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (nghttp2_session_want_read(session_) == 0 &&
|
2014-12-27 18:59:06 +01:00
|
|
|
nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-02-18 15:23:11 +01:00
|
|
|
SSLOG(INFO, this) << "No more read/write for this session";
|
2012-12-16 17:10:45 +01:00
|
|
|
}
|
2014-02-18 15:23:11 +01:00
|
|
|
return -1;
|
2012-12-16 17:10:45 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
2014-02-18 15:23:11 +01:00
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
void Http2Session::signal_write() { write_requested_ = true; }
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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_;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_io *Http2Session::get_wev() { return &wev_; }
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::get_state() const { return state_; }
|
2012-11-20 17:29:39 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Session::set_state(int state) { state_ = state; }
|
2013-02-09 08:42:01 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::terminate_session(uint32_t error_code) {
|
2013-10-31 13:45:17 +01:00
|
|
|
int rv;
|
2013-12-25 16:23:07 +01:00
|
|
|
rv = nghttp2_session_terminate_session(session_, error_code);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-10-31 13:45:17 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
SSL *Http2Session::get_ssl() const { return ssl_; }
|
2014-04-26 16:00:58 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Session::consume(int32_t stream_id, size_t len) {
|
2014-07-25 14:26:03 +02:00
|
|
|
int rv;
|
2014-07-02 16:20:45 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!session_) {
|
2014-07-31 16:34:33 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-25 14:26:03 +02:00
|
|
|
rv = nghttp2_session_consume(session_, stream_id, len);
|
2014-07-02 16:20:45 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-11-08 02:51:56 +01:00
|
|
|
SSLOG(WARN, this) << "nghttp2_session_consume() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2014-07-02 16:20:45 +02:00
|
|
|
|
2014-07-25 14:26:03 +02:00
|
|
|
return -1;
|
2014-07-02 16:20:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
bool Http2Session::can_push_request() const {
|
|
|
|
return state_ == CONNECTED &&
|
|
|
|
connection_check_state_ == CONNECTION_CHECK_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Session::start_checking_connection() {
|
|
|
|
if (state_ != CONNECTED ||
|
|
|
|
connection_check_state_ != CONNECTION_CHECK_REQUIRED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
connection_check_state_ = CONNECTION_CHECK_STARTED;
|
|
|
|
|
|
|
|
SSLOG(INFO, this) << "Start checking connection";
|
|
|
|
// If connection is down, we may get error when writing data. Issue
|
|
|
|
// ping frame to see whether connection is alive.
|
|
|
|
nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, NULL);
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
signal_write();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
void Http2Session::reset_connection_check_timer() {
|
|
|
|
ev_timer_again(loop_, &connchk_timer_);
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
void Http2Session::connection_alive() {
|
|
|
|
reset_connection_check_timer();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
|
|
|
if (connection_check_state_ == CONNECTION_CHECK_NONE) {
|
2014-12-27 18:59:06 +01:00
|
|
|
return;
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
SSLOG(INFO, this) << "Connection alive";
|
|
|
|
connection_check_state_ = CONNECTION_CHECK_NONE;
|
|
|
|
|
|
|
|
// submit pending request
|
|
|
|
for (auto dconn : dconns_) {
|
|
|
|
auto downstream = dconn->get_downstream();
|
|
|
|
if (!downstream ||
|
|
|
|
(downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
|
|
|
|
downstream->get_request_state() != Downstream::MSG_COMPLETE) ||
|
|
|
|
downstream->get_response_state() != Downstream::INITIAL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto upstream = downstream->get_upstream();
|
|
|
|
|
|
|
|
if (dconn->push_request_headers() == 0) {
|
|
|
|
upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
SSLOG(INFO, this) << "backend request failed";
|
|
|
|
}
|
|
|
|
|
|
|
|
upstream->on_downstream_abort_request(downstream, 400);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Session::set_connection_check_state(int state) {
|
|
|
|
connection_check_state_ = state;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Session::noop() { return 0; }
|
|
|
|
|
|
|
|
int Http2Session::connected() {
|
2015-01-05 08:14:10 +01:00
|
|
|
if (!util::check_socket_connected(fd_)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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 (;;) {
|
2015-01-06 14:48:17 +01:00
|
|
|
// we should process buffered data first before we read EOF.
|
2014-12-27 18:59:06 +01:00
|
|
|
if (rb_.rleft() && on_read() != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-01-06 14:48:17 +01:00
|
|
|
if (rb_.rleft()) {
|
|
|
|
return 0;
|
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-01-02 05:54:41 +01:00
|
|
|
wb_.reset();
|
2014-12-27 18:59:06 +01:00
|
|
|
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_);
|
|
|
|
|
2015-01-08 16:07:28 +01:00
|
|
|
ERR_clear_error();
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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_);
|
|
|
|
|
2015-01-08 16:07:28 +01:00
|
|
|
ERR_clear_error();
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
for (;;) {
|
2015-01-06 14:48:17 +01:00
|
|
|
// we should process buffered data first before we read EOF.
|
2014-12-27 18:59:06 +01:00
|
|
|
if (rb_.rleft() && on_read() != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-01-06 14:48:17 +01:00
|
|
|
if (rb_.rleft()) {
|
|
|
|
return 0;
|
|
|
|
}
|
2015-01-02 05:54:41 +01:00
|
|
|
rb_.reset();
|
2014-12-27 18:59:06 +01:00
|
|
|
struct iovec iov[2];
|
|
|
|
auto iovcnt = rb_.wiovec(iov);
|
2015-01-06 13:56:19 +01:00
|
|
|
if (iovcnt == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
2015-01-06 13:56:19 +01:00
|
|
|
auto rv = SSL_read(ssl_, iov[0].iov_base, iov[0].iov_len);
|
2014-12-27 18:59:06 +01:00
|
|
|
|
2015-01-06 13:56:19 +01:00
|
|
|
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:
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
SSLOG(INFO, this) << "SSL_read: SSL_get_error returned " << err;
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
2015-01-06 13:56:19 +01:00
|
|
|
return -1;
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
|
|
|
}
|
2015-01-06 13:56:19 +01:00
|
|
|
|
|
|
|
rb_.write(rv);
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Session::write_tls() {
|
|
|
|
ev_timer_again(loop_, &rt_);
|
|
|
|
|
2015-01-08 16:07:28 +01:00
|
|
|
ERR_clear_error();
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
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:
|
2015-01-06 13:56:19 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
SSLOG(INFO, this) << "SSL_write: SSL_get_error returned " << err;
|
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wb_.drain(rv);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
2015-01-02 05:54:41 +01:00
|
|
|
wb_.reset();
|
2014-12-27 18:59:06 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-20 17:29:39 +01:00
|
|
|
} // namespace shrpx
|