Merge pull request #819 from nghttp2/nghttpx-https-redirect

nghttpx: Redirect to HTTPS URI with redirect-if-no-tls parameter in backend option
This commit is contained in:
Tatsuhiro Tsujikawa 2017-02-19 21:02:32 +09:00 committed by GitHub
commit 0797e89a90
25 changed files with 404 additions and 65 deletions

View File

@ -160,6 +160,7 @@ OPTIONS = [
"accesslog-write-early",
"tls-min-proto-version",
"tls-max-proto-version",
"redirect-https-port",
]
LOGVARS = [

View File

@ -533,6 +533,49 @@ func TestH1H1RespPhaseReturn(t *testing.T) {
}
}
// TestH1H1HTTPSRedirect tests that the request to the backend which
// requires TLS is redirected to https URI.
func TestH1H1HTTPSRedirect(t *testing.T) {
st := newServerTester([]string{"--redirect-if-not-tls"}, t, noopHandler)
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1HTTPSRedirect",
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 308; got != want {
t.Errorf("status = %v; want %v", got, want)
}
if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
t.Errorf("location: %v; want %v", got, want)
}
}
// TestH1H1HTTPSRedirectPort tests that the request to the backend
// which requires TLS is redirected to https URI with given port.
func TestH1H1HTTPSRedirectPort(t *testing.T) {
st := newServerTester([]string{"--redirect-if-not-tls", "--redirect-https-port=8443"}, t, noopHandler)
defer st.Close()
res, err := st.http1(requestParam{
path: "/foo?bar",
name: "TestH1H1HTTPSRedirectPort",
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 308; got != want {
t.Errorf("status = %v; want %v", got, want)
}
if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
t.Errorf("location: %v; want %v", got, want)
}
}
// // TestH1H2ConnectFailure tests that server handles the situation that
// // connection attempt to HTTP/2 backend failed.
// func TestH1H2ConnectFailure(t *testing.T) {

View File

@ -1405,6 +1405,49 @@ func TestH2H1DNS(t *testing.T) {
}
}
// TestH2H1HTTPSRedirect tests that the request to the backend which
// requires TLS is redirected to https URI.
func TestH2H1HTTPSRedirect(t *testing.T) {
st := newServerTester([]string{"--redirect-if-not-tls"}, t, noopHandler)
defer st.Close()
res, err := st.http2(requestParam{
name: "TestH2H1HTTPSRedirect",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 308; got != want {
t.Errorf("status = %v; want %v", got, want)
}
if got, want := res.header.Get("location"), "https://127.0.0.1/"; got != want {
t.Errorf("location: %v; want %v", got, want)
}
}
// TestH2H1HTTPSRedirectPort tests that the request to the backend
// which requires TLS is redirected to https URI with given port.
func TestH2H1HTTPSRedirectPort(t *testing.T) {
st := newServerTester([]string{"--redirect-if-not-tls", "--redirect-https-port=8443"}, t, noopHandler)
defer st.Close()
res, err := st.http2(requestParam{
path: "/foo?bar",
name: "TestH2H1HTTPSRedirectPort",
})
if err != nil {
t.Fatalf("Error st.http2() = %v", err)
}
if got, want := res.status, 308; got != want {
t.Errorf("status = %v; want %v", got, want)
}
if got, want := res.header.Get("location"), "https://127.0.0.1:8443/foo?bar"; got != want {
t.Errorf("location: %v; want %v", got, want)
}
}
// TestH2H1GracefulShutdown tests graceful shutdown.
func TestH2H1GracefulShutdown(t *testing.T) {
st := newServerTester(nil, t, noopHandler)

View File

@ -101,10 +101,8 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
args := []string{}
backendTLS := false
dns := false
externalDNS := false
acceptProxyProtocol := false
var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS bool
for _, k := range src_args {
switch k {
case "--http2-bridge":
@ -116,6 +114,8 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
externalDNS = true
case "--accept-proxy-protocol":
acceptProxyProtocol = true
case "--redirect-if-not-tls":
redirectIfNotTLS = true
default:
args = append(args, k)
}
@ -164,6 +164,10 @@ func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handl
b += ";dns"
}
if redirectIfNotTLS {
b += ";redirect-if-not-tls"
}
noTLS := ";no-tls"
if frontendTLS {
noTLS = ""

View File

@ -185,6 +185,8 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_is_hex_string",
shrpx::test_util_is_hex_string) ||
!CU_add_test(pSuite, "util_decode_hex", shrpx::test_util_decode_hex) ||
!CU_add_test(pSuite, "util_extract_host",
shrpx::test_util_extract_host) ||
!CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
!CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
!CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||

View File

@ -1447,6 +1447,7 @@ void fill_default_config(Config *config) {
httpconf.max_request_header_fields = 100;
httpconf.response_header_field_buffer = 64_k;
httpconf.max_response_header_fields = 500;
httpconf.redirect_https_port = StringRef::from_lit("443");
auto &http2conf = config->http2;
{
@ -1678,12 +1679,12 @@ Connections:
The parameters are delimited by ";". The available
parameters are: "proto=<PROTO>", "tls",
"sni=<SNI_HOST>", "fall=<N>", "rise=<N>",
"affinity=<METHOD>", "dns", and "frontend-tls". The
parameter consists of keyword, and optionally followed
by "=" and value. For example, the parameter "proto=h2"
consists of the keyword "proto" and value "h2". The
parameter "tls" consists of the keyword "tls" without
value. Each parameter is described as follows.
"affinity=<METHOD>", "dns", and "redirect-if-not-tls".
The parameter consists of keyword, and optionally
followed by "=" and value. For example, the parameter
"proto=h2" consists of the keyword "proto" and value
"h2". The parameter "tls" consists of the keyword "tls"
without value. Each parameter is described as follows.
The backend application protocol can be specified using
optional "proto" parameter, and in the form of
@ -1740,16 +1741,18 @@ Connections:
backend host name at start up, or reloading
configuration is skipped.
If "frontend-tls" parameter is used, the matched backend
requires frontend TLS connection. In other words, even
if pattern is matched, frontend connection is not TLS
protected, the request is forwarded to one of catch-all
backends. For this reason, catch-all backend cannot
have "frontend-tls" parameter. If at least one backend
has "frontend-tls" parameter, this feature is enabled
for all backend servers sharing the same <PATTERN>. It
is advised to set "frontend-tls" parameter to all
backends explicitly if this feature is desired.
If "redirect-if-not-tls" parameter is used, the matched
backend requires that frontend connection is TLS
encrypted. If it isn't, nghttpx responds to the request
with 308 status code, and https URI the client should
use instead is included in Location header field. The
port number in redirect URI is 443 by default, and can
be changed using --redirect-https-port option. If at
least one backend has "redirect-if-not-tls" parameter,
this feature is enabled for all backend servers sharing
the same <PATTERN>. It is advised to set
"redirect-if-no-tls" parameter to all backends
explicitly if this feature is desired.
Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special
@ -2545,6 +2548,12 @@ HTTP:
Don't rewrite server header field in default mode. When
--http2-proxy is used, these headers will not be altered
regardless of this option.
--redirect-https-port=<PORT>
Specify the port number which appears in Location header
field when redirect to HTTPS URI is made due to
"redirect-if-not-tls" parameter in --backend option.
Default: )"
<< config->http.redirect_https_port << R"(
API:
--api-max-request-body=<SIZE>
@ -3253,6 +3262,7 @@ int main(int argc, char **argv) {
152},
{SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag,
153},
{SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -3975,6 +3985,10 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION,
StringRef{optarg});
break;
case 154:
// --redirect-https-port
cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg});
break;
default:
break;
}

View File

@ -961,7 +961,7 @@ uint32_t next_cycle(const WeightedPri &pri) {
} // namespace
std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection(Downstream *downstream) {
ClientHandler::get_downstream_connection(int &err, Downstream *downstream) {
size_t group_idx;
auto &downstreamconf = *worker_->get_downstream_config();
auto &routerconf = downstreamconf.router;
@ -971,6 +971,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
const auto &req = downstream->request();
err = 0;
switch (faddr_->alt_mode) {
case ALTMODE_API:
return make_unique<APIDownstreamConnection>(worker_);
@ -1009,11 +1011,13 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
}
if (groups[group_idx]->shared_addr->require_upstream_tls && !conn_.tls.ssl) {
if (groups[group_idx]->shared_addr->redirect_if_not_tls && !conn_.tls.ssl) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Downstream address group " << group_idx
<< " requires frontend TLS connection. Send request to "
"catch-all group.";
group_idx = catch_all;
<< " requires frontend TLS connection.";
}
err = SHRPX_ERR_TLS_REQUIRED;
return nullptr;
}
auto &group = groups[group_idx];
@ -1087,6 +1091,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
CLOG(INFO, this) << "No working downstream address found";
}
err = -1;
return nullptr;
}
@ -1099,6 +1104,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
auto http2session = select_http2_session(group);
if (http2session == nullptr) {
err = -1;
return nullptr;
}

