nghttpx: Add --http2-altsvc option
This commit is contained in:
parent
d88eadff13
commit
51bf79bb8c
|
@ -179,6 +179,7 @@ OPTIONS = [
|
||||||
"no-strip-incoming-early-data",
|
"no-strip-incoming-early-data",
|
||||||
"bpf-program-file",
|
"bpf-program-file",
|
||||||
"no-bpf",
|
"no-bpf",
|
||||||
|
"http2-altsvc",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOGVARS = [
|
LOGVARS = [
|
||||||
|
|
|
@ -135,6 +135,8 @@ int main(int argc, char *argv[]) {
|
||||||
shrpx::test_shrpx_http_create_via_header_value) ||
|
shrpx::test_shrpx_http_create_via_header_value) ||
|
||||||
!CU_add_test(pSuite, "http_create_affinity_cookie",
|
!CU_add_test(pSuite, "http_create_affinity_cookie",
|
||||||
shrpx::test_shrpx_http_create_affinity_cookie) ||
|
shrpx::test_shrpx_http_create_affinity_cookie) ||
|
||||||
|
!CU_add_test(pSuite, "http_create_atlsvc_header_field_value",
|
||||||
|
shrpx::test_shrpx_http_create_altsvc_header_value) ||
|
||||||
!CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
|
!CU_add_test(pSuite, "router_match", shrpx::test_shrpx_router_match) ||
|
||||||
!CU_add_test(pSuite, "router_match_wildcard",
|
!CU_add_test(pSuite, "router_match_wildcard",
|
||||||
shrpx::test_shrpx_router_match_wildcard) ||
|
shrpx::test_shrpx_router_match_wildcard) ||
|
||||||
|
@ -197,6 +199,7 @@ int main(int argc, char *argv[]) {
|
||||||
shrpx::test_util_extract_host) ||
|
shrpx::test_util_extract_host) ||
|
||||||
!CU_add_test(pSuite, "util_split_hostport",
|
!CU_add_test(pSuite, "util_split_hostport",
|
||||||
shrpx::test_util_split_hostport) ||
|
shrpx::test_util_split_hostport) ||
|
||||||
|
!CU_add_test(pSuite, "util_split_str", shrpx::test_util_split_str) ||
|
||||||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
|
||||||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
|
||||||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
|
||||||
|
|
33
src/shrpx.cc
33
src/shrpx.cc
|
@ -87,6 +87,7 @@
|
||||||
#include "shrpx_signal.h"
|
#include "shrpx_signal.h"
|
||||||
#include "shrpx_connection.h"
|
#include "shrpx_connection.h"
|
||||||
#include "shrpx_log.h"
|
#include "shrpx_log.h"
|
||||||
|
#include "shrpx_http.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "app_helper.h"
|
#include "app_helper.h"
|
||||||
#include "tls.h"
|
#include "tls.h"
|
||||||
|
@ -2723,13 +2724,18 @@ HTTP:
|
||||||
Rewrite host and :authority header fields in default
|
Rewrite host and :authority header fields in default
|
||||||
mode. When --http2-proxy is used, these headers will
|
mode. When --http2-proxy is used, these headers will
|
||||||
not be altered regardless of this option.
|
not be altered regardless of this option.
|
||||||
--altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
|
--altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
|
||||||
Specify protocol ID, port, host and origin of
|
Specify protocol ID, port, host and origin of
|
||||||
alternative service. <HOST> and <ORIGIN> are optional.
|
alternative service. <HOST>, <ORIGIN> and <PARAMS> are
|
||||||
They are advertised in alt-svc header field only in
|
optional. Empty <HOST> and <ORIGIN> are allowed and
|
||||||
HTTP/1.1 frontend. This option can be used multiple
|
they are treated as nothing is specified. They are
|
||||||
times to specify multiple alternative services.
|
advertised in alt-svc header field only in HTTP/1.1
|
||||||
Example: --altsvc=h2,443
|
frontend. This option can be used multiple times to
|
||||||
|
specify multiple alternative services.
|
||||||
|
Example: --altsvc="h2,443,,,ma=3600; persist=1'
|
||||||
|
--http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
|
||||||
|
Just like --altsvc option, but this altsvc is only sent
|
||||||
|
in HTTP/2 frontend.
|
||||||
--add-request-header=<HEADER>
|
--add-request-header=<HEADER>
|
||||||
Specify additional header field to add to request header
|
Specify additional header field to add to request header
|
||||||
set. This option just appends header field and won't
|
set. This option just appends header field and won't
|
||||||
|
@ -3183,6 +3189,16 @@ int process_options(Config *config,
|
||||||
config->http2.upstream.callbacks = create_http2_upstream_callbacks();
|
config->http2.upstream.callbacks = create_http2_upstream_callbacks();
|
||||||
config->http2.downstream.callbacks = create_http2_downstream_callbacks();
|
config->http2.downstream.callbacks = create_http2_downstream_callbacks();
|
||||||
|
|
||||||
|
if (!config->http.altsvcs.empty()) {
|
||||||
|
config->http.altsvc_header_value =
|
||||||
|
http::create_altsvc_header_value(config->balloc, config->http.altsvcs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config->http.http2_altsvcs.empty()) {
|
||||||
|
config->http.http2_altsvc_header_value = http::create_altsvc_header_value(
|
||||||
|
config->balloc, config->http.http2_altsvcs);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -3571,6 +3587,7 @@ int main(int argc, char **argv) {
|
||||||
&flag, 168},
|
&flag, 168},
|
||||||
{SHRPX_OPT_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, 169},
|
{SHRPX_OPT_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, 169},
|
||||||
{SHRPX_OPT_NO_BPF.c_str(), no_argument, &flag, 170},
|
{SHRPX_OPT_NO_BPF.c_str(), no_argument, &flag, 170},
|
||||||
|
{SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
@ -4382,6 +4399,10 @@ int main(int argc, char **argv) {
|
||||||
// --no-bpf
|
// --no-bpf
|
||||||
cmdcfgs.emplace_back(SHRPX_OPT_NO_BPF, StringRef::from_lit("yes"));
|
cmdcfgs.emplace_back(SHRPX_OPT_NO_BPF, StringRef::from_lit("yes"));
|
||||||
break;
|
break;
|
||||||
|
case 171:
|
||||||
|
// --http2-altsvc
|
||||||
|
cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -372,6 +372,57 @@ int parse_int(T *dest, const StringRef &opt, const char *optarg) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int parse_altsvc(AltSvc &altsvc, const StringRef &opt,
|
||||||
|
const StringRef &optarg) {
|
||||||
|
// PROTOID, PORT, HOST, ORIGIN, PARAMS.
|
||||||
|
auto tokens = util::split_str(optarg, ',', 5);
|
||||||
|
|
||||||
|
if (tokens.size() < 2) {
|
||||||
|
// Requires at least protocol_id and port
|
||||||
|
LOG(ERROR) << opt << ": too few parameters: " << optarg;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int port;
|
||||||
|
|
||||||
|
if (parse_uint(&port, opt, tokens[1]) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port < 1 ||
|
||||||
|
port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
|
||||||
|
LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
|
||||||
|
|
||||||
|
altsvc.port = port;
|
||||||
|
altsvc.service = make_string_ref(config->balloc, tokens[1]);
|
||||||
|
|
||||||
|
if (tokens.size() > 2) {
|
||||||
|
if (!tokens[2].empty()) {
|
||||||
|
altsvc.host = make_string_ref(config->balloc, tokens[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.size() > 3) {
|
||||||
|
if (!tokens[3].empty()) {
|
||||||
|
altsvc.origin = make_string_ref(config->balloc, tokens[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.size() > 4) {
|
||||||
|
if (!tokens[4].empty()) {
|
||||||
|
altsvc.params = make_string_ref(config->balloc, tokens[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// generated by gennghttpxfun.py
|
// generated by gennghttpxfun.py
|
||||||
LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
|
LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
|
||||||
|
@ -1863,6 +1914,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
||||||
return SHRPX_OPTID_BACKEND_IPV6;
|
return SHRPX_OPTID_BACKEND_IPV6;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'c':
|
||||||
|
if (util::strieq_l("http2-altsv", name, 11)) {
|
||||||
|
return SHRPX_OPTID_HTTP2_ALTSVC;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'e':
|
case 'e':
|
||||||
if (util::strieq_l("host-rewrit", name, 11)) {
|
if (util::strieq_l("host-rewrit", name, 11)) {
|
||||||
return SHRPX_OPTID_HOST_REWRITE;
|
return SHRPX_OPTID_HOST_REWRITE;
|
||||||
|
@ -3186,45 +3242,10 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
case SHRPX_OPTID_PADDING:
|
case SHRPX_OPTID_PADDING:
|
||||||
return parse_uint(&config->padding, opt, optarg);
|
return parse_uint(&config->padding, opt, optarg);
|
||||||
case SHRPX_OPTID_ALTSVC: {
|
case SHRPX_OPTID_ALTSVC: {
|
||||||
auto tokens = util::split_str(optarg, ',');
|
|
||||||
|
|
||||||
if (tokens.size() < 2) {
|
|
||||||
// Requires at least protocol_id and port
|
|
||||||
LOG(ERROR) << opt << ": too few parameters: " << optarg;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokens.size() > 4) {
|
|
||||||
// We only need protocol_id, port, host and origin
|
|
||||||
LOG(ERROR) << opt << ": too many parameters: " << optarg;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int port;
|
|
||||||
|
|
||||||
if (parse_uint(&port, opt, tokens[1]) != 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port < 1 ||
|
|
||||||
port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
|
|
||||||
LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
AltSvc altsvc{};
|
AltSvc altsvc{};
|
||||||
|
|
||||||
altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
|
if (parse_altsvc(altsvc, opt, optarg) != 0) {
|
||||||
|
return -1;
|
||||||
altsvc.port = port;
|
|
||||||
altsvc.service = make_string_ref(config->balloc, tokens[1]);
|
|
||||||
|
|
||||||
if (tokens.size() > 2) {
|
|
||||||
altsvc.host = make_string_ref(config->balloc, tokens[2]);
|
|
||||||
|
|
||||||
if (tokens.size() > 3) {
|
|
||||||
altsvc.origin = make_string_ref(config->balloc, tokens[3]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config->http.altsvcs.push_back(std::move(altsvc));
|
config->http.altsvcs.push_back(std::move(altsvc));
|
||||||
|
@ -3841,6 +3862,17 @@ int parse_config(Config *config, int optid, const StringRef &opt,
|
||||||
#endif // ENABLE_HTTP3
|
#endif // ENABLE_HTTP3
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
case SHRPX_OPTID_HTTP2_ALTSVC: {
|
||||||
|
AltSvc altsvc{};
|
||||||
|
|
||||||
|
if (parse_altsvc(altsvc, opt, optarg) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
config->http.http2_altsvcs.push_back(std::move(altsvc));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
case SHRPX_OPTID_CONF:
|
case SHRPX_OPTID_CONF:
|
||||||
LOG(WARN) << "conf: ignored";
|
LOG(WARN) << "conf: ignored";
|
||||||
|
|
||||||
|
|
|
@ -366,6 +366,7 @@ constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA =
|
||||||
constexpr auto SHRPX_OPT_BPF_PROGRAM_FILE =
|
constexpr auto SHRPX_OPT_BPF_PROGRAM_FILE =
|
||||||
StringRef::from_lit("bpf-program-file");
|
StringRef::from_lit("bpf-program-file");
|
||||||
constexpr auto SHRPX_OPT_NO_BPF = StringRef::from_lit("no-bpf");
|
constexpr auto SHRPX_OPT_NO_BPF = StringRef::from_lit("no-bpf");
|
||||||
|
constexpr auto SHRPX_OPT_HTTP2_ALTSVC = StringRef::from_lit("http2-altsvc");
|
||||||
|
|
||||||
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||||
|
|
||||||
|
@ -426,7 +427,7 @@ enum class ForwardedNode {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AltSvc {
|
struct AltSvc {
|
||||||
StringRef protocol_id, host, origin, service;
|
StringRef protocol_id, host, origin, service, params;
|
||||||
|
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
};
|
};
|
||||||
|
@ -771,6 +772,11 @@ struct HttpConfig {
|
||||||
bool strip_incoming;
|
bool strip_incoming;
|
||||||
} early_data;
|
} early_data;
|
||||||
std::vector<AltSvc> altsvcs;
|
std::vector<AltSvc> altsvcs;
|
||||||
|
// altsvcs serialized in a wire format.
|
||||||
|
StringRef altsvc_header_value;
|
||||||
|
std::vector<AltSvc> http2_altsvcs;
|
||||||
|
// http2_altsvcs serialized in a wire format.
|
||||||
|
StringRef http2_altsvc_header_value;
|
||||||
std::vector<ErrorPage> error_pages;
|
std::vector<ErrorPage> error_pages;
|
||||||
HeaderRefs add_request_headers;
|
HeaderRefs add_request_headers;
|
||||||
HeaderRefs add_response_headers;
|
HeaderRefs add_response_headers;
|
||||||
|
@ -1161,6 +1167,7 @@ enum {
|
||||||
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
|
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
|
||||||
SHRPX_OPTID_HEADER_FIELD_BUFFER,
|
SHRPX_OPTID_HEADER_FIELD_BUFFER,
|
||||||
SHRPX_OPTID_HOST_REWRITE,
|
SHRPX_OPTID_HOST_REWRITE,
|
||||||
|
SHRPX_OPTID_HTTP2_ALTSVC,
|
||||||
SHRPX_OPTID_HTTP2_BRIDGE,
|
SHRPX_OPTID_HTTP2_BRIDGE,
|
||||||
SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
|
SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS,
|
||||||
SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,
|
SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING,
|
||||||
|
|
|
@ -220,6 +220,57 @@ bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringRef create_altsvc_header_value(BlockAllocator &balloc,
|
||||||
|
const std::vector<AltSvc> &altsvcs) {
|
||||||
|
// <PROTOID>="<HOST>:<SERVICE>"; <PARAMS>
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
if (altsvcs.empty()) {
|
||||||
|
return StringRef{};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &altsvc : altsvcs) {
|
||||||
|
len += util::percent_encode_tokenlen(altsvc.protocol_id);
|
||||||
|
len += str_size("=\"");
|
||||||
|
len += util::quote_stringlen(altsvc.host);
|
||||||
|
len += str_size(":");
|
||||||
|
len += altsvc.service.size();
|
||||||
|
len += str_size("\"");
|
||||||
|
if (!altsvc.params.empty()) {
|
||||||
|
len += str_size("; ");
|
||||||
|
len += altsvc.params.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ", " between items.
|
||||||
|
len += (altsvcs.size() - 1) * 2;
|
||||||
|
|
||||||
|
// We will write additional ", " at the end, and cut it later.
|
||||||
|
auto iov = make_byte_ref(balloc, len + 2);
|
||||||
|
auto p = iov.base;
|
||||||
|
|
||||||
|
for (auto &altsvc : altsvcs) {
|
||||||
|
p = util::percent_encode_token(p, altsvc.protocol_id);
|
||||||
|
p = util::copy_lit(p, "=\"");
|
||||||
|
p = util::quote_string(p, altsvc.host);
|
||||||
|
*p++ = ':';
|
||||||
|
p = std::copy(std::begin(altsvc.service), std::end(altsvc.service), p);
|
||||||
|
*p++ = '"';
|
||||||
|
if (!altsvc.params.empty()) {
|
||||||
|
p = util::copy_lit(p, "; ");
|
||||||
|
p = std::copy(std::begin(altsvc.params), std::end(altsvc.params), p);
|
||||||
|
}
|
||||||
|
p = util::copy_lit(p, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
p -= 2;
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
assert(static_cast<size_t>(p - iov.base) == len);
|
||||||
|
|
||||||
|
return StringRef{iov.base, p};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -79,6 +79,10 @@ StringRef create_affinity_cookie(BlockAllocator &balloc, const StringRef &name,
|
||||||
bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
|
bool require_cookie_secure_attribute(SessionAffinityCookieSecure secure,
|
||||||
const StringRef &scheme);
|
const StringRef &scheme);
|
||||||
|
|
||||||
|
// Returns RFC 7838 alt-svc header field value.
|
||||||
|
StringRef create_altsvc_header_value(BlockAllocator &balloc,
|
||||||
|
const std::vector<AltSvc> &altsvcs);
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -1725,9 +1725,9 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
// 5 means :status and possible server, via, x-http2-push, and
|
// 6 means :status and possible server, via, x-http2-push, alt-svc,
|
||||||
// set-cookie (for affinity cookie) header field.
|
// and set-cookie (for affinity cookie) header field.
|
||||||
nva.reserve(resp.fs.headers().size() + 5 +
|
nva.reserve(resp.fs.headers().size() + 6 +
|
||||||
httpconf.add_response_headers.size());
|
httpconf.add_response_headers.size());
|
||||||
|
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
|
@ -1795,6 +1795,14 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!resp.fs.header(http2::HD_ALT_SVC)) {
|
||||||
|
// We won't change or alter alt-svc from backend for now
|
||||||
|
if (!httpconf.http2_altsvc_header_value.empty()) {
|
||||||
|
nva.push_back(http2::make_nv_ls_nocopy(
|
||||||
|
"alt-svc", httpconf.http2_altsvc_header_value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto via = resp.fs.header(http2::HD_VIA);
|
auto via = resp.fs.header(http2::HD_VIA);
|
||||||
if (httpconf.no_via) {
|
if (httpconf.no_via) {
|
||||||
if (via) {
|
if (via) {
|
||||||
|
|
|
@ -118,4 +118,40 @@ void test_shrpx_http_create_affinity_cookie(void) {
|
||||||
CU_ASSERT("charlie=01111111; Path=bar; Secure" == c);
|
CU_ASSERT("charlie=01111111; Path=bar; Secure" == c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_shrpx_http_create_altsvc_header_value(void) {
|
||||||
|
{
|
||||||
|
BlockAllocator balloc(1024, 1024);
|
||||||
|
std::vector<AltSvc> altsvcs{
|
||||||
|
AltSvc{
|
||||||
|
.protocol_id = StringRef::from_lit("h3"),
|
||||||
|
.host = StringRef::from_lit("127.0.0.1"),
|
||||||
|
.service = StringRef::from_lit("443"),
|
||||||
|
.params = StringRef::from_lit("ma=3600"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
CU_ASSERT(R"(h3="127.0.0.1:443"; ma=3600)" ==
|
||||||
|
http::create_altsvc_header_value(balloc, altsvcs));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
BlockAllocator balloc(1024, 1024);
|
||||||
|
std::vector<AltSvc> altsvcs{
|
||||||
|
AltSvc{
|
||||||
|
.protocol_id = StringRef::from_lit("h3"),
|
||||||
|
.service = StringRef::from_lit("443"),
|
||||||
|
.params = StringRef::from_lit("ma=3600"),
|
||||||
|
},
|
||||||
|
AltSvc{
|
||||||
|
.protocol_id = StringRef::from_lit("h3%"),
|
||||||
|
.host = StringRef::from_lit("\"foo\""),
|
||||||
|
.service = StringRef::from_lit("4433"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
CU_ASSERT(R"(h3=":443"; ma=3600, h3%25="\"foo\":4433")" ==
|
||||||
|
http::create_altsvc_header_value(balloc, altsvcs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -34,6 +34,7 @@ namespace shrpx {
|
||||||
void test_shrpx_http_create_forwarded(void);
|
void test_shrpx_http_create_forwarded(void);
|
||||||
void test_shrpx_http_create_via_header_value(void);
|
void test_shrpx_http_create_via_header_value(void);
|
||||||
void test_shrpx_http_create_affinity_cookie(void);
|
void test_shrpx_http_create_affinity_cookie(void);
|
||||||
|
void test_shrpx_http_create_altsvc_header_value(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
|
@ -1055,18 +1055,6 @@ std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
|
||||||
return std::unique_ptr<Downstream>(downstream_.release());
|
return std::unique_ptr<Downstream>(downstream_.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
void write_altsvc(DefaultMemchunks *buf, BlockAllocator &balloc,
|
|
||||||
const AltSvc &altsvc) {
|
|
||||||
buf->append(util::percent_encode_token(balloc, altsvc.protocol_id));
|
|
||||||
buf->append("=\"");
|
|
||||||
buf->append(util::quote_string(balloc, altsvc.host));
|
|
||||||
buf->append(':');
|
|
||||||
buf->append(altsvc.service);
|
|
||||||
buf->append('"');
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
if (LOG_ENABLED(INFO)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
if (downstream->get_non_final_response()) {
|
if (downstream->get_non_final_response()) {
|
||||||
|
@ -1225,13 +1213,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||||
// We won't change or alter alt-svc from backend for now
|
// We won't change or alter alt-svc from backend for now
|
||||||
if (!httpconf.altsvcs.empty()) {
|
if (!httpconf.altsvcs.empty()) {
|
||||||
buf->append("Alt-Svc: ");
|
buf->append("Alt-Svc: ");
|
||||||
|
buf->append(httpconf.altsvc_header_value);
|
||||||
auto &altsvcs = httpconf.altsvcs;
|
|
||||||
write_altsvc(buf, downstream->get_block_allocator(), altsvcs[0]);
|
|
||||||
for (size_t i = 1; i < altsvcs.size(); ++i) {
|
|
||||||
buf->append(", ");
|
|
||||||
write_altsvc(buf, downstream->get_block_allocator(), altsvcs[i]);
|
|
||||||
}
|
|
||||||
buf->append("\r\n");
|
buf->append("\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
81
src/util.cc
81
src/util.cc
|
@ -167,24 +167,29 @@ bool in_attr_char(char c) {
|
||||||
StringRef percent_encode_token(BlockAllocator &balloc,
|
StringRef percent_encode_token(BlockAllocator &balloc,
|
||||||
const StringRef &target) {
|
const StringRef &target) {
|
||||||
auto iov = make_byte_ref(balloc, target.size() * 3 + 1);
|
auto iov = make_byte_ref(balloc, target.size() * 3 + 1);
|
||||||
auto p = iov.base;
|
auto p = percent_encode_token(iov.base, target);
|
||||||
|
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
return StringRef{iov.base, p};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t percent_encode_tokenlen(const StringRef &target) {
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
for (auto first = std::begin(target); first != std::end(target); ++first) {
|
for (auto first = std::begin(target); first != std::end(target); ++first) {
|
||||||
uint8_t c = *first;
|
uint8_t c = *first;
|
||||||
|
|
||||||
if (c != '%' && in_token(c)) {
|
if (c != '%' && in_token(c)) {
|
||||||
*p++ = c;
|
++n;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
*p++ = '%';
|
// percent-encoded character '%ff'
|
||||||
*p++ = UPPER_XDIGITS[c >> 4];
|
n += 3;
|
||||||
*p++ = UPPER_XDIGITS[(c & 0x0f)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*p = '\0';
|
return n;
|
||||||
|
|
||||||
return StringRef{iov.base, p};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t hex_to_uint(char c) {
|
uint32_t hex_to_uint(char c) {
|
||||||
|
@ -208,21 +213,27 @@ StringRef quote_string(BlockAllocator &balloc, const StringRef &target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto iov = make_byte_ref(balloc, target.size() + cnt + 1);
|
auto iov = make_byte_ref(balloc, target.size() + cnt + 1);
|
||||||
auto p = iov.base;
|
auto p = quote_string(iov.base, target);
|
||||||
|
|
||||||
for (auto c : target) {
|
|
||||||
if (c == '"') {
|
|
||||||
*p++ = '\\';
|
|
||||||
*p++ = '"';
|
|
||||||
} else {
|
|
||||||
*p++ = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
|
|
||||||
return StringRef{iov.base, p};
|
return StringRef{iov.base, p};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t quote_stringlen(const StringRef &target) {
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
for (auto c : target) {
|
||||||
|
if (c == '"') {
|
||||||
|
n += 2;
|
||||||
|
} else {
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
template <typename Iterator>
|
template <typename Iterator>
|
||||||
Iterator cpydig(Iterator d, uint32_t n, size_t len) {
|
Iterator cpydig(Iterator d, uint32_t n, size_t len) {
|
||||||
|
@ -871,6 +882,42 @@ std::vector<StringRef> split_str(const StringRef &s, char delim) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n) {
|
||||||
|
if (n == 0) {
|
||||||
|
return split_str(s, delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n == 1) {
|
||||||
|
return {s};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t len = 1;
|
||||||
|
auto last = std::end(s);
|
||||||
|
StringRef::const_iterator d;
|
||||||
|
for (auto first = std::begin(s);
|
||||||
|
len < n && (d = std::find(first, last, delim)) != last;
|
||||||
|
++len, first = d + 1)
|
||||||
|
;
|
||||||
|
|
||||||
|
auto list = std::vector<StringRef>(len);
|
||||||
|
|
||||||
|
len = 0;
|
||||||
|
for (auto first = std::begin(s);; ++len) {
|
||||||
|
if (len == n - 1) {
|
||||||
|
list[len] = StringRef{first, last};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stop = std::find(first, last, delim);
|
||||||
|
list[len] = StringRef{first, stop};
|
||||||
|
if (stop == last) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
first = stop + 1;
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) {
|
std::vector<std::string> parse_config_str_list(const StringRef &s, char delim) {
|
||||||
auto sublist = split_str(s, delim);
|
auto sublist = split_str(s, delim);
|
||||||
auto res = std::vector<std::string>();
|
auto res = std::vector<std::string>();
|
||||||
|
|
51
src/util.h
51
src/util.h
|
@ -74,6 +74,8 @@ constexpr size_t NGHTTP2_MAX_UINT64_DIGITS = str_size("18446744073709551615");
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
|
|
||||||
|
extern const char UPPER_XDIGITS[];
|
||||||
|
|
||||||
inline bool is_alpha(const char c) {
|
inline bool is_alpha(const char c) {
|
||||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||||
}
|
}
|
||||||
|
@ -136,10 +138,52 @@ StringRef percent_decode(BlockAllocator &balloc, const StringRef &src);
|
||||||
// Percent encode |target| if character is not in token or '%'.
|
// Percent encode |target| if character is not in token or '%'.
|
||||||
StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target);
|
StringRef percent_encode_token(BlockAllocator &balloc, const StringRef &target);
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
OutputIt percent_encode_token(OutputIt it, const StringRef &target) {
|
||||||
|
for (auto first = std::begin(target); first != std::end(target); ++first) {
|
||||||
|
uint8_t c = *first;
|
||||||
|
|
||||||
|
if (c != '%' && in_token(c)) {
|
||||||
|
*it++ = c;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*it++ = '%';
|
||||||
|
*it++ = UPPER_XDIGITS[c >> 4];
|
||||||
|
*it++ = UPPER_XDIGITS[(c & 0x0f)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of bytes written by percent_encode_token with
|
||||||
|
// the same |target| parameter. The return value does not include a
|
||||||
|
// terminal NUL byte.
|
||||||
|
size_t percent_encode_tokenlen(const StringRef &target);
|
||||||
|
|
||||||
// Returns quotedString version of |target|. Currently, this function
|
// Returns quotedString version of |target|. Currently, this function
|
||||||
// just replace '"' with '\"'.
|
// just replace '"' with '\"'.
|
||||||
StringRef quote_string(BlockAllocator &balloc, const StringRef &target);
|
StringRef quote_string(BlockAllocator &balloc, const StringRef &target);
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
OutputIt quote_string(OutputIt it, const StringRef &target) {
|
||||||
|
for (auto c : target) {
|
||||||
|
if (c == '"') {
|
||||||
|
*it++ = '\\';
|
||||||
|
*it++ = '"';
|
||||||
|
} else {
|
||||||
|
*it++ = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of bytes written by quote_string with the same
|
||||||
|
// |target| parameter. The return value does not include a terminal
|
||||||
|
// NUL byte.
|
||||||
|
size_t quote_stringlen(const StringRef &target);
|
||||||
|
|
||||||
std::string format_hex(const unsigned char *s, size_t len);
|
std::string format_hex(const unsigned char *s, size_t len);
|
||||||
|
|
||||||
template <size_t N> std::string format_hex(const unsigned char (&s)[N]) {
|
template <size_t N> std::string format_hex(const unsigned char (&s)[N]) {
|
||||||
|
@ -443,8 +487,6 @@ template <typename T> std::string utos_funit(T n) {
|
||||||
return dtos(static_cast<double>(n) / (1 << b)) + u;
|
return dtos(static_cast<double>(n) / (1 << b)) + u;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const char UPPER_XDIGITS[];
|
|
||||||
|
|
||||||
template <typename T> std::string utox(T n) {
|
template <typename T> std::string utox(T n) {
|
||||||
std::string res;
|
std::string res;
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
@ -564,6 +606,11 @@ std::vector<std::string> parse_config_str_list(const StringRef &s,
|
||||||
// treated as a part of substring.
|
// treated as a part of substring.
|
||||||
std::vector<StringRef> split_str(const StringRef &s, char delim);
|
std::vector<StringRef> split_str(const StringRef &s, char delim);
|
||||||
|
|
||||||
|
// Behaves like split_str, but this variant splits at most |n| - 1
|
||||||
|
// times and returns at most |n| sub-strings. If |n| is zero, it
|
||||||
|
// falls back to split_str.
|
||||||
|
std::vector<StringRef> split_str(const StringRef &s, char delim, size_t n);
|
||||||
|
|
||||||
// Writes given time |tp| in Common Log format (e.g.,
|
// Writes given time |tp| in Common Log format (e.g.,
|
||||||
// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer
|
// 03/Jul/2014:00:19:38 +0900) in buffer pointed by |out|. The buffer
|
||||||
// must be at least 27 bytes, including terminal NULL byte. Expected
|
// must be at least 27 bytes, including terminal NULL byte. Expected
|
||||||
|
|
|
@ -652,4 +652,44 @@ void test_util_split_hostport(void) {
|
||||||
util::split_hostport(StringRef::from_lit("[::1]80")));
|
util::split_hostport(StringRef::from_lit("[::1]80")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_util_split_str(void) {
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
|
||||||
|
util::split_str(StringRef::from_lit(""), ','));
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha"), ','));
|
||||||
|
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,"), ','));
|
||||||
|
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("bravo")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,bravo"), ','));
|
||||||
|
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("bravo"),
|
||||||
|
StringRef::from_lit("charlie")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ','));
|
||||||
|
CU_ASSERT(
|
||||||
|
(std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("bravo"),
|
||||||
|
StringRef::from_lit("charlie")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 0));
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
|
||||||
|
util::split_str(StringRef::from_lit(""), ',', 1));
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("")} ==
|
||||||
|
util::split_str(StringRef::from_lit(""), ',', 2));
|
||||||
|
CU_ASSERT(
|
||||||
|
(std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("bravo,charlie")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 2));
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha"), ',', 2));
|
||||||
|
CU_ASSERT((std::vector<StringRef>{StringRef::from_lit("alpha"),
|
||||||
|
StringRef::from_lit("")}) ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,"), ',', 2));
|
||||||
|
CU_ASSERT(std::vector<StringRef>{StringRef::from_lit("alpha")} ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha"), ',', 0));
|
||||||
|
CU_ASSERT(
|
||||||
|
std::vector<StringRef>{StringRef::from_lit("alpha,bravo,charlie")} ==
|
||||||
|
util::split_str(StringRef::from_lit("alpha,bravo,charlie"), ',', 1));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -68,6 +68,7 @@ void test_util_is_hex_string(void);
|
||||||
void test_util_decode_hex(void);
|
void test_util_decode_hex(void);
|
||||||
void test_util_extract_host(void);
|
void test_util_extract_host(void);
|
||||||
void test_util_split_hostport(void);
|
void test_util_split_hostport(void);
|
||||||
|
void test_util_split_str(void);
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue