summaryrefslogtreecommitdiffstats
path: root/src/shrpx_https_upstream.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/shrpx_https_upstream.cc')
-rw-r--r--src/shrpx_https_upstream.cc1558
1 files changed, 1558 insertions, 0 deletions
diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc
new file mode 100644
index 0000000..458b542
--- /dev/null
+++ b/src/shrpx_https_upstream.cc
@@ -0,0 +1,1558 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_https_upstream.h"
+
+#include <cassert>
+#include <set>
+#include <sstream>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_http.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_log_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_log.h"
+#ifdef HAVE_MRUBY
+# include "shrpx_mruby.h"
+#endif // HAVE_MRUBY
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+#include "base64.h"
+#include "url-parser/url_parser.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+int htp_msg_begin(llhttp_t *htp);
+int htp_uricb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len);
+int htp_hdrs_completecb(llhttp_t *htp);
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len);
+int htp_msg_completecb(llhttp_t *htp);
+} // namespace
+
+namespace {
+constexpr llhttp_settings_t htp_hooks = {
+ htp_msg_begin, // llhttp_cb on_message_begin;
+ htp_uricb, // llhttp_data_cb on_url;
+ nullptr, // llhttp_data_cb on_status;
+ nullptr, // llhttp_data_cb on_method;
+ nullptr, // llhttp_data_cb on_version;
+ htp_hdr_keycb, // llhttp_data_cb on_header_field;
+ htp_hdr_valcb, // llhttp_data_cb on_header_value;
+ nullptr, // llhttp_data_cb on_chunk_extension_name;
+ nullptr, // llhttp_data_cb on_chunk_extension_value;
+ htp_hdrs_completecb, // llhttp_cb on_headers_complete;
+ htp_bodycb, // llhttp_data_cb on_body;
+ htp_msg_completecb, // llhttp_cb on_message_complete;
+ nullptr, // llhttp_cb on_url_complete;
+ nullptr, // llhttp_cb on_status_complete;
+ nullptr, // llhttp_cb on_method_complete;
+ nullptr, // llhttp_cb on_version_complete;
+ nullptr, // llhttp_cb on_header_field_complete;
+ nullptr, // llhttp_cb on_header_value_complete;
+ nullptr, // llhttp_cb on_chunk_extension_name_complete;
+ nullptr, // llhttp_cb on_chunk_extension_value_complete;
+ nullptr, // llhttp_cb on_chunk_header;
+ nullptr, // llhttp_cb on_chunk_complete;
+ nullptr, // llhttp_cb on_reset;
+};
+} // namespace
+
+HttpsUpstream::HttpsUpstream(ClientHandler *handler)
+ : handler_(handler),
+ current_header_length_(0),
+ ioctrl_(handler->get_rlimit()),
+ num_requests_(0) {
+ llhttp_init(&htp_, HTTP_REQUEST, &htp_hooks);
+ htp_.data = this;
+}
+
+HttpsUpstream::~HttpsUpstream() {}
+
+void HttpsUpstream::reset_current_header_length() {
+ current_header_length_ = 0;
+}
+
+void HttpsUpstream::on_start_request() {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP request started";
+ }
+ reset_current_header_length();
+
+ auto downstream =
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
+
+ attach_downstream(std::move(downstream));
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.read;
+
+ handler_->repeat_read_timer();
+
+ ++num_requests_;
+}
+
+namespace {
+int htp_msg_begin(llhttp_t *htp) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ upstream->on_start_request();
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_uricb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ auto &balloc = downstream->get_block_allocator();
+
+ // We happen to have the same value for method token.
+ req.method = htp->method;
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large URI size="
+ << req.fs.buffer_size() + len;
+ }
+ assert(downstream->get_request_state() == DownstreamState::INITIAL);
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too long request URI");
+ return HPE_USER;
+ }
+
+ req.fs.add_extra_buffer_size(len);
+
+ if (req.method == HTTP_CONNECT) {
+ req.authority =
+ concat_string_ref(balloc, req.authority, StringRef{data, len});
+ } else {
+ req.path = concat_string_ref(balloc, req.path, StringRef{data, len});
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &httpconf = get_config()->http;
+
+ if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ if (req.fs.header_key_prev()) {
+ req.fs.append_last_header_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_header_name(StringRef{data, len});
+ }
+ } else {
+ // trailer part
+ if (req.fs.trailer_key_prev()) {
+ req.fs.append_last_trailer_key(data, len);
+ } else {
+ if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream)
+ << "Too many header field num=" << req.fs.num_fields() + 1;
+ }
+ llhttp_set_error_reason(htp, "too many headers");
+ return HPE_USER;
+ }
+ req.fs.alloc_add_trailer_name(StringRef{data, len});
+ }
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(llhttp_t *htp, const char *data, size_t len) {
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+
+ if (req.fs.buffer_size() + len >
+ get_config()->http.request_header_field_buffer) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "Too large header block size="
+ << req.fs.buffer_size() + len;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ downstream->set_request_state(
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE);
+ }
+ llhttp_set_error_reason(htp, "too large header");
+ return HPE_USER;
+ }
+ if (downstream->get_request_state() == DownstreamState::INITIAL) {
+ req.fs.append_last_header_value(data, len);
+ } else {
+ req.fs.append_last_trailer_value(data, len);
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+void rewrite_request_host_path_from_uri(BlockAllocator &balloc, Request &req,
+ const StringRef &uri,
+ http_parser_url &u) {
+ assert(u.field_set & (1 << UF_HOST));
+
+ // As per https://tools.ietf.org/html/rfc7230#section-5.4, we
+ // rewrite host header field with authority component.
+ auto authority = util::get_uri_field(uri.c_str(), u, UF_HOST);
+ // TODO properly check IPv6 numeric address
+ auto ipv6 = std::find(std::begin(authority), std::end(authority), ':') !=
+ std::end(authority);
+ auto authoritylen = authority.size();
+ if (ipv6) {
+ authoritylen += 2;
+ }
+ if (u.field_set & (1 << UF_PORT)) {
+ authoritylen += 1 + str_size("65535");
+ }
+ if (authoritylen > authority.size()) {
+ auto iovec = make_byte_ref(balloc, authoritylen + 1);
+ auto p = iovec.base;
+ if (ipv6) {
+ *p++ = '[';
+ }
+ p = std::copy(std::begin(authority), std::end(authority), p);
+ if (ipv6) {
+ *p++ = ']';
+ }
+
+ if (u.field_set & (1 << UF_PORT)) {
+ *p++ = ':';
+ p = util::utos(p, u.port);
+ }
+ *p = '\0';
+
+ req.authority = StringRef{iovec.base, p};
+ } else {
+ req.authority = authority;
+ }
+
+ req.scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+
+ StringRef path;
+ if (u.field_set & (1 << UF_PATH)) {
+ path = util::get_uri_field(uri.c_str(), u, UF_PATH);
+ } else if (req.method == HTTP_OPTIONS) {
+ // Server-wide OPTIONS takes following form in proxy request:
+ //
+ // OPTIONS http://example.org HTTP/1.1
+ //
+ // Notice that no slash after authority. See
+ // http://tools.ietf.org/html/rfc7230#section-5.3.4
+ req.path = StringRef::from_lit("");
+ // we ignore query component here
+ return;
+ } else {
+ path = StringRef::from_lit("/");
+ }
+
+ if (u.field_set & (1 << UF_QUERY)) {
+ auto &fdata = u.field_data[UF_QUERY];
+
+ if (u.field_set & (1 << UF_PATH)) {
+ auto q = util::get_uri_field(uri.c_str(), u, UF_QUERY);
+ path = StringRef{std::begin(path), std::end(q)};
+ } else {
+ path = concat_string_ref(balloc, path, StringRef::from_lit("?"),
+ StringRef{&uri[fdata.off], fdata.len});
+ }
+ }
+
+ req.path = http2::rewrite_clean_path(balloc, path);
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request headers completed";
+ }
+
+ auto handler = upstream->get_client_handler();
+
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.headers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ req.tstamp = lgconf->tstamp;
+
+ req.http_major = htp->http_major;
+ req.http_minor = htp->http_minor;
+
+ req.connection_close = !llhttp_should_keep_alive(htp);
+
+ handler->stop_read_timer();
+
+ auto method = req.method;
+
+ if (LOG_ENABLED(INFO)) {
+ std::stringstream ss;
+ ss << http2::to_method_string(method) << " "
+ << (method == HTTP_CONNECT ? req.authority : req.path) << " "
+ << "HTTP/" << req.http_major << "." << req.http_minor << "\n";
+
+ for (const auto &kv : req.fs.headers()) {
+ if (kv.name == "authorization") {
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": <redacted>\n";
+ continue;
+ }
+ ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n";
+ }
+
+ ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
+ }
+
+ // set content-length if method is not CONNECT, and no
+ // transfer-encoding is given. If transfer-encoding is given, leave
+ // req.fs.content_length to -1.
+ if (method != HTTP_CONNECT && !req.fs.header(http2::HD_TRANSFER_ENCODING)) {
+ // llhttp sets 0 to htp->content_length if there is no
+ // content-length header field. If we don't have both
+ // transfer-encoding and content-length header field, we assume
+ // that there is no request body.
+ req.fs.content_length = htp->content_length;
+ }
+
+ auto host = req.fs.header(http2::HD_HOST);
+
+ if (req.http_major > 1 || req.http_minor > 1) {
+ req.http_major = 1;
+ req.http_minor = 1;
+ return -1;
+ }
+
+ if (req.http_major == 1 && req.http_minor == 1 && !host) {
+ return -1;
+ }
+
+ if (host) {
+ const auto &value = host->value;
+ // Not allow at least '"' or '\' in host. They are illegal in
+ // authority component, also they cause headaches when we put them
+ // in quoted-string.
+ if (std::find_if(std::begin(value), std::end(value), [](char c) {
+ return c == '"' || c == '\\';
+ }) != std::end(value)) {
+ return -1;
+ }
+ }
+
+ downstream->inspect_http1_request();
+
+ auto faddr = handler->get_upstream_addr();
+ auto config = get_config();
+
+ if (method != HTTP_CONNECT) {
+ http_parser_url u{};
+ rv = http_parser_parse_url(req.path.c_str(), req.path.size(), 0, &u);
+ if (rv != 0) {
+ // Expect to respond with 400 bad request
+ return -1;
+ }
+ // checking UF_HOST could be redundant, but just in case ...
+ if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
+ req.no_authority = true;
+
+ if (method == HTTP_OPTIONS && req.path == StringRef::from_lit("*")) {
+ req.path = StringRef{};
+ } else {
+ req.path = http2::rewrite_clean_path(balloc, req.path);
+ }
+
+ if (host) {
+ req.authority = host->value;
+ }
+
+ if (handler->get_ssl()) {
+ req.scheme = StringRef::from_lit("https");
+ } else {
+ req.scheme = StringRef::from_lit("http");
+ }
+ } else {
+ rewrite_request_host_path_from_uri(balloc, req, req.path, u);
+ }
+ }
+
+ downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
+
+ auto &resp = downstream->response();
+
+ if (config->http.require_http_scheme &&
+ !http::check_http_scheme(req.scheme, handler->get_ssl() != nullptr)) {
+ resp.http_status = 400;
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ auto worker = handler->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+#endif // HAVE_MRUBY
+
+ // mruby hook may change method value
+
+ if (req.no_authority && config->http2_proxy &&
+ faddr->alt_mode == UpstreamAltMode::NONE) {
+ // Request URI should be absolute-form for client proxy mode
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ DownstreamConnection *dconn_ptr;
+#endif // HAVE_MRUBY
+
+ for (;;) {
+ auto dconn = handler->get_downstream_connection(rv, downstream);
+
+ if (!dconn) {
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ upstream->redirect_to_https(downstream);
+ }
+ downstream->set_request_state(DownstreamState::CONNECT_FAIL);
+ return -1;
+ }
+
+#ifdef HAVE_MRUBY
+ dconn_ptr = dconn.get();
+#endif // HAVE_MRUBY
+ if (downstream->attach_downstream_connection(std::move(dconn)) == 0) {
+ break;
+ }
+ }
+
+#ifdef HAVE_MRUBY
+ const auto &group = dconn_ptr->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_request_proc(downstream) != 0) {
+ resp.http_status = 500;
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ rv = downstream->push_request_headers();
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ if (faddr->alt_mode != UpstreamAltMode::NONE) {
+ // Normally, we forward expect: 100-continue to backend server,
+ // and let them decide whether responds with 100 Continue or not.
+ // For alternative mode, we have no backend, so just send 100
+ // Continue here to make the client happy.
+ if (downstream->get_expect_100_continue()) {
+ auto output = downstream->get_response_buf();
+ constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n");
+ output->append(res);
+ handler->signal_write();
+ }
+ }
+
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(llhttp_t *htp, const char *data, size_t len) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ auto downstream = upstream->get_downstream();
+ rv = downstream->push_upload_data_chunk(
+ reinterpret_cast<const uint8_t *>(data), len);
+ if (rv != 0) {
+ // Ignore error if response has been completed. We will end up in
+ // htp_msg_completecb, and request will end gracefully.
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return 0;
+ }
+
+ llhttp_set_error_reason(htp, "could not process request body");
+ return HPE_USER;
+ }
+ return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(llhttp_t *htp) {
+ int rv;
+ auto upstream = static_cast<HttpsUpstream *>(htp->data);
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP request completed";
+ }
+ auto handler = upstream->get_client_handler();
+ auto downstream = upstream->get_downstream();
+ auto &req = downstream->request();
+ auto &balloc = downstream->get_block_allocator();
+
+ for (auto &kv : req.fs.trailers()) {
+ kv.value = util::rstrip(balloc, kv.value);
+ }
+
+ downstream->set_request_state(DownstreamState::MSG_COMPLETE);
+ rv = downstream->end_upload_data();
+ if (rv != 0) {
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ // Here both response and request were completed. One of the
+ // reason why end_upload_data() failed is when we sent response
+ // in request phase hook. We only delete and proceed to the
+ // next request handling (if we don't close the connection). We
+ // first pause parser here just as we normally do, and call
+ // signal_write() to run on_write().
+ return HPE_PAUSED;
+ }
+ return -1;
+ }
+
+ if (handler->get_http2_upgrade_allowed() &&
+ downstream->get_http2_upgrade_request() &&
+ handler->perform_http2_upgrade(upstream) != 0) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, upstream) << "HTTP Upgrade to HTTP/2 failed";
+ }
+ }
+
+ // Stop further processing to complete this request
+ return HPE_PAUSED;
+}
+} // namespace
+
+// on_read() does not consume all available data in input buffer if
+// one http request is fully received.
+int HttpsUpstream::on_read() {
+ auto rb = handler_->get_rb();
+ auto rlimit = handler_->get_rlimit();
+ auto downstream = get_downstream();
+
+ if (rb->rleft() == 0 || handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ // downstream can be nullptr here, because it is initialized in the
+ // callback chain called by llhttp_execute()
+ if (downstream && downstream->get_upgraded()) {
+
+ auto rv = downstream->push_upload_data_chunk(rb->pos(), rb->rleft());
+
+ if (rv != 0) {
+ return -1;
+ }
+
+ rb->reset();
+ rlimit->startw();
+
+ if (downstream->request_buf_full()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream request buf is full";
+ }
+ pause_read(SHRPX_NO_BUFFER);
+
+ return 0;
+ }
+
+ return 0;
+ }
+
+ if (downstream) {
+ // To avoid reading next pipelined request
+ switch (downstream->get_request_state()) {
+ case DownstreamState::INITIAL:
+ case DownstreamState::HEADER_COMPLETE:
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ // llhttp_execute() does nothing once it entered error state.
+ auto htperr = llhttp_execute(&htp_, reinterpret_cast<const char *>(rb->pos()),
+ rb->rleft());
+
+ auto nread =
+ htperr == HPE_OK
+ ? rb->rleft()
+ : reinterpret_cast<const uint8_t *>(llhttp_get_error_pos(&htp_)) -
+ rb->pos();
+ rb->drain(nread);
+ rlimit->startw();
+
+ // Well, actually header length + some body bytes
+ current_header_length_ += nread;
+
+ // Get downstream again because it may be initialized in http parser
+ // execution
+ downstream = get_downstream();
+
+ if (htperr == HPE_PAUSED) {
+ // We may pause parser in htp_msg_completecb when both side are
+ // completed. Signal write, so that we can run on_write().
+ if (downstream &&
+ downstream->get_request_state() == DownstreamState::MSG_COMPLETE &&
+ downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ handler_->signal_write();
+ }
+ return 0;
+ }
+
+ if (htperr != HPE_OK) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "HTTP parse failure: "
+ << "(" << llhttp_errno_name(htperr) << ") "
+ << llhttp_get_error_reason(&htp_);
+ }
+
+ if (downstream &&
+ downstream->get_response_state() != DownstreamState::INITIAL) {
+ handler_->set_should_close_after_write(true);
+ handler_->signal_write();
+ return 0;
+ }
+
+ unsigned int status_code;
+
+ if (htperr == HPE_INVALID_METHOD) {
+ status_code = 501;
+ } else if (downstream) {
+ status_code = downstream->response().http_status;
+ if (status_code == 0) {
+ if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
+ status_code = 502;
+ } else if (downstream->get_request_state() ==
+ DownstreamState::HTTP1_REQUEST_HEADER_TOO_LARGE) {
+ status_code = 431;
+ } else {
+ status_code = 400;
+ }
+ }
+ } else {
+ status_code = 400;
+ }
+
+ error_reply(status_code);
+
+ handler_->signal_write();
+
+ return 0;
+ }
+
+ // downstream can be NULL here.
+ if (downstream && downstream->request_buf_full()) {
+ if (LOG_ENABLED(INFO)) {
+ ULOG(INFO, this) << "Downstream request buffer is full";
+ }
+
+ pause_read(SHRPX_NO_BUFFER);
+
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::on_write() {
+ auto downstream = get_downstream();
+ if (!downstream) {
+ return 0;
+ }
+
+ auto output = downstream->get_response_buf();
+ const auto &resp = downstream->response();
+
+ if (output->rleft() > 0) {
+ return 0;
+ }
+
+ // We need to postpone detachment until all data are sent so that
+ // we can notify nghttp2 library all data consumed.
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ } else {
+ // Connection close
+ downstream->pop_downstream_connection();
+ // dconn was deleted
+ }
+ // We need this if response ends before request.
+ if (downstream->get_request_state() == DownstreamState::MSG_COMPLETE) {
+ delete_downstream();
+
+ if (handler_->get_should_close_after_write()) {
+ return 0;
+ }
+
+ auto conn = handler_->get_connection();
+ auto &upstreamconf = get_config()->conn.upstream;
+
+ conn->rt.repeat = upstreamconf.timeout.idle_read;
+
+ handler_->repeat_read_timer();
+
+ return resume_read(SHRPX_NO_BUFFER, nullptr, 0);
+ } else {
+ // If the request is not complete, close the connection.
+ delete_downstream();
+
+ handler_->set_should_close_after_write(true);
+
+ return 0;
+ }
+ }
+
+ return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length);
+}
+
+int HttpsUpstream::on_event() { return 0; }
+
+ClientHandler *HttpsUpstream::get_client_handler() const { return handler_; }
+
+void HttpsUpstream::pause_read(IOCtrlReason reason) {
+ ioctrl_.pause_read(reason);
+}
+
+int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+ size_t consumed) {
+ // downstream could be nullptr
+ if (downstream && downstream->request_buf_full()) {
+ return 0;
+ }
+ if (ioctrl_.resume_read(reason)) {
+ // Process remaining data in input buffer here because these bytes
+ // are not notified by readcb until new data arrive.
+ llhttp_resume(&htp_);
+
+ auto conn = handler_->get_connection();
+ ev_feed_event(conn->loop, &conn->rev, EV_READ);
+ return 0;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+ int rv;
+
+ rv = downstream->on_read();
+
+ if (rv == SHRPX_ERR_EOF) {
+ if (downstream->get_request_header_sent()) {
+ return downstream_eof(dconn);
+ }
+ return SHRPX_ERR_RETRY;
+ }
+
+ if (rv == SHRPX_ERR_DCONN_CANCELED) {
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (rv < 0) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_BAD_HEADER) {
+ error_reply(502);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->can_detach_downstream_connection()) {
+ // Keep-alive
+ downstream->detach_downstream_connection();
+ }
+
+end:
+ handler_->signal_write();
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_write(DownstreamConnection *dconn) {
+ int rv;
+ rv = dconn->on_write();
+ if (rv == SHRPX_ERR_NETWORK) {
+ return downstream_error(dconn, Downstream::EVENT_ERROR);
+ }
+
+ if (rv != 0) {
+ return rv;
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
+ auto downstream = dconn->get_downstream();
+
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "EOF";
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
+ // Server may indicate the end of the request by EOF
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "The end of the response body was indicated by "
+ << "EOF";
+ }
+ on_downstream_body_complete(downstream);
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::INITIAL) {
+ // we did not send any response headers, so we can reply error
+ // message.
+ if (LOG_ENABLED(INFO)) {
+ DCLOG(INFO, dconn) << "Return error reply";
+ }
+ error_reply(502);
+ downstream->pop_downstream_connection();
+ goto end;
+ }
+
+ // Otherwise, we don't know how to recover from this situation. Just
+ // drop connection.
+ return -1;
+end:
+ handler_->signal_write();
+
+ return 0;
+}
+
+int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
+ auto downstream = dconn->get_downstream();
+ if (LOG_ENABLED(INFO)) {
+ if (events & Downstream::EVENT_ERROR) {
+ DCLOG(INFO, dconn) << "Network error/general error";
+ } else {
+ DCLOG(INFO, dconn) << "Timeout";
+ }
+ }
+ if (downstream->get_response_state() != DownstreamState::INITIAL) {
+ return -1;
+ }
+
+ unsigned int status;
+ if (events & Downstream::EVENT_TIMEOUT) {
+ if (downstream->get_request_header_sent()) {
+ status = 504;
+ } else {
+ status = 408;
+ }
+ } else {
+ status = 502;
+ }
+ error_reply(status);
+
+ downstream->pop_downstream_connection();
+
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
+ size_t bodylen) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ auto connection_close = false;
+
+ auto worker = handler_->get_worker();
+
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.fs.add_header_token(StringRef::from_lit("connection"),
+ StringRef::from_lit("close"), false,
+ http2::HD_CONNECTION);
+ connection_close = true;
+ } else if (req.http_major <= 0 ||
+ (req.http_major == 1 && req.http_minor == 0)) {
+ connection_close = true;
+ } else {
+ auto c = resp.fs.header(http2::HD_CONNECTION);
+ if (c && util::strieq_l("close", c->value)) {
+ connection_close = true;
+ }
+ }
+
+ if (connection_close) {
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+ }
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, resp.http_status));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(resp.http_status));
+ output->append("\r\n");
+
+ for (auto &kv : resp.fs.headers()) {
+ if (kv.name.empty() || kv.name[0] == ':') {
+ continue;
+ }
+ http2::capitalize(output, kv.name);
+ output->append(": ");
+ output->append(kv.value);
+ output->append("\r\n");
+ }
+
+ if (!resp.fs.header(http2::HD_SERVER)) {
+ output->append("Server: ");
+ output->append(config->http.server_name);
+ output->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ output->append(p.name);
+ output->append(": ");
+ output->append(p.value);
+ output->append("\r\n");
+ }
+
+ output->append("\r\n");
+
+ output->append(body, bodylen);
+
+ downstream->response_sent_body_length += bodylen;
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+
+ return 0;
+}
+
+void HttpsUpstream::error_reply(unsigned int status_code) {
+ auto downstream = get_downstream();
+
+ if (!downstream) {
+ attach_downstream(
+ std::make_unique<Downstream>(this, handler_->get_mcpool(), 1));
+ downstream = get_downstream();
+ }
+
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+
+ auto html = http::create_error_html(balloc, status_code);
+
+ resp.http_status = status_code;
+ // we are going to close connection for both frontend and backend in
+ // error condition. This is safest option.
+ resp.connection_close = true;
+ handler_->set_should_close_after_write(true);
+
+ auto output = downstream->get_response_buf();
+
+ output->append("HTTP/1.1 ");
+ output->append(http2::stringify_status(balloc, status_code));
+ output->append(' ');
+ output->append(http2::get_reason_phrase(status_code));
+ output->append("\r\nServer: ");
+ output->append(get_config()->http.server_name);
+ output->append("\r\nContent-Length: ");
+ std::array<uint8_t, NGHTTP2_MAX_UINT64_DIGITS> intbuf;
+ output->append(StringRef{std::begin(intbuf),
+ util::utos(std::begin(intbuf), html.size())});
+ output->append("\r\nDate: ");
+ auto lgconf = log_config();
+ lgconf->update_tstamp(std::chrono::system_clock::now());
+ output->append(lgconf->tstamp->time_http);
+ output->append("\r\nContent-Type: text/html; "
+ "charset=UTF-8\r\nConnection: close\r\n\r\n");
+ output->append(html);
+
+ downstream->response_sent_body_length += html.size();
+ downstream->set_response_state(DownstreamState::MSG_COMPLETE);
+}
+
+void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
+ assert(!downstream_);
+ downstream_ = std::move(downstream);
+}
+
+void HttpsUpstream::delete_downstream() {
+ if (downstream_ && downstream_->accesslog_ready()) {
+ handler_->write_accesslog(downstream_.get());
+ }
+
+ downstream_.reset();
+}
+
+Downstream *HttpsUpstream::get_downstream() const { return downstream_.get(); }
+
+std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
+ return std::unique_ptr<Downstream>(downstream_.release());
+}
+
+int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
+ if (LOG_ENABLED(INFO)) {
+ if (downstream->get_non_final_response()) {
+ DLOG(INFO, downstream) << "HTTP non-final response header";
+ } else {
+ DLOG(INFO, downstream) << "HTTP response header completed";
+ }
+ }
+
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+ auto &balloc = downstream->get_block_allocator();
+ auto dconn = downstream->get_downstream_connection();
+ // dconn might be nullptr if this is non-final response from mruby.
+
+ if (downstream->get_non_final_response() &&
+ !downstream->supports_non_final_response()) {
+ resp.fs.clear_headers();
+ return 0;
+ }
+
+#ifdef HAVE_MRUBY
+ if (!downstream->get_non_final_response()) {
+ assert(dconn);
+ const auto &group = dconn->get_downstream_addr_group();
+ if (group) {
+ const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
+
+ if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+
+ auto worker = handler_->get_worker();
+ auto mruby_ctx = worker->get_mruby_context();
+
+ if (mruby_ctx->run_on_response_proc(downstream) != 0) {
+ error_reply(500);
+ return -1;
+ }
+
+ if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
+ return -1;
+ }
+ }
+#endif // HAVE_MRUBY
+
+ auto connect_method = req.method == HTTP_CONNECT;
+
+ auto buf = downstream->get_response_buf();
+ buf->append("HTTP/");
+ buf->append('0' + req.http_major);
+ buf->append('.');
+ buf->append('0' + req.http_minor);
+ buf->append(' ');
+ if (req.connect_proto != ConnectProto::NONE && downstream->get_upgraded()) {
+ buf->append(http2::stringify_status(balloc, 101));
+ 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");
+
+ auto config = get_config();
+ auto &httpconf = config->http;
+
+ if (!config->http2_proxy && !httpconf.no_location_rewrite) {
+ downstream->rewrite_location_response_header(
+ get_client_handler()->get_upstream_scheme());
+ }
+
+ if (downstream->get_non_final_response()) {
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(),
+ http2::HDOP_STRIP_ALL);
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ resp.fs.clear_headers();
+
+ return 0;
+ }
+
+ auto build_flags = (http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA) |
+ (!http2::legacy_http1(req.http_major, req.http_minor)
+ ? 0
+ : http2::HDOP_STRIP_TRANSFER_ENCODING);
+
+ http2::build_http1_headers_from_headers(buf, resp.fs.headers(), build_flags);
+
+ auto worker = handler_->get_worker();
+
+ // after graceful shutdown commenced, add connection: close header
+ // field.
+ if (httpconf.max_requests <= num_requests_ ||
+ worker->get_graceful_shutdown()) {
+ resp.connection_close = true;
+ }
+
+ // We check downstream->get_response_connection_close() in case when
+ // the Content-Length is not available.
+ if (!req.connection_close && !resp.connection_close) {
+ if (req.http_major <= 0 || req.http_minor <= 0) {
+ // We add this header for HTTP/1.0 or HTTP/0.9 clients
+ buf->append("Connection: Keep-Alive\r\n");
+ }
+ } else if (!downstream->get_upgraded()) {
+ buf->append("Connection: close\r\n");
+ }
+
+ if (!connect_method && downstream->get_upgraded()) {
+ if (req.connect_proto == ConnectProto::WEBSOCKET &&
+ resp.http_status / 100 == 2) {
+ buf->append("Upgrade: websocket\r\nConnection: Upgrade\r\n");
+ 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");
+ } 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);
+ if (upgrade) {
+ buf->append("Upgrade: ");
+ buf->append((*upgrade).value);
+ buf->append("\r\n");
+ }
+ }
+ }
+
+ if (!resp.fs.header(http2::HD_ALT_SVC)) {
+ // We won't change or alter alt-svc from backend for now
+ if (!httpconf.altsvcs.empty()) {
+ buf->append("Alt-Svc: ");
+ buf->append(httpconf.altsvc_header_value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (!config->http2_proxy && !httpconf.no_server_rewrite) {
+ buf->append("Server: ");
+ buf->append(httpconf.server_name);
+ buf->append("\r\n");
+ } else {
+ auto server = resp.fs.header(http2::HD_SERVER);
+ if (server) {
+ buf->append("Server: ");
+ buf->append((*server).value);
+ buf->append("\r\n");
+ }
+ }
+
+ if (req.method != HTTP_CONNECT || !downstream->get_upgraded()) {
+ auto affinity_cookie = downstream->get_affinity_cookie_to_send();
+ if (affinity_cookie) {
+ auto &group = dconn->get_downstream_addr_group();
+ auto &shared_addr = group->shared_addr;
+ auto &cookieconf = shared_addr->affinity.cookie;
+ auto secure =
+ http::require_cookie_secure_attribute(cookieconf.secure, req.scheme);
+ auto cookie_str = http::create_affinity_cookie(
+ balloc, cookieconf.name, affinity_cookie, cookieconf.path, secure);
+ buf->append("Set-Cookie: ");
+ buf->append(cookie_str);
+ buf->append("\r\n");
+ }
+ }
+
+ auto via = resp.fs.header(http2::HD_VIA);
+ if (httpconf.no_via) {
+ if (via) {
+ buf->append("Via: ");
+ buf->append((*via).value);
+ buf->append("\r\n");
+ }
+ } else {
+ buf->append("Via: ");
+ if (via) {
+ buf->append((*via).value);
+ buf->append(", ");
+ }
+ std::array<char, 16> viabuf;
+ auto end = http::create_via_header_value(viabuf.data(), resp.http_major,
+ resp.http_minor);
+ buf->append(viabuf.data(), end - std::begin(viabuf));
+ buf->append("\r\n");
+ }
+
+ for (auto &p : httpconf.add_response_headers) {
+ buf->append(p.name);
+ buf->append(": ");
+ buf->append(p.value);
+ buf->append("\r\n");
+ }
+
+ buf->append("\r\n");
+
+ if (LOG_ENABLED(INFO)) {
+ log_response_headers(buf);
+ }
+
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_body(Downstream *downstream,
+ const uint8_t *data, size_t len,
+ bool flush) {
+ if (len == 0) {
+ return 0;
+ }
+ auto output = downstream->get_response_buf();
+ if (downstream->get_chunked_response()) {
+ output->append(util::utox(len));
+ output->append("\r\n");
+ }
+ output->append(data, len);
+
+ downstream->response_sent_body_length += len;
+
+ if (downstream->get_chunked_response()) {
+ output->append("\r\n");
+ }
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
+ const auto &req = downstream->request();
+ auto &resp = downstream->response();
+
+ if (downstream->get_chunked_response()) {
+ auto output = downstream->get_response_buf();
+ const auto &trailers = resp.fs.trailers();
+ if (trailers.empty()) {
+ output->append("0\r\n\r\n");
+ } else {
+ output->append("0\r\n");
+ http2::build_http1_headers_from_headers(output, trailers,
+ http2::HDOP_STRIP_ALL);
+ output->append("\r\n");
+ }
+ }
+ if (LOG_ENABLED(INFO)) {
+ DLOG(INFO, downstream) << "HTTP response completed";
+ }
+
+ if (!downstream->validate_response_recv_body_length()) {
+ resp.connection_close = true;
+ }
+
+ if (req.connection_close || resp.connection_close ||
+ // To avoid to stall upload body
+ downstream->get_request_state() != DownstreamState::MSG_COMPLETE) {
+ auto handler = get_client_handler();
+ handler->set_should_close_after_write(true);
+ }
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
+ unsigned int status_code) {
+ error_reply(status_code);
+ handler_->signal_write();
+ return 0;
+}
+
+int HttpsUpstream::on_downstream_abort_request_with_https_redirect(
+ Downstream *downstream) {
+ redirect_to_https(downstream);
+ handler_->signal_write();
+ 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) {
+ nhdrs.append(chunk->pos, chunk->last);
+ }
+ if (log_config()->errorlog_tty) {
+ nhdrs = http::colorizeHeaders(nhdrs.c_str());
+ }
+ ULOG(INFO, this) << "HTTP response headers\n" << nhdrs;
+}
+
+void HttpsUpstream::on_handler_delete() {
+ if (downstream_ && downstream_->accesslog_ready()) {
+ handler_->write_accesslog(downstream_.get());
+ }
+}
+
+int HttpsUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
+ int rv;
+ std::unique_ptr<DownstreamConnection> dconn;
+
+ assert(downstream == downstream_.get());
+
+ downstream_->pop_downstream_connection();
+
+ if (!downstream_->request_submission_ready()) {
+ switch (downstream_->get_response_state()) {
+ case DownstreamState::MSG_COMPLETE:
+ // We have got all response body already. Send it off.
+ return 0;
+ case DownstreamState::INITIAL:
+ if (on_downstream_abort_request(downstream_.get(), 502) != 0) {
+ return -1;
+ }
+ return 0;
+ default:
+ break;
+ }
+ // Return error so that caller can delete handler
+ return -1;
+ }
+
+ downstream_->add_retry();
+
+ rv = 0;
+
+ if (no_retry || downstream_->no_more_retry()) {
+ goto fail;
+ }
+
+ for (;;) {
+ auto dconn = handler_->get_downstream_connection(rv, downstream_.get());
+ if (!dconn) {
+ goto fail;
+ }
+
+ rv = downstream_->attach_downstream_connection(std::move(dconn));
+ if (rv == 0) {
+ break;
+ }
+ }
+
+ rv = downstream_->push_request_headers();
+ if (rv != 0) {
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (rv == SHRPX_ERR_TLS_REQUIRED) {
+ rv = on_downstream_abort_request_with_https_redirect(downstream);
+ } else {
+ rv = on_downstream_abort_request(downstream_.get(), 502);
+ }
+ if (rv != 0) {
+ return -1;
+ }
+ downstream_->pop_downstream_connection();
+
+ return 0;
+}
+
+int HttpsUpstream::initiate_push(Downstream *downstream, const StringRef &uri) {
+ return 0;
+}
+
+int HttpsUpstream::response_riovec(struct iovec *iov, int iovcnt) const {
+ if (!downstream_) {
+ return 0;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->riovec(iov, iovcnt);
+}
+
+void HttpsUpstream::response_drain(size_t n) {
+ if (!downstream_) {
+ return;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ buf->drain(n);
+}
+
+bool HttpsUpstream::response_empty() const {
+ if (!downstream_) {
+ return true;
+ }
+
+ auto buf = downstream_->get_response_buf();
+
+ return buf->rleft() == 0;
+}
+
+Downstream *
+HttpsUpstream::on_downstream_push_promise(Downstream *downstream,
+ int32_t promised_stream_id) {
+ return nullptr;
+}
+
+int HttpsUpstream::on_downstream_push_promise_complete(
+ Downstream *downstream, Downstream *promised_downstream) {
+ return -1;
+}
+
+bool HttpsUpstream::push_enabled() const { return false; }
+
+void HttpsUpstream::cancel_premature_downstream(
+ Downstream *promised_downstream) {}
+
+} // namespace shrpx