nghttpx: Use :authority and host instead of :host

This commit is contained in:
Tatsuhiro Tsujikawa 2013-10-25 21:50:56 +09:00
parent c4ae19e2a0
commit 67553d47e0
8 changed files with 175 additions and 78 deletions

View File

@ -144,7 +144,6 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen)
namespace { namespace {
const char *DISALLOWED_HD[] = { const char *DISALLOWED_HD[] = {
"connection", "connection",
"host",
"keep-alive", "keep-alive",
"proxy-connection", "proxy-connection",
"te", "te",
@ -161,7 +160,6 @@ namespace {
const char *IGN_HD[] = { const char *IGN_HD[] = {
"connection", "connection",
"expect", "expect",
"host",
"http2-settings", "http2-settings",
"keep-alive", "keep-alive",
"proxy-connection", "proxy-connection",
@ -247,13 +245,19 @@ const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen,
std::string name_to_str(const nghttp2_nv *nv) std::string name_to_str(const nghttp2_nv *nv)
{ {
if(nv) {
return std::string(reinterpret_cast<const char*>(nv->name), nv->namelen); return std::string(reinterpret_cast<const char*>(nv->name), nv->namelen);
} }
return "";
}
std::string value_to_str(const nghttp2_nv *nv) std::string value_to_str(const nghttp2_nv *nv)
{ {
if(nv) {
return std::string(reinterpret_cast<const char*>(nv->value), nv->valuelen); return std::string(reinterpret_cast<const char*>(nv->value), nv->valuelen);
} }
return "";
}
bool value_lws(const nghttp2_nv *nv) bool value_lws(const nghttp2_nv *nv)
{ {
@ -269,6 +273,11 @@ bool value_lws(const nghttp2_nv *nv)
return true; return true;
} }
bool non_empty_value(const nghttp2_nv* nv)
{
return nv && !http2::value_lws(nv) && http2::check_header_value(nv);
}
void copy_norm_headers_to_nv void copy_norm_headers_to_nv
(std::vector<const char*>& nv, (std::vector<const char*>& nv,
const std::vector<std::pair<std::string, std::string>>& headers) const std::vector<std::pair<std::string, std::string>>& headers)

View File

@ -89,6 +89,10 @@ std::string value_to_str(const nghttp2_nv *nv);
// Returns true if the value of |nv| includes only ' ' (0x20) or '\t'. // Returns true if the value of |nv| includes only ' ' (0x20) or '\t'.
bool value_lws(const nghttp2_nv *nv); bool value_lws(const nghttp2_nv *nv);
// Returns true if the value of |nv| is not empty value and not LWS
// and not contain illegal characters.
bool non_empty_value(const nghttp2_nv* nv);
// Appends headers in |headers| to |nv|. Certain headers, including // Appends headers in |headers| to |nv|. Certain headers, including
// disallowed headers in HTTP/2.0 spec and headers which require // disallowed headers in HTTP/2.0 spec and headers which require
// special handling (i.e. via), are not copied. // special handling (i.e. via), are not copied.

View File

@ -249,6 +249,26 @@ const std::string& Downstream::get_request_path() const
return request_path_; return request_path_;
} }
const std::string& Downstream::get_request_http2_scheme() const
{
return request_http2_scheme_;
}
void Downstream::set_request_http2_scheme(std::string scheme)
{
request_http2_scheme_ = std::move(scheme);
}
const std::string& Downstream::get_request_http2_authority() const
{
return request_http2_authority_;
}
void Downstream::set_request_http2_authority(std::string authority)
{
request_http2_authority_ = std::move(authority);
}
void Downstream::set_request_major(int major) void Downstream::set_request_major(int major)
{ {
request_major_ = major; request_major_ = major;

View File

@ -104,7 +104,15 @@ public:
const std::string& get_request_method() const; const std::string& get_request_method() const;
void set_request_path(std::string path); void set_request_path(std::string path);
void append_request_path(const char *data, size_t len); void append_request_path(const char *data, size_t len);
// Returns request path. For HTTP/1.1, this is request-target. For
// HTTP/2, this is :path header field value.
const std::string& get_request_path() const; const std::string& get_request_path() const;
// Returns HTTP/2 :scheme header field value.
const std::string& get_request_http2_scheme() const;
void set_request_http2_scheme(std::string scheme);
// Returns HTTP/2 :authority header field value.
const std::string& get_request_http2_authority() const;
void set_request_http2_authority(std::string authority);
void set_request_major(int major); void set_request_major(int major);
void set_request_minor(int minor); void set_request_minor(int minor);
int get_request_major() const; int get_request_major() const;
@ -184,6 +192,8 @@ private:
int request_state_; int request_state_;
std::string request_method_; std::string request_method_;
std::string request_path_; std::string request_path_;
std::string request_http2_scheme_;
std::string request_http2_authority_;
int request_major_; int request_major_;
int request_minor_; int request_minor_;
bool chunked_request_; bool chunked_request_;

View File

@ -222,23 +222,34 @@ int on_frame_recv_callback
} }
} }
auto host = http2::get_unique_header(nva, nvlen, ":host"); auto host = http2::get_unique_header(nva, nvlen, "host");
auto authority = http2::get_unique_header(nva, nvlen, ":authority");
auto path = http2::get_unique_header(nva, nvlen, ":path"); auto path = http2::get_unique_header(nva, nvlen, ":path");
auto method = http2::get_unique_header(nva, nvlen, ":method"); auto method = http2::get_unique_header(nva, nvlen, ":method");
auto scheme = http2::get_unique_header(nva, nvlen, ":scheme"); auto scheme = http2::get_unique_header(nva, nvlen, ":scheme");
bool is_connect = method && bool is_connect = method &&
util::streq("CONNECT", method->value, method->valuelen); util::streq("CONNECT", method->value, method->valuelen);
if(!host || !path || !method || bool having_host = http2::non_empty_value(host);
http2::value_lws(host) || http2::value_lws(path) || bool having_authority = http2::non_empty_value(authority);
http2::value_lws(method) ||
(!is_connect && (!scheme || http2::value_lws(scheme))) || if(is_connect) {
!http2::check_header_value(host) || // Here we strictly require :authority header field.
!http2::check_header_value(path) || if(scheme || path || !having_authority) {
!http2::check_header_value(method) ||
(scheme && !http2::check_header_value(scheme))) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return 0; return 0;
} }
} else {
// For proxy, :authority is required. Otherwise, we can accept
// :authority or host for methods.
if(!http2::non_empty_value(method) ||
!http2::non_empty_value(scheme) ||
(get_config()->spdy_proxy && !having_authority) ||
(!get_config()->spdy_proxy && !having_authority && !having_host) ||
!http2::non_empty_value(path)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
}
if(!is_connect && if(!is_connect &&
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
auto content_length = http2::get_header(nva, nvlen, "content-length"); auto content_length = http2::get_header(nva, nvlen, "content-length");
@ -251,21 +262,10 @@ int on_frame_recv_callback
} }
downstream->set_request_method(http2::value_to_str(method)); downstream->set_request_method(http2::value_to_str(method));
downstream->set_request_http2_scheme(http2::value_to_str(scheme));
// SpdyDownstreamConnection examines request path to find downstream->set_request_http2_authority(http2::value_to_str(authority));
// scheme. We construct abs URI for spdy_bridge mode as well as
// spdy_proxy mode.
if((get_config()->spdy_proxy || get_config()->spdy_bridge) &&
scheme && path->value[0] == '/') {
auto reqpath = http2::value_to_str(scheme);
reqpath += "://";
reqpath += http2::value_to_str(host);
reqpath += http2::value_to_str(path);
downstream->set_request_path(std::move(reqpath));
} else {
downstream->set_request_path(http2::value_to_str(path)); downstream->set_request_path(http2::value_to_str(path));
}
downstream->add_request_header("host", http2::value_to_str(host));
downstream->check_upgrade_request(); downstream->check_upgrade_request();
auto dconn = upstream->get_client_handler()->get_downstream_connection(); auto dconn = upstream->get_client_handler()->get_downstream_connection();

View File

@ -116,13 +116,39 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream)
int HttpDownstreamConnection::push_request_headers() int HttpDownstreamConnection::push_request_headers()
{ {
downstream_->normalize_request_headers();
auto end_headers = std::end(downstream_->get_request_headers());
// Assume that method and request path do not contain \r\n. // Assume that method and request path do not contain \r\n.
std::string hdrs = downstream_->get_request_method(); std::string hdrs = downstream_->get_request_method();
hdrs += " "; hdrs += " ";
if(downstream_->get_request_method() == "CONNECT") {
if(!downstream_->get_request_http2_authority().empty()) {
hdrs += downstream_->get_request_http2_authority();
} else {
hdrs += downstream_->get_request_path(); hdrs += downstream_->get_request_path();
}
} else if(get_config()->spdy_proxy &&
!downstream_->get_request_http2_scheme().empty() &&
!downstream_->get_request_http2_authority().empty() &&
downstream_->get_request_path().c_str()[0] == '/') {
// Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy.
hdrs += downstream_->get_request_http2_scheme();
hdrs += "://";
hdrs += downstream_->get_request_http2_authority();
hdrs += downstream_->get_request_path();
} else {
// No proxy case. get_request_path() may be absolute-form but we
// don't care.
hdrs += downstream_->get_request_path();
}
hdrs += " HTTP/1.1\r\n"; hdrs += " HTTP/1.1\r\n";
downstream_->normalize_request_headers(); if(downstream_->get_norm_request_header("host") == end_headers &&
auto end_headers = std::end(downstream_->get_request_headers()); !downstream_->get_request_http2_authority().empty()) {
hdrs += "Host: ";
hdrs += downstream_->get_request_http2_authority();
hdrs += "\r\n";
}
http2::build_http1_headers_from_norm_headers http2::build_http1_headers_from_norm_headers
(hdrs, downstream_->get_request_headers()); (hdrs, downstream_->get_request_headers());
@ -147,10 +173,13 @@ int HttpDownstreamConnection::push_request_headers()
} }
if(downstream_->get_request_method() != "CONNECT") { if(downstream_->get_request_method() != "CONNECT") {
hdrs += "X-Forwarded-Proto: "; hdrs += "X-Forwarded-Proto: ";
if(util::istartsWith(downstream_->get_request_path(), "http:")) { if(!downstream_->get_request_http2_scheme().empty()) {
hdrs += "http\r\n"; hdrs += downstream_->get_request_http2_scheme();
} else { hdrs += "\r\n";
} else if(util::istartsWith(downstream_->get_request_path(), "https:")) {
hdrs += "https\r\n"; hdrs += "https\r\n";
} else {
hdrs += "http\r\n";
} }
} }
auto expect = downstream_->get_norm_request_header("expect"); auto expect = downstream_->get_norm_request_header("expect");

View File

@ -232,19 +232,43 @@ int SpdyDownstreamConnection::push_request_headers()
size_t nheader = downstream_->get_request_headers().size(); size_t nheader = downstream_->get_request_headers().size();
downstream_->normalize_request_headers(); downstream_->normalize_request_headers();
auto end_headers = std::end(downstream_->get_request_headers()); auto end_headers = std::end(downstream_->get_request_headers());
// 10 means :method, :scheme, :path and possible via and // 12 means:
// x-forwarded-for header fields. We rename host header field as // 1. :method
// :host. // 2. :scheme
// 3. :path
// 4. :authority (optional)
// 5. via (optional)
// 6. x-forwarded-for (optional)
auto nv = std::vector<const char*>(); auto nv = std::vector<const char*>();
nv.reserve(nheader * 2 + 10 + 1); nv.reserve(nheader * 2 + 10 + 1);
std::string via_value; std::string via_value;
std::string xff_value; std::string xff_value;
std::string scheme, path, query; std::string scheme, authority, path, query;
if(downstream_->get_request_method() == "CONNECT") { if(downstream_->get_request_method() == "CONNECT") {
// No :scheme header field for CONNECT method. // The upstream may be HTTP/2 or HTTP/1
nv.push_back(":authority");
if(!downstream_->get_request_http2_authority().empty()) {
nv.push_back(downstream_->get_request_http2_authority().c_str());
} else {
nv.push_back(downstream_->get_request_path().c_str());
}
} else if(!downstream_->get_request_http2_scheme().empty()) {
// Here the upstream is HTTP/2
nv.push_back(":scheme");
nv.push_back(downstream_->get_request_http2_scheme().c_str());
nv.push_back(":path"); nv.push_back(":path");
nv.push_back(downstream_->get_request_path().c_str()); nv.push_back(downstream_->get_request_path().c_str());
if(!downstream_->get_request_http2_authority().empty()) {
nv.push_back(":authority");
nv.push_back(downstream_->get_request_http2_authority().c_str());
} else if(downstream_->get_norm_request_header("host") == end_headers) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
return -1;
}
} else { } else {
// The upstream is HTTP/1
http_parser_url u; http_parser_url u;
const char *url = downstream_->get_request_path().c_str(); const char *url = downstream_->get_request_path().c_str();
memset(&u, 0, sizeof(u)); memset(&u, 0, sizeof(u));
@ -253,6 +277,7 @@ int SpdyDownstreamConnection::push_request_headers()
0, &u); 0, &u);
if(rv == 0) { if(rv == 0) {
http2::copy_url_component(scheme, &u, UF_SCHEMA, url); http2::copy_url_component(scheme, &u, UF_SCHEMA, url);
http2::copy_url_component(authority, &u, UF_HOST, url);
http2::copy_url_component(path, &u, UF_PATH, url); http2::copy_url_component(path, &u, UF_PATH, url);
http2::copy_url_component(query, &u, UF_QUERY, url); http2::copy_url_component(query, &u, UF_QUERY, url);
if(path.empty()) { if(path.empty()) {
@ -277,6 +302,24 @@ int SpdyDownstreamConnection::push_request_headers()
} else { } else {
nv.push_back(path.c_str()); nv.push_back(path.c_str());
} }
if(!authority.empty()) {
// TODO properly check IPv6 numeric address
if(authority.find(":") != std::string::npos) {
authority = "[" + authority;
authority += "]";
}
if(u.field_set & (1 << UF_PORT)) {
authority += ":";
authority += util::utos(u.port);
}
nv.push_back(":authority");
nv.push_back(authority.c_str());
} else if(downstream_->get_norm_request_header("host") == end_headers) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
return -1;
}
} }
nv.push_back(":method"); nv.push_back(":method");
@ -284,16 +327,6 @@ int SpdyDownstreamConnection::push_request_headers()
http2::copy_norm_headers_to_nv(nv, downstream_->get_request_headers()); http2::copy_norm_headers_to_nv(nv, downstream_->get_request_headers());
auto host = downstream_->get_norm_request_header("host");
if(host == end_headers) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
return -1;
}
nv.push_back(":host");
nv.push_back((*host).second.c_str());
bool content_length = false; bool content_length = false;
if(downstream_->get_norm_request_header("content-length") != end_headers) { if(downstream_->get_norm_request_header("content-length") != end_headers) {
content_length = true; content_length = true;

View File

@ -143,25 +143,24 @@ void on_ctrl_recv_callback
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data) void *user_data)
{ {
SpdyUpstream *upstream = reinterpret_cast<SpdyUpstream*>(user_data); auto upstream = reinterpret_cast<SpdyUpstream*>(user_data);
switch(type) { switch(type) {
case SPDYLAY_SYN_STREAM: { case SPDYLAY_SYN_STREAM: {
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Received upstream SYN_STREAM stream_id=" ULOG(INFO, upstream) << "Received upstream SYN_STREAM stream_id="
<< frame->syn_stream.stream_id; << frame->syn_stream.stream_id;
} }
Downstream *downstream; auto downstream = new Downstream(upstream,
downstream = new Downstream(upstream,
frame->syn_stream.stream_id, frame->syn_stream.stream_id,
frame->syn_stream.pri); frame->syn_stream.pri);
upstream->add_downstream(downstream); upstream->add_downstream(downstream);
downstream->init_response_body_buf(); downstream->init_response_body_buf();
char **nv = frame->syn_stream.nv; auto nv = frame->syn_stream.nv;
const char *path = 0; const char *path = nullptr;
const char *scheme = 0; const char *scheme = nullptr;
const char *host = 0; const char *host = nullptr;
const char *method = 0; const char *method = nullptr;
const char *content_length = 0; const char *content_length = 0;
for(size_t i = 0; nv[i]; i += 2) { for(size_t i = 0; nv[i]; i += 2) {
if(strcmp(nv[i], ":path") == 0) { if(strcmp(nv[i], ":path") == 0) {
@ -170,7 +169,6 @@ void on_ctrl_recv_callback
scheme = nv[i+1]; scheme = nv[i+1];
} else if(strcmp(nv[i], ":method") == 0) { } else if(strcmp(nv[i], ":method") == 0) {
method = nv[i+1]; method = nv[i+1];
downstream->set_request_method(nv[i+1]);
} else if(strcmp(nv[i], ":host") == 0) { } else if(strcmp(nv[i], ":host") == 0) {
host = nv[i+1]; host = nv[i+1];
} else if(nv[i][0] != ':') { } else if(nv[i][0] != ':') {
@ -180,36 +178,31 @@ void on_ctrl_recv_callback
downstream->add_request_header(nv[i], nv[i+1]); downstream->add_request_header(nv[i], nv[i+1]);
} }
} }
bool is_connect = method && strcmp("CONNECT", method) == 0;
if(!path || !host || !method || if(!path || !host || !method ||
!http2::check_header_value(host) || !http2::check_header_value(host) ||
!http2::check_header_value(path) || !http2::check_header_value(path) ||
!http2::check_header_value(method) || !http2::check_header_value(method) ||
(scheme && !http2::check_header_value(scheme))) { (!is_connect && (!scheme || !http2::check_header_value(scheme)))) {
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
return; return;
} }
// Require content-length if FIN flag is not set. // Require content-length if FIN flag is not set.
if(strcmp("CONNECT", method) != 0 && if(!is_connect && !content_length &&
(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0 && (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0) {
!content_length) {
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
return; return;
} }
// SpdyDownstreamConnection examines request path to find
// scheme. We construct abs URI for spdy_bridge mode as well as downstream->set_request_method(method);
// spdy_proxy mode. if(is_connect) {
if((get_config()->spdy_proxy || get_config()->spdy_bridge) && downstream->set_request_http2_authority(path);
scheme && path[0] == '/') {
std::string reqpath = scheme;
reqpath += "://";
reqpath += host;
reqpath += path;
downstream->set_request_path(std::move(reqpath));
} else { } else {
downstream->set_request_http2_scheme(scheme);
downstream->set_request_http2_authority(host);
downstream->set_request_path(path); downstream->set_request_path(path);
} }
downstream->add_request_header("host", host);
downstream->check_upgrade_request(); downstream->check_upgrade_request();
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
@ -222,8 +215,7 @@ void on_ctrl_recv_callback
<< "\n" << ss.str(); << "\n" << ss.str();
} }
DownstreamConnection *dconn; auto dconn = upstream->get_client_handler()->get_downstream_connection();
dconn = upstream->get_client_handler()->get_downstream_connection();
int rv = dconn->attach_downstream(downstream); int rv = dconn->attach_downstream(downstream);
if(rv != 0) { if(rv != 0) {
// If downstream connection fails, issue RST_STREAM. // If downstream connection fails, issue RST_STREAM.