nghttpx: Implement RFC 8441 Bootstrapping WebSocket with HTTP/2
This commit is contained in:
parent
651e147711
commit
d2a594a753
|
@ -9,6 +9,7 @@ HEADERS = [
|
||||||
':scheme',
|
':scheme',
|
||||||
':status',
|
':status',
|
||||||
':host', # for spdy
|
':host', # for spdy
|
||||||
|
':protocol',
|
||||||
'expect',
|
'expect',
|
||||||
'host',
|
'host',
|
||||||
'if-modified-since',
|
'if-modified-since',
|
||||||
|
@ -32,6 +33,8 @@ HEADERS = [
|
||||||
"date",
|
"date",
|
||||||
"content-type",
|
"content-type",
|
||||||
"early-data",
|
"early-data",
|
||||||
|
"sec-websocket-accept",
|
||||||
|
"sec-websocket-key",
|
||||||
# disallowed h1 headers
|
# disallowed h1 headers
|
||||||
'connection',
|
'connection',
|
||||||
'keep-alive',
|
'keep-alive',
|
||||||
|
|
78
src/base64.h
78
src/base64.h
|
@ -36,14 +36,17 @@ namespace nghttp2 {
|
||||||
|
|
||||||
namespace base64 {
|
namespace base64 {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr char B64_CHARS[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||||
|
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||||
|
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
template <typename InputIt> std::string encode(InputIt first, InputIt last) {
|
template <typename InputIt> std::string encode(InputIt first, InputIt last) {
|
||||||
static constexpr char CHAR_TABLE[] = {
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
|
||||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
|
||||||
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
|
|
||||||
};
|
|
||||||
std::string res;
|
std::string res;
|
||||||
size_t len = last - first;
|
size_t len = last - first;
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
|
@ -57,29 +60,72 @@ template <typename InputIt> std::string encode(InputIt first, InputIt last) {
|
||||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
n += static_cast<uint8_t>(*first++) << 8;
|
n += static_cast<uint8_t>(*first++) << 8;
|
||||||
n += static_cast<uint8_t>(*first++);
|
n += static_cast<uint8_t>(*first++);
|
||||||
*p++ = CHAR_TABLE[n >> 18];
|
*p++ = B64_CHARS[n >> 18];
|
||||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
|
*p++ = B64_CHARS[(n >> 6) & 0x3fu];
|
||||||
*p++ = CHAR_TABLE[n & 0x3fu];
|
*p++ = B64_CHARS[n & 0x3fu];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r == 2) {
|
if (r == 2) {
|
||||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
n += static_cast<uint8_t>(*first++) << 8;
|
n += static_cast<uint8_t>(*first++) << 8;
|
||||||
*p++ = CHAR_TABLE[n >> 18];
|
*p++ = B64_CHARS[n >> 18];
|
||||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
|
*p++ = B64_CHARS[(n >> 6) & 0x3fu];
|
||||||
*p++ = '=';
|
*p++ = '=';
|
||||||
} else if (r == 1) {
|
} else if (r == 1) {
|
||||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
*p++ = CHAR_TABLE[n >> 18];
|
*p++ = B64_CHARS[n >> 18];
|
||||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
*p++ = '=';
|
*p++ = '=';
|
||||||
*p++ = '=';
|
*p++ = '=';
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr size_t encode_length(size_t n) { return (n + 2) / 3 * 4; }
|
||||||
|
|
||||||
|
template <typename InputIt, typename OutputIt>
|
||||||
|
OutputIt encode(InputIt first, InputIt last, OutputIt d_first) {
|
||||||
|
size_t len = last - first;
|
||||||
|
if (len == 0) {
|
||||||
|
return d_first;
|
||||||
|
}
|
||||||
|
auto r = len % 3;
|
||||||
|
auto j = last - r;
|
||||||
|
auto p = d_first;
|
||||||
|
while (first != j) {
|
||||||
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
|
n += static_cast<uint8_t>(*first++) << 8;
|
||||||
|
n += static_cast<uint8_t>(*first++);
|
||||||
|
*p++ = B64_CHARS[n >> 18];
|
||||||
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
|
*p++ = B64_CHARS[(n >> 6) & 0x3fu];
|
||||||
|
*p++ = B64_CHARS[n & 0x3fu];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case 2: {
|
||||||
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
|
n += static_cast<uint8_t>(*first++) << 8;
|
||||||
|
*p++ = B64_CHARS[n >> 18];
|
||||||
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
|
*p++ = B64_CHARS[(n >> 6) & 0x3fu];
|
||||||
|
*p++ = '=';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||||
|
*p++ = B64_CHARS[n >> 18];
|
||||||
|
*p++ = B64_CHARS[(n >> 12) & 0x3fu];
|
||||||
|
*p++ = '=';
|
||||||
|
*p++ = '=';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename InputIt>
|
template <typename InputIt>
|
||||||
InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) {
|
InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) {
|
||||||
for (; first != last; ++first) {
|
for (; first != last; ++first) {
|
||||||
|
|
47
src/http2.cc
47
src/http2.cc
|
@ -394,6 +394,16 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case HD_SEC_WEBSOCKET_ACCEPT:
|
||||||
|
if (flags & HDOP_STRIP_SEC_WEBSOCKET_ACCEPT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HD_SEC_WEBSOCKET_KEY:
|
||||||
|
if (flags & HDOP_STRIP_SEC_WEBSOCKET_KEY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case HD_FORWARDED:
|
case HD_FORWARDED:
|
||||||
if (flags & HDOP_STRIP_FORWARDED) {
|
if (flags & HDOP_STRIP_FORWARDED) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -834,6 +844,11 @@ int lookup_token(const uint8_t *name, size_t namelen) {
|
||||||
return HD_FORWARDED;
|
return HD_FORWARDED;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'l':
|
||||||
|
if (util::streq_l(":protoco", name, 8)) {
|
||||||
|
return HD__PROTOCOL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 10:
|
case 10:
|
||||||
|
@ -942,6 +957,20 @@ int lookup_token(const uint8_t *name, size_t namelen) {
|
||||||
return HD_X_FORWARDED_PROTO;
|
return HD_X_FORWARDED_PROTO;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'y':
|
||||||
|
if (util::streq_l("sec-websocket-ke", name, 16)) {
|
||||||
|
return HD_SEC_WEBSOCKET_KEY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
switch (name[19]) {
|
||||||
|
case 't':
|
||||||
|
if (util::streq_l("sec-websocket-accep", name, 19)) {
|
||||||
|
return HD_SEC_WEBSOCKET_ACCEPT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1331,7 +1360,8 @@ std::string path_join(const StringRef &base_path, const StringRef &base_query,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool expect_response_body(int status_code) {
|
bool expect_response_body(int status_code) {
|
||||||
return status_code / 100 != 1 && status_code != 304 && status_code != 204;
|
return status_code == 101 ||
|
||||||
|
(status_code / 100 != 1 && status_code != 304 && status_code != 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool expect_response_body(const std::string &method, int status_code) {
|
bool expect_response_body(const std::string &method, int status_code) {
|
||||||
|
@ -1829,6 +1859,21 @@ bool contains_trailers(const StringRef &s) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key) {
|
||||||
|
static constexpr uint8_t magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
std::array<uint8_t, base64::encode_length(16) + str_size(magic)> s;
|
||||||
|
auto p = std::copy(std::begin(key), std::end(key), std::begin(s));
|
||||||
|
std::copy_n(magic, str_size(magic), p);
|
||||||
|
|
||||||
|
std::array<uint8_t, 20> h;
|
||||||
|
if (util::sha1(h.data(), StringRef{std::begin(s), std::end(s)}) != 0) {
|
||||||
|
return StringRef{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end = base64::encode(std::begin(h), std::end(h), dest);
|
||||||
|
return StringRef{dest, end};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
16
src/http2.h
16
src/http2.h
|
@ -41,6 +41,7 @@
|
||||||
#include "memchunk.h"
|
#include "memchunk.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
#include "allocator.h"
|
#include "allocator.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
namespace nghttp2 {
|
namespace nghttp2 {
|
||||||
|
|
||||||
|
@ -210,6 +211,12 @@ enum HeaderBuildOp {
|
||||||
HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
|
HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
|
||||||
HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA |
|
HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA |
|
||||||
HDOP_STRIP_EARLY_DATA,
|
HDOP_STRIP_EARLY_DATA,
|
||||||
|
// Sec-WebSocket-Accept header field must be stripped. If this flag
|
||||||
|
// is not set, all Sec-WebSocket-Accept header fields are added.
|
||||||
|
HDOP_STRIP_SEC_WEBSOCKET_ACCEPT = 1 << 5,
|
||||||
|
// Sec-WebSocket-Key header field must be stripped. If this flag is
|
||||||
|
// not set, all Sec-WebSocket-Key header fields are added.
|
||||||
|
HDOP_STRIP_SEC_WEBSOCKET_KEY = 1 << 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Appends headers in |headers| to |nv|. |headers| must be indexed
|
// Appends headers in |headers| to |nv|. |headers| must be indexed
|
||||||
|
@ -297,6 +304,7 @@ enum {
|
||||||
HD__HOST,
|
HD__HOST,
|
||||||
HD__METHOD,
|
HD__METHOD,
|
||||||
HD__PATH,
|
HD__PATH,
|
||||||
|
HD__PROTOCOL,
|
||||||
HD__SCHEME,
|
HD__SCHEME,
|
||||||
HD__STATUS,
|
HD__STATUS,
|
||||||
HD_ACCEPT_ENCODING,
|
HD_ACCEPT_ENCODING,
|
||||||
|
@ -318,6 +326,8 @@ enum {
|
||||||
HD_LINK,
|
HD_LINK,
|
||||||
HD_LOCATION,
|
HD_LOCATION,
|
||||||
HD_PROXY_CONNECTION,
|
HD_PROXY_CONNECTION,
|
||||||
|
HD_SEC_WEBSOCKET_ACCEPT,
|
||||||
|
HD_SEC_WEBSOCKET_KEY,
|
||||||
HD_SERVER,
|
HD_SERVER,
|
||||||
HD_TE,
|
HD_TE,
|
||||||
HD_TRAILER,
|
HD_TRAILER,
|
||||||
|
@ -421,6 +431,12 @@ StringRef copy_lower(BlockAllocator &balloc, const StringRef &src);
|
||||||
// Returns true if te header field value |s| contains "trailers".
|
// Returns true if te header field value |s| contains "trailers".
|
||||||
bool contains_trailers(const StringRef &s);
|
bool contains_trailers(const StringRef &s);
|
||||||
|
|
||||||
|
// Creates Sec-WebSocket-Accept value for |key|. The capacity of
|
||||||
|
// buffer pointed by |dest| must have at least 24 bytes (base64
|
||||||
|
// encoded length of 16 bytes data). It returns empty string in case
|
||||||
|
// of error.
|
||||||
|
StringRef make_websocket_accept_token(uint8_t *dest, const StringRef &key);
|
||||||
|
|
||||||
} // namespace http2
|
} // namespace http2
|
||||||
|
|
||||||
} // namespace nghttp2
|
} // namespace nghttp2
|
||||||
|
|
|
@ -979,7 +979,7 @@ ClientHandler::get_downstream_connection(int &err, Downstream *downstream,
|
||||||
StringRef path;
|
StringRef path;
|
||||||
// CONNECT method does not have path. But we requires path in
|
// CONNECT method does not have path. But we requires path in
|
||||||
// host-path mapping. As workaround, we assume that path is "/".
|
// host-path mapping. As workaround, we assume that path is "/".
|
||||||
if (req.method != HTTP_CONNECT) {
|
if (!req.regular_connect_method()) {
|
||||||
path = req.path;
|
path = req.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -763,13 +763,47 @@ bool Downstream::validate_response_recv_body_length() const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Downstream::check_upgrade_fulfilled() {
|
void Downstream::check_upgrade_fulfilled_http2() {
|
||||||
if (req_.method == HTTP_CONNECT) {
|
if (req_.method == HTTP_CONNECT) {
|
||||||
|
// This handles nonzero req_.connect_proto as well.
|
||||||
upgraded_ = 200 <= resp_.http_status && resp_.http_status < 300;
|
upgraded_ = 200 <= resp_.http_status && resp_.http_status < 300;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (req_.connect_proto == CONNECT_PROTO_WEBSOCKET) {
|
||||||
|
// h1 frontend requests WebSocket upgrade
|
||||||
|
upgraded_ = resp_.http_status == 200;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Downstream::check_upgrade_fulfilled_http1() {
|
||||||
|
if (req_.method == HTTP_CONNECT) {
|
||||||
|
if (req_.connect_proto == CONNECT_PROTO_WEBSOCKET) {
|
||||||
|
if (resp_.http_status != 101) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is done for HTTP/2 frontend only.
|
||||||
|
auto accept = resp_.fs.header(http2::HD_SEC_WEBSOCKET_ACCEPT);
|
||||||
|
if (!accept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<uint8_t, base64::encode_length(20)> accept_buf;
|
||||||
|
auto expected =
|
||||||
|
http2::make_websocket_accept_token(accept_buf.data(), ws_key_);
|
||||||
|
|
||||||
|
upgraded_ = expected != "" && expected == accept->value;
|
||||||
|
} else {
|
||||||
|
upgraded_ = 200 <= resp_.http_status && resp_.http_status < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (resp_.http_status == 101) {
|
if (resp_.http_status == 101) {
|
||||||
// TODO Do more strict checking for upgrade headers
|
// TODO Do more strict checking for upgrade headers
|
||||||
upgraded_ = req_.upgrade_request;
|
upgraded_ = req_.upgrade_request;
|
||||||
|
@ -787,7 +821,7 @@ void Downstream::inspect_http2_request() {
|
||||||
void Downstream::inspect_http1_request() {
|
void Downstream::inspect_http1_request() {
|
||||||
if (req_.method == HTTP_CONNECT) {
|
if (req_.method == HTTP_CONNECT) {
|
||||||
req_.upgrade_request = true;
|
req_.upgrade_request = true;
|
||||||
} else {
|
} else if (req_.http_minor > 0) {
|
||||||
auto upgrade = req_.fs.header(http2::HD_UPGRADE);
|
auto upgrade = req_.fs.header(http2::HD_UPGRADE);
|
||||||
if (upgrade) {
|
if (upgrade) {
|
||||||
const auto &val = upgrade->value;
|
const auto &val = upgrade->value;
|
||||||
|
@ -797,6 +831,12 @@ void Downstream::inspect_http1_request() {
|
||||||
req_.http2_upgrade_seen = true;
|
req_.http2_upgrade_seen = true;
|
||||||
} else {
|
} else {
|
||||||
req_.upgrade_request = true;
|
req_.upgrade_request = true;
|
||||||
|
|
||||||
|
// TODO Should we check Sec-WebSocket-Key, and
|
||||||
|
// Sec-WebSocket-Version as well?
|
||||||
|
if (util::strieq_l("websocket", val)) {
|
||||||
|
req_.connect_proto = CONNECT_PROTO_WEBSOCKET;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1103,4 +1143,6 @@ bool Downstream::get_blocked_request_data_eof() const {
|
||||||
return blocked_request_data_eof_;
|
return blocked_request_data_eof_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downstream::set_ws_key(const StringRef &key) { ws_key_ = key; }
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -134,6 +134,12 @@ private:
|
||||||
bool trailer_key_prev_;
|
bool trailer_key_prev_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Protocols allowed in HTTP/2 :protocol header field.
|
||||||
|
enum shrpx_connect_proto {
|
||||||
|
CONNECT_PROTO_NONE,
|
||||||
|
CONNECT_PROTO_WEBSOCKET,
|
||||||
|
};
|
||||||
|
|
||||||
struct Request {
|
struct Request {
|
||||||
Request(BlockAllocator &balloc)
|
Request(BlockAllocator &balloc)
|
||||||
: fs(balloc, 16),
|
: fs(balloc, 16),
|
||||||
|
@ -142,6 +148,7 @@ struct Request {
|
||||||
method(-1),
|
method(-1),
|
||||||
http_major(1),
|
http_major(1),
|
||||||
http_minor(1),
|
http_minor(1),
|
||||||
|
connect_proto(CONNECT_PROTO_NONE),
|
||||||
upgrade_request(false),
|
upgrade_request(false),
|
||||||
http2_upgrade_seen(false),
|
http2_upgrade_seen(false),
|
||||||
connection_close(false),
|
connection_close(false),
|
||||||
|
@ -153,6 +160,12 @@ struct Request {
|
||||||
unconsumed_body_length -= len;
|
unconsumed_body_length -= len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool regular_connect_method() const {
|
||||||
|
return method == HTTP_CONNECT && !connect_proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool extended_connect_method() const { return connect_proto; }
|
||||||
|
|
||||||
FieldStore fs;
|
FieldStore fs;
|
||||||
// Timestamp when all request header fields are received.
|
// Timestamp when all request header fields are received.
|
||||||
std::shared_ptr<Timestamp> tstamp;
|
std::shared_ptr<Timestamp> tstamp;
|
||||||
|
@ -176,6 +189,10 @@ struct Request {
|
||||||
int method;
|
int method;
|
||||||
// HTTP major and minor version
|
// HTTP major and minor version
|
||||||
int http_major, http_minor;
|
int http_major, http_minor;
|
||||||
|
// connect_proto specified in HTTP/2 :protocol pseudo header field
|
||||||
|
// which enables extended CONNECT method. This field is also set if
|
||||||
|
// WebSocket upgrade is requested in h1 frontend for convenience.
|
||||||
|
int connect_proto;
|
||||||
// Returns true if the request is HTTP upgrade (HTTP Upgrade or
|
// Returns true if the request is HTTP upgrade (HTTP Upgrade or
|
||||||
// CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2
|
// CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2
|
||||||
// Upgrade, check get_http2_upgrade_request().
|
// Upgrade, check get_http2_upgrade_request().
|
||||||
|
@ -283,11 +300,14 @@ public:
|
||||||
// Returns true if output buffer is full. If underlying dconn_ is
|
// Returns true if output buffer is full. If underlying dconn_ is
|
||||||
// NULL, this function always returns false.
|
// NULL, this function always returns false.
|
||||||
bool request_buf_full();
|
bool request_buf_full();
|
||||||
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
|
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
|
||||||
// This should not depend on inspect_http1_response().
|
// h1 backend. This should not depend on inspect_http1_response().
|
||||||
void check_upgrade_fulfilled();
|
void check_upgrade_fulfilled_http1();
|
||||||
|
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded in
|
||||||
|
// h2 backend.
|
||||||
|
void check_upgrade_fulfilled_http2();
|
||||||
// Returns true if the upgrade is succeeded as a result of the call
|
// Returns true if the upgrade is succeeded as a result of the call
|
||||||
// check_upgrade_fulfilled(). HTTP/2 Upgrade is excluded.
|
// check_upgrade_fulfilled_http*(). HTTP/2 Upgrade is excluded.
|
||||||
bool get_upgraded() const;
|
bool get_upgraded() const;
|
||||||
// Inspects HTTP/2 request.
|
// Inspects HTTP/2 request.
|
||||||
void inspect_http2_request();
|
void inspect_http2_request();
|
||||||
|
@ -461,6 +481,8 @@ public:
|
||||||
// field, returns 0.
|
// field, returns 0.
|
||||||
uint32_t get_affinity_cookie_to_send() const;
|
uint32_t get_affinity_cookie_to_send() const;
|
||||||
|
|
||||||
|
void set_ws_key(const StringRef &key);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
EVENT_ERROR = 0x1,
|
EVENT_ERROR = 0x1,
|
||||||
EVENT_TIMEOUT = 0x2,
|
EVENT_TIMEOUT = 0x2,
|
||||||
|
@ -500,6 +522,10 @@ private:
|
||||||
DefaultMemchunks request_buf_;
|
DefaultMemchunks request_buf_;
|
||||||
DefaultMemchunks response_buf_;
|
DefaultMemchunks response_buf_;
|
||||||
|
|
||||||
|
// The Sec-WebSocket-Key field sent to the peer. This field is used
|
||||||
|
// if frontend uses RFC 8441 WebSocket bootstrapping via HTTP/2.
|
||||||
|
StringRef ws_key_;
|
||||||
|
|
||||||
ev_timer upstream_rtimer_;
|
ev_timer upstream_rtimer_;
|
||||||
ev_timer upstream_wtimer_;
|
ev_timer upstream_wtimer_;
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,7 @@ int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||||
auto &req = downstream_->request();
|
auto &req = downstream_->request();
|
||||||
|
|
||||||
// HTTP/2 disables HTTP Upgrade.
|
// HTTP/2 disables HTTP Upgrade.
|
||||||
if (req.method != HTTP_CONNECT) {
|
if (req.method != HTTP_CONNECT && !req.connect_proto) {
|
||||||
req.upgrade_request = false;
|
req.upgrade_request = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
if (!downstream_) {
|
if (!downstream_) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!http2session_->can_push_request()) {
|
if (!http2session_->can_push_request(downstream_)) {
|
||||||
// The HTTP2 session to the backend has not been established or
|
// The HTTP2 session to the backend has not been established or
|
||||||
// connection is now being checked. This function will be called
|
// connection is now being checked. This function will be called
|
||||||
// again just after it is established.
|
// again just after it is established.
|
||||||
|
@ -244,6 +244,10 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
const auto &req = downstream_->request();
|
const auto &req = downstream_->request();
|
||||||
|
|
||||||
|
if (req.connect_proto && !http2session_->get_allow_connect_proto()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
auto &balloc = downstream_->get_block_allocator();
|
auto &balloc = downstream_->get_block_allocator();
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
|
@ -251,7 +255,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
auto &http2conf = config->http2;
|
auto &http2conf = config->http2;
|
||||||
|
|
||||||
auto no_host_rewrite = httpconf.no_host_rewrite || config->http2_proxy ||
|
auto no_host_rewrite = httpconf.no_host_rewrite || config->http2_proxy ||
|
||||||
req.method == HTTP_CONNECT;
|
req.regular_connect_method();
|
||||||
|
|
||||||
// http2session_ has already in CONNECTED state, so we can get
|
// http2session_ has already in CONNECTED state, so we can get
|
||||||
// addr_idx here.
|
// addr_idx here.
|
||||||
|
@ -272,25 +276,31 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
num_cookies = downstream_->count_crumble_request_cookie();
|
num_cookies = downstream_->count_crumble_request_cookie();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10 means:
|
// 11 means:
|
||||||
// 1. :method
|
// 1. :method
|
||||||
// 2. :scheme
|
// 2. :scheme
|
||||||
// 3. :path
|
// 3. :path
|
||||||
// 4. :authority (or host)
|
// 4. :authority (or host)
|
||||||
// 5. via (optional)
|
// 5. :protocol (optional)
|
||||||
// 6. x-forwarded-for (optional)
|
// 6. via (optional)
|
||||||
// 7. x-forwarded-proto (optional)
|
// 7. x-forwarded-for (optional)
|
||||||
// 8. te (optional)
|
// 8. x-forwarded-proto (optional)
|
||||||
// 9. forwarded (optional)
|
// 9. te (optional)
|
||||||
// 10. early-data (optional)
|
// 10. forwarded (optional)
|
||||||
|
// 11. early-data (optional)
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
nva.reserve(req.fs.headers().size() + 10 + num_cookies +
|
nva.reserve(req.fs.headers().size() + 11 + num_cookies +
|
||||||
httpconf.add_request_headers.size());
|
httpconf.add_request_headers.size());
|
||||||
|
|
||||||
nva.push_back(
|
if (req.connect_proto == CONNECT_PROTO_WEBSOCKET) {
|
||||||
http2::make_nv_ls_nocopy(":method", http2::to_method_string(req.method)));
|
nva.push_back(http2::make_nv_ll(":method", "CONNECT"));
|
||||||
|
nva.push_back(http2::make_nv_ll(":protocol", "websocket"));
|
||||||
|
} else {
|
||||||
|
nva.push_back(http2::make_nv_ls_nocopy(
|
||||||
|
":method", http2::to_method_string(req.method)));
|
||||||
|
}
|
||||||
|
|
||||||
if (req.method != HTTP_CONNECT) {
|
if (!req.regular_connect_method()) {
|
||||||
assert(!req.scheme.empty());
|
assert(!req.scheme.empty());
|
||||||
|
|
||||||
auto addr = http2session_->get_addr();
|
auto addr = http2session_->get_addr();
|
||||||
|
@ -308,7 +318,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
|
nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.no_authority) {
|
if (!req.no_authority || req.connect_proto) {
|
||||||
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
|
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
|
||||||
} else {
|
} else {
|
||||||
nva.push_back(http2::make_nv_ls_nocopy("host", authority));
|
nva.push_back(http2::make_nv_ls_nocopy("host", authority));
|
||||||
|
@ -326,7 +336,8 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
|
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
|
||||||
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
|
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
|
||||||
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
|
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
|
||||||
(earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0);
|
(earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
|
||||||
|
http2::HDOP_STRIP_SEC_WEBSOCKET_KEY;
|
||||||
|
|
||||||
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
|
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
|
||||||
|
|
||||||
|
@ -351,7 +362,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
if (fwdconf.params) {
|
if (fwdconf.params) {
|
||||||
auto params = fwdconf.params;
|
auto params = fwdconf.params;
|
||||||
|
|
||||||
if (config->http2_proxy || req.method == HTTP_CONNECT) {
|
if (config->http2_proxy || req.regular_connect_method()) {
|
||||||
params &= ~FORWARDED_PROTO;
|
params &= ~FORWARDED_PROTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,7 +403,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
|
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config->http2_proxy && req.method != HTTP_CONNECT) {
|
if (!config->http2_proxy && !req.regular_connect_method()) {
|
||||||
auto xfp = xfpconf.strip_incoming
|
auto xfp = xfpconf.strip_incoming
|
||||||
? nullptr
|
? nullptr
|
||||||
: req.fs.header(http2::HD_X_FORWARDED_PROTO);
|
: req.fs.header(http2::HD_X_FORWARDED_PROTO);
|
||||||
|
@ -464,7 +475,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
// Add body as long as transfer-encoding is given even if
|
// Add body as long as transfer-encoding is given even if
|
||||||
// req.fs.content_length == 0 to forward trailer fields.
|
// req.fs.content_length == 0 to forward trailer fields.
|
||||||
if (req.method == HTTP_CONNECT || transfer_encoding ||
|
if (req.method == HTTP_CONNECT || req.connect_proto || transfer_encoding ||
|
||||||
req.fs.content_length > 0 || req.http2_expect_body) {
|
req.fs.content_length > 0 || req.http2_expect_body) {
|
||||||
// Request-body is expected.
|
// Request-body is expected.
|
||||||
data_prd = {{}, http2_data_read_callback};
|
data_prd = {{}, http2_data_read_callback};
|
||||||
|
|
|
@ -199,7 +199,9 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
|
||||||
raddr_(nullptr),
|
raddr_(nullptr),
|
||||||
state_(DISCONNECTED),
|
state_(DISCONNECTED),
|
||||||
connection_check_state_(CONNECTION_CHECK_NONE),
|
connection_check_state_(CONNECTION_CHECK_NONE),
|
||||||
freelist_zone_(FREELIST_ZONE_NONE) {
|
freelist_zone_(FREELIST_ZONE_NONE),
|
||||||
|
settings_recved_(false),
|
||||||
|
allow_connect_proto_(false) {
|
||||||
read_ = write_ = &Http2Session::noop;
|
read_ = write_ = &Http2Session::noop;
|
||||||
|
|
||||||
on_read_ = &Http2Session::read_noop;
|
on_read_ = &Http2Session::read_noop;
|
||||||
|
@ -1141,7 +1143,7 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
||||||
}
|
}
|
||||||
|
|
||||||
downstream->set_response_state(Downstream::HEADER_COMPLETE);
|
downstream->set_response_state(Downstream::HEADER_COMPLETE);
|
||||||
downstream->check_upgrade_fulfilled();
|
downstream->check_upgrade_fulfilled_http2();
|
||||||
|
|
||||||
if (downstream->get_upgraded()) {
|
if (downstream->get_upgraded()) {
|
||||||
resp.connection_close = true;
|
resp.connection_close = true;
|
||||||
|
@ -1308,6 +1310,7 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
}
|
}
|
||||||
case NGHTTP2_SETTINGS: {
|
case NGHTTP2_SETTINGS: {
|
||||||
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
||||||
|
http2session->on_settings_received(frame);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1769,7 +1772,6 @@ int Http2Session::downstream_write() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const uint8_t *data;
|
const uint8_t *data;
|
||||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||||
|
|
||||||
if (datalen < 0) {
|
if (datalen < 0) {
|
||||||
SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
|
SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
|
||||||
<< nghttp2_strerror(datalen);
|
<< nghttp2_strerror(datalen);
|
||||||
|
@ -1856,9 +1858,11 @@ int Http2Session::consume(int32_t stream_id, size_t len) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Http2Session::can_push_request() const {
|
bool Http2Session::can_push_request(const Downstream *downstream) const {
|
||||||
|
auto &req = downstream->request();
|
||||||
return state_ == CONNECTED &&
|
return state_ == CONNECTED &&
|
||||||
connection_check_state_ == CONNECTION_CHECK_NONE;
|
connection_check_state_ == CONNECTION_CHECK_NONE &&
|
||||||
|
(!req.connect_proto || settings_recved_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Session::start_checking_connection() {
|
void Http2Session::start_checking_connection() {
|
||||||
|
@ -1917,6 +1921,11 @@ void Http2Session::submit_pending_requests() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto &req = downstream->request();
|
||||||
|
if (req.connect_proto && !settings_recved_) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto upstream = downstream->get_upstream();
|
auto upstream = downstream->get_upstream();
|
||||||
|
|
||||||
if (dconn->push_request_headers() != 0) {
|
if (dconn->push_request_headers() != 0) {
|
||||||
|
@ -2405,4 +2414,28 @@ void Http2Session::check_retire() {
|
||||||
|
|
||||||
const Address *Http2Session::get_raddr() const { return raddr_; }
|
const Address *Http2Session::get_raddr() const { return raddr_; }
|
||||||
|
|
||||||
|
void Http2Session::on_settings_received(const nghttp2_frame *frame) {
|
||||||
|
// TODO This effectively disallows nghttpx to change its behaviour
|
||||||
|
// based on the 2nd SETTINGS.
|
||||||
|
if (settings_recved_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
settings_recved_ = true;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < frame->settings.niv; ++i) {
|
||||||
|
auto &ent = frame->settings.iv[i];
|
||||||
|
if (ent.settings_id == NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL) {
|
||||||
|
allow_connect_proto_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit_pending_requests();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Http2Session::get_allow_connect_proto() const {
|
||||||
|
return allow_connect_proto_;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -146,7 +146,7 @@ public:
|
||||||
int consume(int32_t stream_id, size_t len);
|
int consume(int32_t stream_id, size_t len);
|
||||||
|
|
||||||
// Returns true if request can be issued on downstream connection.
|
// Returns true if request can be issued on downstream connection.
|
||||||
bool can_push_request() const;
|
bool can_push_request(const Downstream *downstream) const;
|
||||||
// Initiates the connection checking if downstream connection has
|
// Initiates the connection checking if downstream connection has
|
||||||
// been established and connection checking is required.
|
// been established and connection checking is required.
|
||||||
void start_checking_connection();
|
void start_checking_connection();
|
||||||
|
@ -212,6 +212,12 @@ public:
|
||||||
// Returns address used to connect to backend. Could be nullptr.
|
// Returns address used to connect to backend. Could be nullptr.
|
||||||
const Address *get_raddr() const;
|
const Address *get_raddr() const;
|
||||||
|
|
||||||
|
// This is called when SETTINGS frame without ACK flag set is
|
||||||
|
// received.
|
||||||
|
void on_settings_received(const nghttp2_frame *frame);
|
||||||
|
|
||||||
|
bool get_allow_connect_proto() const;
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
// Disconnected
|
// Disconnected
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
|
@ -280,6 +286,10 @@ private:
|
||||||
int state_;
|
int state_;
|
||||||
int connection_check_state_;
|
int connection_check_state_;
|
||||||
int freelist_zone_;
|
int freelist_zone_;
|
||||||
|
// true if SETTINGS without ACK is received from peer.
|
||||||
|
bool settings_recved_;
|
||||||
|
// true if peer enables RFC 8441 CONNECT protocol.
|
||||||
|
bool allow_connect_proto_;
|
||||||
};
|
};
|
||||||
|
|
||||||
nghttp2_session_callbacks *create_http2_downstream_callbacks();
|
nghttp2_session_callbacks *create_http2_downstream_callbacks();
|
||||||
|
|
|
@ -391,6 +391,17 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
|
||||||
|
if (connect_proto) {
|
||||||
|
if (connect_proto->value != "websocket") {
|
||||||
|
if (error_reply(downstream, 400) != 0) {
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
req.connect_proto = CONNECT_PROTO_WEBSOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
||||||
req.http2_expect_body = true;
|
req.http2_expect_body = true;
|
||||||
} else if (req.fs.content_length == -1) {
|
} else if (req.fs.content_length == -1) {
|
||||||
|
@ -1025,7 +1036,7 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
|
||||||
flow_control_ = true;
|
flow_control_ = true;
|
||||||
|
|
||||||
// TODO Maybe call from outside?
|
// TODO Maybe call from outside?
|
||||||
std::array<nghttp2_settings_entry, 3> entry;
|
std::array<nghttp2_settings_entry, 4> entry;
|
||||||
size_t nentry = 2;
|
size_t nentry = 2;
|
||||||
|
|
||||||
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||||
|
@ -1038,6 +1049,12 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
|
||||||
entry[1].value = http2conf.upstream.window_size;
|
entry[1].value = http2conf.upstream.window_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config->http2_proxy) {
|
||||||
|
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
|
||||||
|
entry[nentry].value = 1;
|
||||||
|
++nentry;
|
||||||
|
}
|
||||||
|
|
||||||
if (http2conf.upstream.decoder_dynamic_table_size !=
|
if (http2conf.upstream.decoder_dynamic_table_size !=
|
||||||
NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
|
NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
|
||||||
entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
|
entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
|
||||||
|
@ -1704,11 +1721,11 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
nva.reserve(resp.fs.headers().size() + 5 +
|
nva.reserve(resp.fs.headers().size() + 5 +
|
||||||
httpconf.add_response_headers.size());
|
httpconf.add_response_headers.size());
|
||||||
|
|
||||||
auto response_status = http2::stringify_status(balloc, resp.http_status);
|
|
||||||
|
|
||||||
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
|
auto response_status = http2::stringify_status(balloc, resp.http_status);
|
||||||
|
|
||||||
|
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
||||||
|
|
||||||
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
|
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
|
||||||
http2::HDOP_STRIP_ALL);
|
http2::HDOP_STRIP_ALL);
|
||||||
|
|
||||||
|
@ -1730,8 +1747,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
http2::copy_headers_to_nva_nocopy(
|
auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
|
||||||
nva, resp.fs.headers(), http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA);
|
StringRef response_status;
|
||||||
|
|
||||||
|
if (req.connect_proto == CONNECT_PROTO_WEBSOCKET && resp.http_status == 101) {
|
||||||
|
response_status = http2::stringify_status(balloc, 200);
|
||||||
|
striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
|
||||||
|
} else {
|
||||||
|
response_status = http2::stringify_status(balloc, resp.http_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
||||||
|
|
||||||
|
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
|
||||||
|
|
||||||
if (!config->http2_proxy && !httpconf.no_server_rewrite) {
|
if (!config->http2_proxy && !httpconf.no_server_rewrite) {
|
||||||
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
|
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
|
||||||
|
@ -1742,7 +1770,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method != HTTP_CONNECT || !downstream->get_upgraded()) {
|
if (!req.regular_connect_method() || !downstream->get_upgraded()) {
|
||||||
auto affinity_cookie = downstream->get_affinity_cookie_to_send();
|
auto affinity_cookie = downstream->get_affinity_cookie_to_send();
|
||||||
if (affinity_cookie) {
|
if (affinity_cookie) {
|
||||||
auto dconn = downstream->get_downstream_connection();
|
auto dconn = downstream->get_downstream_connection();
|
||||||
|
@ -1916,7 +1944,7 @@ int Http2Upstream::on_downstream_abort_request_with_https_redirect(
|
||||||
|
|
||||||
int Http2Upstream::redirect_to_https(Downstream *downstream) {
|
int Http2Upstream::redirect_to_https(Downstream *downstream) {
|
||||||
auto &req = downstream->request();
|
auto &req = downstream->request();
|
||||||
if (req.method == HTTP_CONNECT || req.scheme != "http") {
|
if (req.regular_connect_method() || req.scheme != "http") {
|
||||||
return error_reply(downstream, 400);
|
return error_reply(downstream, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -489,7 +489,7 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
auto &balloc = downstream_->get_block_allocator();
|
auto &balloc = downstream_->get_block_allocator();
|
||||||
|
|
||||||
auto connect_method = req.method == HTTP_CONNECT;
|
auto connect_method = req.regular_connect_method();
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
auto &httpconf = config->http;
|
auto &httpconf = config->http;
|
||||||
|
@ -513,7 +513,8 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
auto buf = downstream_->get_request_buf();
|
auto buf = downstream_->get_request_buf();
|
||||||
|
|
||||||
// Assume that method and request path do not contain \r\n.
|
// Assume that method and request path do not contain \r\n.
|
||||||
auto meth = http2::to_method_string(req.method);
|
auto meth = http2::to_method_string(
|
||||||
|
req.connect_proto == CONNECT_PROTO_WEBSOCKET ? HTTP_GET : req.method);
|
||||||
buf->append(meth);
|
buf->append(meth);
|
||||||
buf->append(' ');
|
buf->append(' ');
|
||||||
|
|
||||||
|
@ -546,7 +547,8 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
|
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
|
||||||
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
|
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
|
||||||
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
|
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
|
||||||
(earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0);
|
(earlydataconf.strip_incoming ? http2::HDOP_STRIP_EARLY_DATA : 0) |
|
||||||
|
(req.http_major == 2 ? http2::HDOP_STRIP_SEC_WEBSOCKET_KEY : 0);
|
||||||
|
|
||||||
http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
|
http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
|
||||||
|
|
||||||
|
@ -559,16 +561,30 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
|
|
||||||
// set transfer-encoding only when content-length is unknown and
|
// set transfer-encoding only when content-length is unknown and
|
||||||
// request body is expected.
|
// request body is expected.
|
||||||
if (!connect_method && req.http2_expect_body && req.fs.content_length == -1) {
|
if (req.method != HTTP_CONNECT && req.http2_expect_body &&
|
||||||
|
req.fs.content_length == -1) {
|
||||||
downstream_->set_chunked_request(true);
|
downstream_->set_chunked_request(true);
|
||||||
buf->append("Transfer-Encoding: chunked\r\n");
|
buf->append("Transfer-Encoding: chunked\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.connection_close) {
|
if (req.connect_proto == CONNECT_PROTO_WEBSOCKET) {
|
||||||
buf->append("Connection: close\r\n");
|
if (req.http_major == 2) {
|
||||||
}
|
std::array<uint8_t, 16> nonce;
|
||||||
|
util::random_bytes(std::begin(nonce), std::end(nonce),
|
||||||
|
worker_->get_randgen());
|
||||||
|
auto iov = make_byte_ref(balloc, base64::encode_length(nonce.size()) + 1);
|
||||||
|
auto p = base64::encode(std::begin(nonce), std::end(nonce), iov.base);
|
||||||
|
*p = '\0';
|
||||||
|
auto key = StringRef{iov.base, p};
|
||||||
|
downstream_->set_ws_key(key);
|
||||||
|
|
||||||
if (!connect_method && req.upgrade_request) {
|
buf->append("Sec-Websocket-Key: ");
|
||||||
|
buf->append(key);
|
||||||
|
buf->append("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
|
||||||
|
} else if (!connect_method && req.upgrade_request) {
|
||||||
auto connection = req.fs.header(http2::HD_CONNECTION);
|
auto connection = req.fs.header(http2::HD_CONNECTION);
|
||||||
if (connection) {
|
if (connection) {
|
||||||
buf->append("Connection: ");
|
buf->append("Connection: ");
|
||||||
|
@ -582,6 +598,8 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
buf->append((*upgrade).value);
|
buf->append((*upgrade).value);
|
||||||
buf->append("\r\n");
|
buf->append("\r\n");
|
||||||
}
|
}
|
||||||
|
} else if (req.connection_close) {
|
||||||
|
buf->append("Connection: close\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto upstream = downstream_->get_upstream();
|
auto upstream = downstream_->get_upstream();
|
||||||
|
@ -708,7 +726,8 @@ int HttpDownstreamConnection::push_request_headers() {
|
||||||
// Don't call signal_write() if we anticipate request body. We call
|
// Don't call signal_write() if we anticipate request body. We call
|
||||||
// signal_write() when we received request body chunk, and it
|
// signal_write() when we received request body chunk, and it
|
||||||
// enables us to send headers and data in one writev system call.
|
// enables us to send headers and data in one writev system call.
|
||||||
if (connect_method || downstream_->get_blocked_request_buf()->rleft() ||
|
if (req.method == HTTP_CONNECT ||
|
||||||
|
downstream_->get_blocked_request_buf()->rleft() ||
|
||||||
(!req.http2_expect_body && req.fs.content_length == 0)) {
|
(!req.http2_expect_body && req.fs.content_length == 0)) {
|
||||||
signal_write();
|
signal_write();
|
||||||
}
|
}
|
||||||
|
@ -954,7 +973,7 @@ int htp_hdrs_completecb(http_parser *htp) {
|
||||||
|
|
||||||
// Check upgrade before processing non-final response, since if
|
// Check upgrade before processing non-final response, since if
|
||||||
// upgrade succeeded, 101 response is treated as final in nghttpx.
|
// upgrade succeeded, 101 response is treated as final in nghttpx.
|
||||||
downstream->check_upgrade_fulfilled();
|
downstream->check_upgrade_fulfilled_http1();
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
// Reset content-length because we reuse same Downstream for the
|
// Reset content-length because we reuse same Downstream for the
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
#include "http2.h"
|
#include "http2.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
@ -1087,9 +1088,15 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
buf->append('.');
|
buf->append('.');
|
||||||
buf->append('0' + req.http_minor);
|
buf->append('0' + req.http_minor);
|
||||||
buf->append(' ');
|
buf->append(' ');
|
||||||
buf->append(http2::stringify_status(balloc, resp.http_status));
|
if (req.connect_proto && downstream->get_upgraded()) {
|
||||||
buf->append(' ');
|
buf->append(http2::stringify_status(balloc, 101));
|
||||||
buf->append(http2::get_reason_phrase(resp.http_status));
|
buf->append(' ');
|
||||||
|
buf->append(http2::get_reason_phrase(101));
|
||||||
|
} else {
|
||||||
|
buf->append(http2::stringify_status(balloc, resp.http_status));
|
||||||
|
buf->append(' ');
|
||||||
|
buf->append(http2::get_reason_phrase(resp.http_status));
|
||||||
|
}
|
||||||
buf->append("\r\n");
|
buf->append("\r\n");
|
||||||
|
|
||||||
auto config = get_config();
|
auto config = get_config();
|
||||||
|
@ -1139,18 +1146,35 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connect_method && downstream->get_upgraded()) {
|
if (!connect_method && downstream->get_upgraded()) {
|
||||||
auto connection = resp.fs.header(http2::HD_CONNECTION);
|
if (req.connect_proto == CONNECT_PROTO_WEBSOCKET &&
|
||||||
if (connection) {
|
resp.http_status == 200) {
|
||||||
buf->append("Connection: ");
|
buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
|
||||||
buf->append((*connection).value);
|
auto key = req.fs.header(http2::HD_SEC_WEBSOCKET_KEY);
|
||||||
|
if (!key || key->value.size() != base64::encode_length(16)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
std::array<uint8_t, base64::encode_length(20)> out;
|
||||||
|
auto accept = http2::make_websocket_accept_token(out.data(), key->value);
|
||||||
|
if (accept.empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf->append("Sec-WebSocket-Accept: ");
|
||||||
|
buf->append(accept);
|
||||||
buf->append("\r\n");
|
buf->append("\r\n");
|
||||||
}
|
} else {
|
||||||
|
auto connection = resp.fs.header(http2::HD_CONNECTION);
|
||||||
|
if (connection) {
|
||||||
|
buf->append("Connection: ");
|
||||||
|
buf->append((*connection).value);
|
||||||
|
buf->append("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
auto upgrade = resp.fs.header(http2::HD_UPGRADE);
|
auto upgrade = resp.fs.header(http2::HD_UPGRADE);
|
||||||
if (upgrade) {
|
if (upgrade) {
|
||||||
buf->append("Upgrade: ");
|
buf->append("Upgrade: ");
|
||||||
buf->append((*upgrade).value);
|
buf->append((*upgrade).value);
|
||||||
buf->append("\r\n");
|
buf->append("\r\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
src/util.cc
16
src/util.cc
|
@ -1453,7 +1453,8 @@ void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { EVP_MD_CTX_destroy(ctx); }
|
||||||
} // namespace
|
} // namespace
|
||||||
#endif // !OPENSSL_1_1_API
|
#endif // !OPENSSL_1_1_API
|
||||||
|
|
||||||
int sha256(uint8_t *res, const StringRef &s) {
|
namespace {
|
||||||
|
int message_digest(uint8_t *res, const EVP_MD *meth, const StringRef &s) {
|
||||||
int rv;
|
int rv;
|
||||||
|
|
||||||
auto ctx = EVP_MD_CTX_new();
|
auto ctx = EVP_MD_CTX_new();
|
||||||
|
@ -1463,7 +1464,7 @@ int sha256(uint8_t *res, const StringRef &s) {
|
||||||
|
|
||||||
auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
|
auto ctx_deleter = defer(EVP_MD_CTX_free, ctx);
|
||||||
|
|
||||||
rv = EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
|
rv = EVP_DigestInit_ex(ctx, meth, nullptr);
|
||||||
if (rv != 1) {
|
if (rv != 1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -1473,7 +1474,7 @@ int sha256(uint8_t *res, const StringRef &s) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int mdlen = 32;
|
unsigned int mdlen = EVP_MD_size(meth);
|
||||||
|
|
||||||
rv = EVP_DigestFinal_ex(ctx, res, &mdlen);
|
rv = EVP_DigestFinal_ex(ctx, res, &mdlen);
|
||||||
if (rv != 1) {
|
if (rv != 1) {
|
||||||
|
@ -1482,6 +1483,15 @@ int sha256(uint8_t *res, const StringRef &s) {
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int sha256(uint8_t *res, const StringRef &s) {
|
||||||
|
return message_digest(res, EVP_sha256(), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sha1(uint8_t *res, const StringRef &s) {
|
||||||
|
return message_digest(res, EVP_sha1(), s);
|
||||||
|
}
|
||||||
|
|
||||||
bool is_hex_string(const StringRef &s) {
|
bool is_hex_string(const StringRef &s) {
|
||||||
if (s.size() % 2) {
|
if (s.size() % 2) {
|
||||||
|
|
11
src/util.h
11
src/util.h
|
@ -739,6 +739,13 @@ OutputIt random_alpha_digit(OutputIt first, OutputIt last, Generator &gen) {
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fills random bytes to the range [|first|, |last|).
|
||||||
|
template <typename OutputIt, typename Generator>
|
||||||
|
void random_bytes(OutputIt first, OutputIt last, Generator &gen) {
|
||||||
|
std::uniform_int_distribution<> dis(0, 255);
|
||||||
|
std::generate(first, last, [&dis, &gen]() { return dis(gen); });
|
||||||
|
}
|
||||||
|
|
||||||
template <typename OutputIterator, typename CharT, size_t N>
|
template <typename OutputIterator, typename CharT, size_t N>
|
||||||
OutputIterator copy_lit(OutputIterator it, CharT (&s)[N]) {
|
OutputIterator copy_lit(OutputIterator it, CharT (&s)[N]) {
|
||||||
return std::copy_n(s, N - 1, it);
|
return std::copy_n(s, N - 1, it);
|
||||||
|
@ -753,6 +760,10 @@ uint32_t hash32(const StringRef &s);
|
||||||
// returns 0 if it succeeds, or -1.
|
// returns 0 if it succeeds, or -1.
|
||||||
int sha256(uint8_t *buf, const StringRef &s);
|
int sha256(uint8_t *buf, const StringRef &s);
|
||||||
|
|
||||||
|
// Computes SHA-1 of |s|, and stores it in |buf|. This function
|
||||||
|
// returns 0 if it succeeds, or -1.
|
||||||
|
int sha1(uint8_t *buf, const StringRef &s);
|
||||||
|
|
||||||
// Returns host from |hostport|. If host cannot be found in
|
// Returns host from |hostport|. If host cannot be found in
|
||||||
// |hostport|, returns empty string. The returned string might not be
|
// |hostport|, returns empty string. The returned string might not be
|
||||||
// NULL-terminated.
|
// NULL-terminated.
|
||||||
|
|
Loading…
Reference in New Issue