View File

@ -99,8 +99,12 @@ public:
void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
void remove_downstream_connection(DownstreamConnection *dconn);
// Returns DownstreamConnection object based on request path. This
// function returns non-null DownstreamConnection, and assigns 0 to
// |err| if it succeeds, or returns nullptr, and assigns negative
// error code to |err|.
std::unique_ptr<DownstreamConnection>
get_downstream_connection(Downstream *downstream);
get_downstream_connection(int &err, Downstream *downstream);
MemchunkPool *get_mcpool();
SSL *get_ssl() const;
// Call this function when HTTP/2 connection header is received at

View File

@ -747,7 +747,7 @@ struct DownstreamParams {
shrpx_session_affinity affinity;
bool tls;
bool dns;
bool frontend_tls;
bool redirect_if_not_tls;
};
namespace {
@ -823,8 +823,8 @@ int parse_downstream_params(DownstreamParams &out,
}
} else if (util::strieq_l("dns", param)) {
out.dns = true;
} else if (util::strieq_l("frontend-tls", param)) {
out.frontend_tls = true;
} else if (util::strieq_l("redirect-if-not-tls", param)) {
out.redirect_if_not_tls = true;
} else if (!param.empty()) {
LOG(ERROR) << "backend: " << param << ": unknown keyword";
return -1;
@ -919,8 +919,8 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
}
// If at least one backend requires frontend TLS connection,
// enable it for all backends sharing the same pattern.
if (params.frontend_tls) {
g.require_upstream_tls = true;
if (params.redirect_if_not_tls) {
g.redirect_if_not_tls = true;
}
g.addrs.push_back(addr);
done = true;
@ -936,7 +936,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
auto &g = addr_groups.back();
g.addrs.push_back(addr);
g.affinity = params.affinity;
g.require_upstream_tls = params.frontend_tls;
g.redirect_if_not_tls = params.redirect_if_not_tls;
if (pattern[0] == '*') {
// wildcard pattern
@ -1753,6 +1753,9 @@ int option_lookup_token(const char *name, size_t namelen) {
}
break;
case 't':
if (util::strieq_l("redirect-https-por", name, 18)) {
return SHRPX_OPTID_REDIRECT_HTTPS_PORT;
}
if (util::strieq_l("stream-read-timeou", name, 18)) {
return SHRPX_OPTID_STREAM_READ_TIMEOUT;
}
@ -3356,6 +3359,16 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg);
case SHRPX_OPTID_TLS_MAX_PROTO_VERSION:
return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg);
case SHRPX_OPTID_REDIRECT_HTTPS_PORT: {
auto n = util::parse_uint(optarg);
if (n == -1 || n < 0 || n > 65535) {
LOG(ERROR) << opt << ": bad value. Specify an integer in the range [0, "
"65535], inclusive";
return -1;
}
config->http.redirect_https_port = optarg;
return 0;
}
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";
@ -3663,12 +3676,6 @@ int configure_downstream_group(Config *config, bool http2_proxy,
return -1;
}
if (addr_groups[catch_all_group].require_upstream_tls) {
LOG(FATAL)
<< "backend: Catch-all backend cannot have frontend-tls parameter";
return -1;
}
downstreamconf.addr_group_catch_all = catch_all_group;
if (LOG_ENABLED(INFO)) {

View File

@ -331,6 +331,8 @@ constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION =
StringRef::from_lit("tls-min-proto-version");
constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION =
StringRef::from_lit("tls-max-proto-version");
constexpr auto SHRPX_OPT_REDIRECT_HTTPS_PORT =
StringRef::from_lit("redirect-https-port");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -434,9 +436,7 @@ struct AffinityHash {
struct DownstreamAddrGroupConfig {
DownstreamAddrGroupConfig(const StringRef &pattern)
: pattern(pattern),
affinity(AFFINITY_NONE),
require_upstream_tls(false) {}
: pattern(pattern), affinity(AFFINITY_NONE), redirect_if_not_tls(false) {}
StringRef pattern;
std::vector<DownstreamAddrConfig> addrs;
@ -445,8 +445,9 @@ struct DownstreamAddrGroupConfig {
std::vector<AffinityHash> affinity_hash;
// Session affinity
shrpx_session_affinity affinity;
// true if this group requires that client connection must be TLS.
bool require_upstream_tls;
// true if this group requires that client connection must be TLS,
// and the request must be redirected to https URI.
bool redirect_if_not_tls;
};
struct TicketKey {
@ -639,6 +640,9 @@ struct HttpConfig {
HeaderRefs add_request_headers;
HeaderRefs add_response_headers;
StringRef server_name;
// Port number which appears in Location header field when https
// redirect is made.
StringRef redirect_https_port;
size_t request_header_field_buffer;
size_t max_request_header_fields;
size_t response_header_field_buffer;
@ -1016,6 +1020,7 @@ enum {
SHRPX_OPTID_PSK_SECRETS,
SHRPX_OPTID_READ_BURST,
SHRPX_OPTID_READ_RATE,
SHRPX_OPTID_REDIRECT_HTTPS_PORT,
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
SHRPX_OPTID_RLIMIT_NOFILE,

View File

@ -38,6 +38,7 @@ enum ErrorCode {
SHRPX_ERR_INPROGRESS = -102,
SHRPX_ERR_DCONN_CANCELED = -103,
SHRPX_ERR_RETRY = -104,
SHRPX_ERR_TLS_REQUIRED = -105,
};
} // namespace shrpx

View File

@ -434,9 +434,25 @@ void Http2Upstream::start_downstream(Downstream *downstream) {
void Http2Upstream::initiate_downstream(Downstream *downstream) {
int rv;
auto dconn = handler_->get_downstream_connection(downstream);
if (!dconn ||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
auto dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = redirect_to_https(downstream);
} else {
rv = error_reply(downstream, 503);
}
if (rv != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
downstream->set_request_state(Downstream::CONNECT_FAIL);
downstream_queue_.mark_failure(downstream);
return;
}
rv = downstream->attach_downstream_connection(std::move(dconn));
if (rv != 0) {
// downstream connection fails, send error page
if (error_reply(downstream, 503) != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
@ -1792,6 +1808,52 @@ int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
return 0;
}
int Http2Upstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
int rv;
rv = redirect_to_https(downstream);
if (rv != 0) {
return -1;
}
handler_->signal_write();
return 0;
}
int Http2Upstream::redirect_to_https(Downstream *downstream) {
auto &req = downstream->request();
if (req.method == HTTP_CONNECT || req.scheme != "http") {
return error_reply(downstream, 400);
}
auto authority = util::extract_host(req.authority);
if (authority.empty()) {
return error_reply(downstream, 400);
}
auto &balloc = downstream->get_block_allocator();
auto config = get_config();
auto &httpconf = config->http;
StringRef loc;
if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
req.path);
} else {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
StringRef::from_lit(":"),
httpconf.redirect_https_port, req.path);
}
auto &resp = downstream->response();
resp.http_status = 308;
resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
http2::HD_LOCATION);
return send_reply(downstream, nullptr, 0);
}
int Http2Upstream::consume(int32_t stream_id, size_t len) {
int rv;
@ -1879,6 +1941,8 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
std::unique_ptr<DownstreamConnection> dconn;
rv = 0;
if (no_retry || downstream->no_more_retry()) {
goto fail;
}
@ -1886,7 +1950,7 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
// downstream connection is clean; we can retry with new
// downstream connection.
dconn = handler_->get_downstream_connection(downstream);
dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn) {
goto fail;
}
@ -1904,7 +1968,12 @@ int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
return 0;
fail:
if (on_downstream_abort_request(downstream, 503) != 0) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = on_downstream_abort_request(downstream, 503);
}
if (rv != 0) {
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
}
downstream->pop_downstream_connection();

View File

@ -54,6 +54,8 @@ public:
virtual int on_timeout(Downstream *downstream);
virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn);
@ -120,6 +122,8 @@ public:
size_t get_max_buffer_size() const;
int redirect_to_https(Downstream *downstream);
private:
DefaultMemchunks wb_;
std::unique_ptr<HttpsUpstream> pre_upstream_;

View File

@ -89,7 +89,8 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
downstream->pop_downstream_connection();
auto ndconn = handler->get_downstream_connection(downstream);
int rv;
auto ndconn = handler->get_downstream_connection(rv, downstream);
if (ndconn) {
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
return;
@ -98,7 +99,13 @@ void connect_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
downstream->set_request_state(Downstream::CONNECT_FAIL);
if (upstream->on_downstream_abort_request(downstream, 504) != 0) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = upstream->on_downstream_abort_request(downstream, 504);
}
if (rv != 0) {
delete handler;
}
}
@ -132,7 +139,8 @@ void backend_retry(Downstream *downstream) {
downstream->pop_downstream_connection();
auto ndconn = handler->get_downstream_connection(downstream);
int rv;
auto ndconn = handler->get_downstream_connection(rv, downstream);
if (ndconn) {
if (downstream->attach_downstream_connection(std::move(ndconn)) == 0) {
return;
@ -141,7 +149,13 @@ void backend_retry(Downstream *downstream) {
downstream->set_request_state(Downstream::CONNECT_FAIL);
if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = upstream->on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = upstream->on_downstream_abort_request(downstream, 503);
}
if (rv != 0) {
delete handler;
}
}

View File

@ -421,10 +421,18 @@ int htp_hdrs_completecb(http_parser *htp) {
return 0;
}
auto dconn = handler->get_downstream_connection(downstream);
auto dconn = handler->get_downstream_connection(rv, downstream);
if (!dconn ||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
if (!dconn) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
upstream->redirect_to_https(downstream);
}
downstream->set_request_state(Downstream::CONNECT_FAIL);
return -1;
}
if (downstream->attach_downstream_connection(std::move(dconn)) != 0) {
downstream->set_request_state(Downstream::CONNECT_FAIL);
return -1;
@ -1225,6 +1233,52 @@ int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
return 0;
}
int HttpsUpstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
redirect_to_https(downstream);
handler_->signal_write_no_wait();
return 0;
}
int HttpsUpstream::redirect_to_https(Downstream *downstream) {
auto &req = downstream->request();
if (req.method == HTTP_CONNECT || req.scheme != "http" ||
req.authority.empty()) {
error_reply(400);
return 0;
}
auto authority = util::extract_host(req.authority);
if (authority.empty()) {
error_reply(400);
return 0;
}
auto &balloc = downstream->get_block_allocator();
auto config = get_config();
auto &httpconf = config->http;
StringRef loc;
if (httpconf.redirect_https_port == StringRef::from_lit("443")) {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
req.path);
} else {
loc = concat_string_ref(balloc, StringRef::from_lit("https://"), authority,
StringRef::from_lit(":"),
httpconf.redirect_https_port, req.path);
}
auto &resp = downstream->response();
resp.http_status = 308;
resp.fs.add_header_token(StringRef::from_lit("location"), loc, false,
http2::HD_LOCATION);
resp.fs.add_header_token(StringRef::from_lit("connection"),
StringRef::from_lit("close"), false,
http2::HD_CONNECTION);
return send_reply(downstream, nullptr, 0);
}
void HttpsUpstream::log_response_headers(DefaultMemchunks *buf) const {
std::string nhdrs;
for (auto chunk = buf->head; chunk; chunk = chunk->next) {
@ -1267,11 +1321,13 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
downstream_->add_retry();
rv = 0;
if (no_retry || downstream_->no_more_retry()) {
goto fail;
}
dconn = handler_->get_downstream_connection(downstream_.get());
dconn = handler_->get_downstream_connection(rv, downstream_.get());
if (!dconn) {
goto fail;
}
@ -1289,7 +1345,12 @@ int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
return 0;
fail:
if (on_downstream_abort_request(downstream_.get(), 503) != 0) {
if (rv == SHRPX_ERR_TLS_REQUIRED) {
rv = on_downstream_abort_request_with_https_redirect(downstream);
} else {
rv = on_downstream_abort_request(downstream_.get(), 503);
}
if (rv != 0) {
return -1;
}
downstream_->pop_downstream_connection();

View File

@ -50,6 +50,8 @@ public:
virtual int on_event();
virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn);
@ -91,6 +93,7 @@ public:
void reset_current_header_length();
void log_response_headers(DefaultMemchunks *buf) const;
int redirect_to_https(Downstream *downstream);
private:
ClientHandler *handler_;

View File

@ -370,7 +370,7 @@ void SpdyUpstream::start_downstream(Downstream *downstream) {
void SpdyUpstream::initiate_downstream(Downstream *downstream) {
int rv;
auto dconn = handler_->get_downstream_connection(downstream);
auto dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn ||
(rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) {
@ -1265,6 +1265,13 @@ int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
return 0;
}
int SpdyUpstream::on_downstream_abort_request_with_https_redirect(
Downstream *downstream) {
// This should not be called since SPDY is only available with TLS.
assert(0);
return 0;
}
int SpdyUpstream::consume(int32_t stream_id, size_t len) {
int rv;
@ -1345,7 +1352,7 @@ int SpdyUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
// downstream connection is clean; we can retry with new
// downstream connection.
dconn = handler_->get_downstream_connection(downstream);
dconn = handler_->get_downstream_connection(rv, downstream);
if (!dconn) {
goto fail;
}

View File

@ -51,6 +51,8 @@ public:
virtual int on_timeout(Downstream *downstream);
virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code);
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream);
virtual ClientHandler *get_client_handler() const;
virtual int downstream_read(DownstreamConnection *dconn);
virtual int downstream_write(DownstreamConnection *dconn);

View File

@ -45,6 +45,10 @@ public:
virtual int on_timeout(Downstream *downstream) { return 0; };
virtual int on_downstream_abort_request(Downstream *downstream,
unsigned int status_code) = 0;
// Called when the current request is aborted without forwarding it
// to backend, and it should be redirected to https URI.
virtual int
on_downstream_abort_request_with_https_redirect(Downstream *downstream) = 0;
virtual int downstream_read(DownstreamConnection *dconn) = 0;
virtual int downstream_write(DownstreamConnection *dconn) = 0;
virtual int downstream_eof(DownstreamConnection *dconn) = 0;

View File

@ -77,7 +77,7 @@ bool match_shared_downstream_addr(
}
if (lhs->affinity != rhs->affinity ||
lhs->require_upstream_tls != rhs->require_upstream_tls) {
lhs->redirect_if_not_tls != rhs->redirect_if_not_tls) {
return false;
}
@ -192,7 +192,7 @@ void Worker::replace_downstream_config(
shared_addr->addrs.resize(src.addrs.size());
shared_addr->affinity = src.affinity;
shared_addr->affinity_hash = src.affinity_hash;
shared_addr->require_upstream_tls = src.require_upstream_tls;
shared_addr->redirect_if_not_tls = src.redirect_if_not_tls;
size_t num_http1 = 0;
size_t num_http2 = 0;

View File

@ -137,7 +137,7 @@ struct SharedDownstreamAddr {
http1_pri{},
http2_pri{},
affinity{AFFINITY_NONE},
require_upstream_tls{false} {}
redirect_if_not_tls{false} {}
SharedDownstreamAddr(const SharedDownstreamAddr &) = delete;
SharedDownstreamAddr(SharedDownstreamAddr &&) = delete;
@ -172,8 +172,9 @@ struct SharedDownstreamAddr {
WeightedPri http2_pri;
// Session affinity
shrpx_session_affinity affinity;
// true if this group requires that client connection must be TLS.
bool require_upstream_tls;
// true if this group requires that client connection must be TLS,
// and the request must be redirected to https URI.
bool redirect_if_not_tls;
};
struct DownstreamAddrGroup {

View File

@ -1432,6 +1432,26 @@ StringRef decode_hex(BlockAllocator &balloc, const StringRef &s) {
return StringRef{iov.base, p};
}
StringRef extract_host(const StringRef &hostport) {
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return StringRef{};
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return StringRef{};
}
return StringRef{std::begin(hostport), p + 1};
}
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return StringRef{};
}
return StringRef{std::begin(hostport), p};
}
} // namespace util
} // namespace nghttp2

View File

@ -739,6 +739,11 @@ uint32_t hash32(const StringRef &s);
// returns 0 if it succeeds, or -1.
int sha256(uint8_t *buf, const StringRef &s);
// Returns host from |hostport|. If host cannot be found in
// |hostport|, returns empty string. The returned string might not be
// NULL-terminated.
StringRef extract_host(const StringRef &hostport);
} // namespace util
} // namespace nghttp2

View File

@ -609,4 +609,22 @@ void test_util_decode_hex(void) {
CU_ASSERT("" == util::decode_hex(balloc, StringRef{}));
}
void test_util_extract_host(void) {
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo")));
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo:")));
CU_ASSERT(StringRef::from_lit("foo") ==
util::extract_host(StringRef::from_lit("foo:0")));
CU_ASSERT(StringRef::from_lit("[::1]") ==
util::extract_host(StringRef::from_lit("[::1]")));
CU_ASSERT(StringRef::from_lit("[::1]") ==
util::extract_host(StringRef::from_lit("[::1]:")));
CU_ASSERT(util::extract_host(StringRef::from_lit(":foo")).empty());
CU_ASSERT(util::extract_host(StringRef::from_lit("[::1")).empty());
CU_ASSERT(util::extract_host(StringRef::from_lit("[::1]0")).empty());
CU_ASSERT(util::extract_host(StringRef{}).empty());
}
} // namespace shrpx

View File

@ -66,6 +66,7 @@ void test_util_random_alpha_digit(void);
void test_util_format_hex(void);
void test_util_is_hex_string(void);
void test_util_decode_hex(void);
void test_util_extract_host(void);
} // namespace shrpx