Commit f620655d authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttp, nghttpx: Add HTTP Upgrade from HTTP/1.1 to HTTP/2.0

nghttpx does not perform upgrade if the request has request body.
parent ea5a1b60
This diff is collapsed.
......@@ -366,8 +366,7 @@ void fill_default_config()
mod_config()->downstream_addrlen = 0;
mod_config()->num_worker = 1;
mod_config()->spdy_max_concurrent_streams =
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
mod_config()->spdy_max_concurrent_streams = 100;
mod_config()->add_x_forwarded_for = false;
mod_config()->no_via = false;
mod_config()->accesslog = false;
......
......@@ -122,13 +122,14 @@ void upstream_eventcb(bufferevent *bev, short events, void *arg)
} // namespace
namespace {
void upstream_connhd_readcb(bufferevent *bev, void *arg)
void upstream_http2_connhd_readcb(bufferevent *bev, void *arg)
{
// This callback assumes upstream is Http2Upstream.
uint8_t data[NGHTTP2_CLIENT_CONNECTION_HEADER_LEN];
auto handler = reinterpret_cast<ClientHandler*>(arg);
size_t leftlen = handler->get_left_connhd_len();
auto leftlen = handler->get_left_connhd_len();
auto input = bufferevent_get_input(bev);
int readlen = evbuffer_remove(input, data, leftlen);
auto readlen = evbuffer_remove(input, data, leftlen);
if(readlen == -1) {
delete handler;
return;
......@@ -136,6 +137,10 @@ void upstream_connhd_readcb(bufferevent *bev, void *arg)
if(memcmp(NGHTTP2_CLIENT_CONNECTION_HEADER +
NGHTTP2_CLIENT_CONNECTION_HEADER_LEN - leftlen,
data, readlen) != 0) {
// There is no downgrade path here. Just drop the connection.
if(LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "invalid client connection header";
}
delete handler;
return;
}
......@@ -153,6 +158,56 @@ void upstream_connhd_readcb(bufferevent *bev, void *arg)
}
} // namespace
namespace {
void upstream_http1_connhd_readcb(bufferevent *bev, void *arg)
{
// This callback assumes upstream is HttpsUpstream.
uint8_t data[NGHTTP2_CLIENT_CONNECTION_HEADER_LEN];
auto handler = reinterpret_cast<ClientHandler*>(arg);
auto leftlen = handler->get_left_connhd_len();
auto input = bufferevent_get_input(bev);
auto readlen = evbuffer_copyout(input, data, leftlen);
if(readlen == -1) {
delete handler;
return;
}
if(memcmp(NGHTTP2_CLIENT_CONNECTION_HEADER +
NGHTTP2_CLIENT_CONNECTION_HEADER_LEN - leftlen,
data, readlen) != 0) {
if(LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "This is HTTP/1.1 connection, "
<< "but may be upgraded to HTTP/2.0 later.";
}
// Reset header length for later HTTP/2.0 upgrade
handler->set_left_connhd_len(NGHTTP2_CLIENT_CONNECTION_HEADER_LEN);
if(handler->on_read() != 0) {
delete handler;
return;
}
return;
}
if(evbuffer_drain(input, readlen) == -1) {
delete handler;
return;
}
leftlen -= readlen;
handler->set_left_connhd_len(leftlen);
if(leftlen == 0) {
if(LOG_ENABLED(INFO)) {
CLOG(INFO, handler) << "direct HTTP/2.0 connection";
}
handler->direct_http2_upgrade();
handler->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(handler->on_read() != 0) {
delete handler;
return;
}
}
}
} // namespace
ClientHandler::ClientHandler(bufferevent *bev, int fd, SSL *ssl,
const char *ipaddr)
: bev_(bev),
......@@ -171,15 +226,11 @@ ClientHandler::ClientHandler(bufferevent *bev, int fd, SSL *ssl,
if(ssl_) {
set_bev_cb(nullptr, upstream_writecb, upstream_eventcb);
} else {
if(get_config()->client_mode) {
// Client mode
// For non-TLS version, first create HttpsUpstream. It may be
// upgraded to HTTP/2.0 through HTTP Upgrade or direct HTTP/2.0
// connection.
upstream_ = new HttpsUpstream(this);
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
} else {
// no-TLS SPDY
upstream_ = new Http2Upstream(this);
set_bev_cb(upstream_connhd_readcb, upstream_writecb, upstream_eventcb);
}
set_bev_cb(upstream_http1_connhd_readcb, nullptr, upstream_eventcb);
}
}
......@@ -249,7 +300,8 @@ int ClientHandler::validate_next_proto()
CLOG(INFO, this) << "The negotiated next protocol: " << proto;
}
if(proto == NGHTTP2_PROTO_VERSION_ID) {
set_bev_cb(upstream_connhd_readcb, upstream_writecb, upstream_eventcb);
set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb,
upstream_eventcb);
upstream_ = new Http2Upstream(this);
return 0;
} else {
......@@ -369,4 +421,38 @@ void ClientHandler::set_left_connhd_len(size_t left)
left_connhd_len_ = left;
}
void ClientHandler::direct_http2_upgrade()
{
delete upstream_;
upstream_= new Http2Upstream(this);
set_bev_cb(upstream_readcb, upstream_writecb, upstream_eventcb);
}
int ClientHandler::perform_http2_upgrade(HttpsUpstream *http)
{
int rv;
auto upstream = new Http2Upstream(this);
if(upstream->upgrade_upstream(http) != 0) {
delete upstream;
return -1;
}
upstream_ = upstream;
set_bev_cb(upstream_http2_connhd_readcb, upstream_writecb, upstream_eventcb);
static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: HTTP/2.0\r\n"
"\r\n";
rv = bufferevent_write(bev_, res, sizeof(res) - 1);
if(rv != 0) {
CLOG(FATAL, this) << "bufferevent_write() faild";
return -1;
}
return 0;
}
bool ClientHandler::get_http2_upgrade_allowed() const
{
return !ssl_;
}
} // namespace shrpx
......@@ -37,6 +37,7 @@ namespace shrpx {
class Upstream;
class DownstreamConnection;
class SpdySession;
class HttpsUpstream;
class ClientHandler {
public:
......@@ -65,6 +66,14 @@ public:
SpdySession* get_spdy_session() const;
size_t get_left_connhd_len() const;
void set_left_connhd_len(size_t left);
// Call this function when HTTP/2.0 connection header is received at
// the start of the connection.
void direct_http2_upgrade();
// Performs HTTP/2.0 Upgrade from the connection managed by
// |http|. If this function fails, the connection must be
// terminated. This function returns 0 if it succeeds, or -1.
int perform_http2_upgrade(HttpsUpstream *http);
bool get_http2_upgrade_allowed() const;
private:
bufferevent *bev_;
int fd_;
......
......@@ -52,6 +52,7 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority)
request_connection_close_(false),
request_expect_100_continue_(false),
request_header_key_prev_(false),
request_bodylen_(0),
response_state_(INITIAL),
response_http_status_(0),
response_major_(1),
......@@ -242,11 +243,24 @@ int Downstream::get_request_minor() const
return request_minor_;
}
void Downstream::reset_upstream(Upstream* upstream)
{
upstream_ = upstream;
if(dconn_) {
dconn_->on_upstream_change(upstream);
}
}
Upstream* Downstream::get_upstream() const
{
return upstream_;
}
void Downstream::set_stream_id(int32_t stream_id)
{
stream_id_ = stream_id;
}
int32_t Downstream::get_stream_id() const
{
return stream_id_;
......@@ -306,6 +320,7 @@ int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen)
DLOG(WARNING, this) << "dconn_ is NULL";
return 0;
}
request_bodylen_ += datalen;
return dconn_->push_upload_data_chunk(data, datalen);
}
......@@ -482,6 +497,7 @@ void Downstream::check_upgrade_fulfilled()
upgraded_ = 200 <= response_http_status_ && response_http_status_ < 300;
} else {
// TODO Do more strict checking for upgrade headers
if(response_http_status_ == 101) {
for(auto& hd : request_headers_) {
if(util::strieq("upgrade", hd.first.c_str())) {
upgraded_ = true;
......@@ -489,6 +505,7 @@ void Downstream::check_upgrade_fulfilled()
}
}
}
}
}
bool Downstream::get_upgraded() const
......@@ -516,6 +533,27 @@ bool Downstream::get_upgrade_request() const
return upgrade_request_;
}
bool Downstream::http2_upgrade_request() const
{
if(request_bodylen_ != 0) {
return false;
}
bool upgrade_seen = false;
bool http2_settings_seen = false;
for(auto& hd : request_headers_) {
// For now just check NGHTTP2_PROTO_VERSION_ID in Upgrade header
// field and existence of HTTP2-Settings header field.
if(util::strieq(hd.first.c_str(), "upgrade")) {
if(util::strieq(hd.second.c_str(), NGHTTP2_PROTO_VERSION_ID)) {
upgrade_seen = true;
}
} else if(util::strieq(hd.first.c_str(), "http2-settings")) {
http2_settings_seen = true;
}
}
return upgrade_seen && http2_settings_seen;
}
void Downstream::set_downstream_stream_id(int32_t stream_id)
{
downstream_stream_id_ = stream_id;
......
......@@ -50,7 +50,9 @@ class Downstream {
public:
Downstream(Upstream *upstream, int stream_id, int priority);
~Downstream();
void reset_upstream(Upstream *upstream);
Upstream* get_upstream() const;
void set_stream_id(int32_t stream_id);
int32_t get_stream_id() const;
void set_priority(int pri);
void pause_read(IOCtrlReason reason);
......@@ -78,6 +80,8 @@ public:
// Returns true if the upgrade is succeded as a result of the call
// check_upgrade_fulfilled().
bool get_upgraded() const;
// Returns true if the request is HTTP Upgrade for HTTP/2.0
bool http2_upgrade_request() const;
// downstream request API
const Headers& get_request_headers() const;
void add_request_header(const std::string& name, const std::string& value);
......@@ -169,6 +173,8 @@ private:
bool request_expect_100_continue_;
Headers request_headers_;
bool request_header_key_prev_;
// the length of request body
int64_t request_bodylen_;
int response_state_;
unsigned int response_http_status_;
......
......@@ -32,6 +32,7 @@
namespace shrpx {
class ClientHandler;
class Upstream;
class Downstream;
class DownstreamConnection {
......@@ -54,6 +55,8 @@ public:
virtual int on_read() = 0;
virtual int on_write() = 0;
virtual void on_upstream_change(Upstream *uptream) = 0;
ClientHandler* get_client_handler();
Downstream* get_downstream();
protected:
......
......@@ -30,12 +30,14 @@
#include <sstream>
#include "shrpx_client_handler.h"
#include "shrpx_https_upstream.h"
#include "shrpx_downstream.h"
#include "shrpx_downstream_connection.h"
#include "shrpx_config.h"
#include "shrpx_http.h"
#include "shrpx_accesslog.h"
#include "util.h"
#include "base64.h"
using namespace nghttp2;
......@@ -135,6 +137,40 @@ void on_stream_close_callback
}
} // namespace
int Http2Upstream::upgrade_upstream(HttpsUpstream *http)
{
int rv;
std::string settings_payload;
auto downstream = http->get_downstream();
for(auto& hd : downstream->get_request_headers()) {
if(util::strieq(hd.first.c_str(), "http2-settings")) {
auto val = hd.second;
util::to_base64(val);
settings_payload = base64::decode(std::begin(val), std::end(val));
break;
}
}
rv = nghttp2_session_upgrade
(session_,
reinterpret_cast<const uint8_t*>(settings_payload.c_str()),
settings_payload.size(),
nullptr);
if(rv != 0) {
ULOG(WARNING, this) << "nghttp2_session_upgrade() returned error: "
<< nghttp2_strerror(rv);
return -1;
}
pre_upstream_ = http;
http->pop_downstream();
downstream->reset_upstream(this);
add_downstream(downstream);
downstream->init_response_body_buf();
downstream->set_stream_id(1);
downstream->set_priority(0);
return 0;
}
namespace {
void on_frame_recv_callback
(nghttp2_session *session, nghttp2_frame *frame, void *user_data)
......@@ -335,7 +371,8 @@ nghttp2_error_code infer_upstream_rst_stream_error_code
Http2Upstream::Http2Upstream(ClientHandler *handler)
: handler_(handler),
session_(nullptr)
session_(nullptr),
pre_upstream_(nullptr)
{
//handler->set_bev_cb(spdy_readcb, 0, spdy_eventcb);
handler->set_upstream_timeouts(&get_config()->spdy_upstream_read_timeout,
......@@ -380,13 +417,12 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_END_FLOW_CONTROL,
0, 0);
assert(rv == 0);
send();
}
Http2Upstream::~Http2Upstream()
{
nghttp2_session_del(session_);
delete pre_upstream_;
}
int Http2Upstream::on_read()
......
......@@ -35,6 +35,7 @@
namespace shrpx {
class ClientHandler;
class HttpsUpstream;
class Http2Upstream : public Upstream {
public:
......@@ -68,12 +69,17 @@ public:
bool get_flow_control() const;
int32_t get_initial_window_size() const;
// Perform HTTP/2.0 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);
private:
ClientHandler *handler_;
nghttp2_session *session_;
bool flow_control_;
int32_t initial_window_size_;
DownstreamQueue downstream_queue_;
HttpsUpstream *pre_upstream_;
};
} // namespace shrpx
......
......@@ -125,27 +125,37 @@ int HttpDownstreamConnection::push_request_headers()
for(Headers::const_iterator i = request_headers.begin();
i != request_headers.end(); ++i) {
if(util::strieq((*i).first.c_str(), "connection")) {
if(util::strifind((*i).second.c_str(), "upgrade")) {
// nghttpx handles HTTP/2.0 upgrade and does not relay it to the
// downstream.
if(util::strifind((*i).second.c_str(), "upgrade") &&
!util::strifind((*i).second.c_str(), "http2-settings")) {
connection_upgrade = (*i).second;
}
continue;
} else if(util::strieq((*i).first.c_str(), "upgrade")) {
// nghttpx handles HTTP/2.0 upgrade and does not relay it to the
// downstream.
if(util::strieq((*i).second.c_str(), NGHTTP2_PROTO_VERSION_ID)) {
continue;
}
} else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") ||
util::strieq((*i).first.c_str(), "keep-alive") ||
util::strieq((*i).first.c_str(), "proxy-connection")) {
util::strieq((*i).first.c_str(), "proxy-connection") ||
util::strieq((*i).first.c_str(), "http2-settings")) {
continue;
}
if(!get_config()->no_via && util::strieq((*i).first.c_str(), "via")) {
} else if(util::strieq((*i).first.c_str(), "via")) {
if(!get_config()->no_via) {
via_value = (*i).second;
continue;
}
if(util::strieq((*i).first.c_str(), "x-forwarded-for")) {
} else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) {
xff_value = (*i).second;
continue;
}
if(util::strieq((*i).first.c_str(), "expect") &&
util::strifind((*i).second.c_str(), "100-continue")) {
} else if(util::strieq((*i).first.c_str(), "expect")) {
if(util::strifind((*i).second.c_str(), "100-continue")) {
continue;
}
}
hdrs += (*i).first;
http::capitalize(hdrs, hdrs.size()-(*i).first.size());
hdrs += ": ";
......@@ -493,4 +503,12 @@ int HttpDownstreamConnection::on_write()
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);
}
} // namespace shrpx
......@@ -57,6 +57,8 @@ public:
virtual int on_read();
virtual int on_write();
virtual void on_upstream_change(Upstream *upstream);
bufferevent* get_bev();
private:
bufferevent *bev_;
......
......@@ -293,11 +293,11 @@ int HttpsUpstream::on_read()
// Get downstream again because it may be initialized in http parser
// execution
downstream = get_downstream();
auto handler = get_client_handler();
http_errno htperr = HTTP_PARSER_ERRNO(htp_);
if(htperr == HPE_PAUSED) {
if(downstream->get_request_state() == Downstream::CONNECT_FAIL) {
get_client_handler()->set_should_close_after_write(true);
handler->set_should_close_after_write(true);
// Following paues_read is needed to avoid reading next data.
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(503) != 0) {
......@@ -311,6 +311,13 @@ int HttpsUpstream::on_read()
assert(downstream->get_response_state() == Downstream::MSG_COMPLETE);
delete_downstream();
} else {
if(handler->get_http2_upgrade_allowed() &&
downstream->http2_upgrade_request()) {
if(handler->perform_http2_upgrade(this) != 0) {
return -1;
}
return 0;
}
pause_read(SHRPX_MSG_BLOCK);
}
}
......@@ -322,7 +329,7 @@ int HttpsUpstream::on_read()
ULOG(WARNING, this) << "Request Header too long:"
<< current_header_length_
<< " bytes";
get_client_handler()->set_should_close_after_write(true);
handler->set_should_close_after_write(true);
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(400) != 0) {
return -1;
......@@ -340,7 +347,7 @@ int HttpsUpstream::on_read()
<< "(" << http_errno_name(htperr) << ") "
<< http_errno_description(htperr);
}
get_client_handler()->set_should_close_after_write(true);
handler->set_should_close_after_write(true);
pause_read(SHRPX_MSG_BLOCK);
if(error_reply(400) != 0) {
return -1;
......@@ -635,6 +642,13 @@ Downstream* HttpsUpstream::get_downstream() const
return downstream_;
}
Downstream* HttpsUpstream::pop_downstream()
{
auto downstream = downstream_;
downstream_ = nullptr;
return downstream;
}
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
{
if(LOG_ENABLED(INFO)) {
......
......@@ -52,6 +52,7 @@ public:
void attach_downstream(Downstream *downstream);
void delete_downstream();
Downstream* get_downstream() const;
Downstream* pop_downstream();
int error_reply(int status_code);
virtual void pause_read(IOCtrlReason reason);
......
......@@ -285,30 +285,41 @@ int SpdyDownstreamConnection::push_request_headers()
chunked_encoding = true;
}
// Ignore transfer-encoding
continue;
} else if(util::strieq((*i).first.c_str(), "upgrade")) {
// nghttpx handles HTTP/2.0 upgrade and does not relay it to the
// downstream.
if(util::strieq((*i).second.c_str(), NGHTTP2_PROTO_VERSION_ID)) {
continue;
}
} else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") ||
util::strieq((*i).first.c_str(), "keep-alive") ||
util::strieq((*i).first.c_str(), "connection") ||
util:: strieq((*i).first.c_str(), "proxy-connection")) {
util::strieq((*i).first.c_str(), "proxy-connection") ||
util::strieq((*i).first.c_str(), "http2-settings")) {
// These are ignored
continue;
} else if(!get_config()->no_via &&
util::strieq((*i).first.c_str(), "via")) {
via_value = (*i).second;
continue;
} else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) {
xff_value = (*i).second;
continue;
} else if(util::strieq((*i).first.c_str(), "expect") &&
util::strifind((*i).second.c_str(), "100-continue")) {
// Ignore
continue;
} else if(util::strieq((*i).first.c_str(), "host")) {
nv[hdidx++] = ":host";
nv[hdidx++] = (*i).second.c_str();
} else {
if(util::strieq((*i).first.c_str(), "content-length")) {
continue;
} else if(util::strieq((*i).first.c_str(), "content-length")) {
content_length = true;
}
nv[hdidx++] = (*i).first.c_str();
nv[hdidx++] = (*i).second.c_str();
}
}
if(get_config()->add_x_forwarded_for) {
nv[hdidx++] = "x-forwarded-for";
......
......@@ -58,6 +58,8 @@ public:
virtual int on_read();
virtual int on_write();
virtual void on_upstream_change(Upstream *upstream) {}
int send();
int init_request_body_buf();
......
......@@ -214,6 +214,40 @@ std::string format_hex(const unsigned char *s, size_t len)
return res;
}
void to_token68(std::string& base64str)
{
for(auto i = std::begin(base64str); i != std::end(base64str); ++i) {
switch(*i) {
case '+':
*i = '-';
break;
case '/':
*i = '_';
break;
case '=':
base64str.erase(i, std::end(base64str));
return;
}
}
return;
}
void to_base64(std::string& token68str)
{
for(auto i = std::begin(token68str); i != std::end(token68str); ++i) {
switch(*i) {
case '-':
*i = '+';
break;
case '_':
*i = '/';
break;
}
}
token68str += std::string(4 - token68str.size() % 4, '=');
return;
}
} // namespace util
} // namespace nghttp2
......@@ -375,6 +375,9 @@ make_unique(size_t size)
return std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]());
}
void to_token68(std::string& base64str);
void to_base64(std::string& token68str);
} // namespace util
} // namespace nghttp2
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment