2012-06-04 16:48:31 +02:00
|
|
|
/*
|
2014-03-30 12:09:21 +02:00
|
|
|
* nghttp2 - HTTP/2 C Library
|
2012-06-04 16:48:31 +02:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2013-07-26 12:38:54 +02:00
|
|
|
#include "shrpx_http2_upstream.h"
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2013-01-10 16:11:41 +01:00
|
|
|
#include <netinet/tcp.h>
|
2012-06-04 16:48:31 +02:00
|
|
|
#include <assert.h>
|
2012-07-15 14:15:28 +02:00
|
|
|
#include <cerrno>
|
2012-06-04 16:48:31 +02:00
|
|
|
#include <sstream>
|
|
|
|
|
|
|
|
#include "shrpx_client_handler.h"
|
2013-08-03 11:51:01 +02:00
|
|
|
#include "shrpx_https_upstream.h"
|
2012-06-04 16:48:31 +02:00
|
|
|
#include "shrpx_downstream.h"
|
2012-06-09 16:14:00 +02:00
|
|
|
#include "shrpx_downstream_connection.h"
|
2012-06-04 16:48:31 +02:00
|
|
|
#include "shrpx_config.h"
|
|
|
|
#include "shrpx_http.h"
|
2015-02-25 14:53:23 +01:00
|
|
|
#include "shrpx_worker.h"
|
2015-07-12 15:16:20 +02:00
|
|
|
#include "shrpx_http2_session.h"
|
2017-02-16 14:46:22 +01:00
|
|
|
#include "shrpx_log.h"
|
2015-09-03 17:54:41 +02:00
|
|
|
#ifdef HAVE_MRUBY
|
2018-06-09 09:21:30 +02:00
|
|
|
# include "shrpx_mruby.h"
|
2015-09-03 17:54:41 +02:00
|
|
|
#endif // HAVE_MRUBY
|
2013-08-27 19:47:22 +02:00
|
|
|
#include "http2.h"
|
2012-06-04 16:48:31 +02:00
|
|
|
#include "util.h"
|
2013-08-03 11:51:01 +02:00
|
|
|
#include "base64.h"
|
2014-02-09 10:47:26 +01:00
|
|
|
#include "app_helper.h"
|
2015-02-05 15:21:53 +01:00
|
|
|
#include "template.h"
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
using namespace nghttp2;
|
2012-06-04 16:48:31 +02:00
|
|
|
|
|
|
|
namespace shrpx {
|
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
namespace {
|
|
|
|
constexpr size_t MAX_BUFFER_SIZE = 32_k;
|
|
|
|
} // namespace
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|
|
|
uint32_t error_code, void *user_data) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2012-12-09 11:15:14 +01:00
|
|
|
ULOG(INFO, upstream) << "Stream stream_id=" << stream_id
|
|
|
|
<< " is being closed";
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-06-01 16:44:32 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-14 15:36:47 +01:00
|
|
|
auto &req = downstream->request();
|
|
|
|
|
|
|
|
upstream->consume(stream_id, req.unconsumed_body_length);
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2016-01-14 15:36:47 +01:00
|
|
|
req.unconsumed_body_length = 0;
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_request_state() == DownstreamState::CONNECT_FAIL) {
|
2014-06-01 16:44:32 +02:00
|
|
|
upstream->remove_downstream(downstream);
|
2014-08-18 15:59:31 +02:00
|
|
|
// downstream was deleted
|
2014-06-01 16:44:32 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-22 14:41:16 +02:00
|
|
|
if (downstream->can_detach_downstream_connection()) {
|
|
|
|
// Keep-alive
|
|
|
|
downstream->detach_downstream_connection();
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_request_state(DownstreamState::STREAM_CLOSED);
|
2015-07-22 14:41:16 +02:00
|
|
|
|
2014-06-01 16:44:32 +02:00
|
|
|
// At this point, downstream read may be paused.
|
|
|
|
|
|
|
|
// If shrpx_downstream::push_request_headers() failed, the
|
|
|
|
// error is handled here.
|
|
|
|
upstream->remove_downstream(downstream);
|
2014-08-18 15:59:31 +02:00
|
|
|
// downstream was deleted
|
|
|
|
|
2014-06-01 16:44:32 +02:00
|
|
|
// How to test this case? Request sufficient large download
|
|
|
|
// and make client send RST_STREAM after it gets first DATA
|
|
|
|
// frame chunk.
|
|
|
|
|
2013-08-29 15:58:05 +02:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
|
2013-08-03 11:51:01 +02:00
|
|
|
int rv;
|
2014-06-15 09:14:00 +02:00
|
|
|
|
2016-10-02 03:22:33 +02:00
|
|
|
auto &balloc = http->get_downstream()->get_block_allocator();
|
2014-06-15 09:14:00 +02:00
|
|
|
|
2016-10-02 03:22:33 +02:00
|
|
|
auto http2_settings = http->get_downstream()->get_http2_settings();
|
|
|
|
http2_settings = util::to_base64(balloc, http2_settings);
|
|
|
|
|
|
|
|
auto settings_payload = base64::decode(balloc, std::begin(http2_settings),
|
|
|
|
std::end(http2_settings));
|
2014-06-15 09:14:00 +02:00
|
|
|
|
2015-11-07 04:16:22 +01:00
|
|
|
rv = nghttp2_session_upgrade2(
|
2016-10-02 03:22:33 +02:00
|
|
|
session_, settings_payload.byte(), settings_payload.size(),
|
2016-01-13 14:45:52 +01:00
|
|
|
http->get_downstream()->request().method == HTTP_HEAD, nullptr);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2015-03-01 02:11:45 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this) << "nghttp2_session_upgrade() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
}
|
2013-08-03 11:51:01 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2013-09-26 14:46:35 +02:00
|
|
|
pre_upstream_.reset(http);
|
2014-08-18 15:59:31 +02:00
|
|
|
auto downstream = http->pop_downstream();
|
2013-08-03 11:51:01 +02:00
|
|
|
downstream->reset_upstream(this);
|
2014-08-18 14:36:55 +02:00
|
|
|
downstream->set_stream_id(1);
|
2014-08-15 03:29:46 +02:00
|
|
|
downstream->reset_upstream_rtimer();
|
2013-08-03 11:51:01 +02:00
|
|
|
downstream->set_stream_id(1);
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
auto ptr = downstream.get();
|
2015-04-09 19:40:09 +02:00
|
|
|
|
|
|
|
nghttp2_session_set_stream_user_data(session_, 1, ptr);
|
2015-03-11 16:17:05 +01:00
|
|
|
downstream_queue_.add_pending(std::move(downstream));
|
|
|
|
downstream_queue_.mark_active(ptr);
|
2014-08-18 15:59:31 +02:00
|
|
|
|
2016-06-23 17:04:39 +02:00
|
|
|
// TODO This might not be necessary
|
|
|
|
handler_->stop_read_timer();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-04-05 11:59:22 +02:00
|
|
|
ULOG(INFO, this) << "Connection upgraded to HTTP/2";
|
|
|
|
}
|
|
|
|
|
2013-08-03 11:51:01 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
void Http2Upstream::start_settings_timer() {
|
|
|
|
ev_timer_start(handler_->get_loop(), &settings_timer_);
|
2013-10-30 16:44:23 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Upstream::stop_settings_timer() {
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
2013-10-30 16:44:23 +01:00
|
|
|
}
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
namespace {
|
2016-03-12 07:05:20 +01:00
|
|
|
int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
nghttp2_rcbuf *name, nghttp2_rcbuf *value,
|
|
|
|
uint8_t flags, void *user_data) {
|
|
|
|
auto namebuf = nghttp2_rcbuf_get_buf(name);
|
|
|
|
auto valuebuf = nghttp2_rcbuf_get_buf(value);
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
2016-03-12 07:05:20 +01:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
if (config->http2.upstream.debug.frame_debug) {
|
2016-03-12 07:05:20 +01:00
|
|
|
verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
|
|
|
|
valuebuf.base, valuebuf.len, flags, user_data);
|
2014-02-09 10:47:26 +01:00
|
|
|
}
|
2015-03-08 08:29:26 +01:00
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
2014-01-26 16:44:08 +01:00
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
auto &req = downstream->request();
|
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto &httpconf = config->http;
|
2016-01-18 09:00:20 +01:00
|
|
|
|
2016-03-12 07:05:20 +01:00
|
|
|
if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
|
2016-02-06 09:22:23 +01:00
|
|
|
httpconf.request_header_field_buffer ||
|
|
|
|
req.fs.num_fields() >= httpconf.max_request_header_fields) {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2014-06-27 15:53:54 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2015-04-29 14:10:59 +02:00
|
|
|
ULOG(INFO, upstream) << "Too large or many header field size="
|
2016-03-12 07:05:20 +01:00
|
|
|
<< req.fs.buffer_size() + namebuf.len + valuebuf.len
|
2016-01-13 14:45:52 +01:00
|
|
|
<< ", num=" << req.fs.num_fields() + 1;
|
2014-01-26 16:44:08 +01:00
|
|
|
}
|
2014-06-27 15:53:54 +02:00
|
|
|
|
2015-03-08 08:29:26 +01:00
|
|
|
// just ignore header fields if this is trailer part.
|
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (upstream->error_reply(downstream, 431) != 0) {
|
2014-06-27 15:53:54 +02:00
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2015-03-08 08:29:26 +01:00
|
|
|
|
2016-03-12 07:05:20 +01:00
|
|
|
auto token = http2::lookup_token(namebuf.base, namebuf.len);
|
2016-02-20 13:41:23 +01:00
|
|
|
auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
|
2016-02-20 11:13:39 +01:00
|
|
|
|
2016-03-12 07:05:20 +01:00
|
|
|
downstream->add_rcbuf(name);
|
|
|
|
downstream->add_rcbuf(value);
|
|
|
|
|
2015-03-08 08:29:26 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
|
|
|
|
// just store header fields for trailer part
|
2016-03-12 07:05:20 +01:00
|
|
|
req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
|
|
|
|
StringRef{valuebuf.base, valuebuf.len}, no_index,
|
|
|
|
token);
|
2015-03-08 08:29:26 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-12 07:05:20 +01:00
|
|
|
req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
|
|
|
|
StringRef{valuebuf.base, valuebuf.len}, no_index,
|
|
|
|
token);
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2016-08-27 17:49:38 +02:00
|
|
|
namespace {
|
|
|
|
int on_invalid_header_callback2(nghttp2_session *session,
|
|
|
|
const nghttp2_frame *frame, nghttp2_rcbuf *name,
|
|
|
|
nghttp2_rcbuf *value, uint8_t flags,
|
|
|
|
void *user_data) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
|
|
|
if (!downstream) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
auto namebuf = nghttp2_rcbuf_get_buf(name);
|
|
|
|
auto valuebuf = nghttp2_rcbuf_get_buf(value);
|
|
|
|
|
|
|
|
ULOG(INFO, upstream) << "Invalid header field for stream_id="
|
|
|
|
<< frame->hd.stream_id << ": name=["
|
|
|
|
<< StringRef{namebuf.base, namebuf.len} << "], value=["
|
|
|
|
<< StringRef{valuebuf.base, valuebuf.len} << "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
|
|
|
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
namespace {
|
2014-01-29 13:23:13 +01:00
|
|
|
int on_begin_headers_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, void *user_data) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2014-01-29 13:23:13 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
2014-01-16 15:41:13 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-01-29 13:23:13 +01:00
|
|
|
ULOG(INFO, upstream) << "Received upstream request HEADERS stream_id="
|
|
|
|
<< frame->hd.stream_id;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2014-03-25 18:04:24 +01:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
upstream->on_start_request(frame);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
2015-04-07 15:13:01 +02:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
void Http2Upstream::on_start_request(const nghttp2_frame *frame) {
|
2018-10-15 16:02:44 +02:00
|
|
|
auto downstream = std::make_unique<Downstream>(this, handler_->get_mcpool(),
|
|
|
|
frame->hd.stream_id);
|
2017-02-20 15:36:50 +01:00
|
|
|
nghttp2_session_set_stream_user_data(session_, frame->hd.stream_id,
|
2015-03-11 16:17:05 +01:00
|
|
|
downstream.get());
|
2014-03-25 18:04:24 +01:00
|
|
|
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->reset_upstream_rtimer();
|
2014-01-29 13:23:13 +01:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
handler_->repeat_read_timer();
|
2016-10-09 10:18:43 +02:00
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
auto &req = downstream->request();
|
|
|
|
|
2014-03-21 11:25:46 +01:00
|
|
|
// Although, we deprecated minor version from HTTP/2, we supply
|
|
|
|
// minor version 0 to use via header field in a conventional way.
|
2016-01-13 14:45:52 +01:00
|
|
|
req.http_major = 2;
|
|
|
|
req.http_minor = 0;
|
2014-03-21 11:25:46 +01:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
add_pending_downstream(std::move(downstream));
|
2014-08-18 15:59:31 +02:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
++num_requests_;
|
|
|
|
|
|
|
|
auto config = get_config();
|
|
|
|
auto &httpconf = config->http;
|
|
|
|
if (httpconf.max_requests <= num_requests_) {
|
|
|
|
start_graceful_shutdown();
|
|
|
|
}
|
2014-01-29 13:23:13 +01:00
|
|
|
}
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
int Http2Upstream::on_request_headers(Downstream *downstream,
|
|
|
|
const nghttp2_frame *frame) {
|
2017-01-11 12:22:55 +01:00
|
|
|
auto lgconf = log_config();
|
|
|
|
lgconf->update_tstamp(std::chrono::system_clock::now());
|
|
|
|
auto &req = downstream->request();
|
|
|
|
req.tstamp = lgconf->tstamp;
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2014-06-27 15:53:54 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
auto &nva = req.fs.headers();
|
2014-01-16 15:41:13 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-01-16 15:41:13 +01:00
|
|
|
std::stringstream ss;
|
2014-11-27 15:39:04 +01:00
|
|
|
for (auto &nv : nva) {
|
2019-04-15 15:59:26 +02:00
|
|
|
if (nv.name == "authorization") {
|
|
|
|
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": <redacted>\n";
|
|
|
|
continue;
|
|
|
|
}
|
2014-04-03 04:22:11 +02:00
|
|
|
ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2015-03-11 16:17:05 +01:00
|
|
|
ULOG(INFO, this) << "HTTP request headers. stream_id="
|
2016-10-15 11:36:04 +02:00
|
|
|
<< downstream->get_stream_id() << "\n"
|
|
|
|
<< ss.str();
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
auto &dump = config->http2.upstream.debug.dump;
|
2016-01-18 09:00:20 +01:00
|
|
|
|
|
|
|
if (dump.request_header) {
|
|
|
|
http2::dump_nv(dump.request_header, nva);
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
|
2015-04-10 15:10:51 +02:00
|
|
|
if (content_length) {
|
|
|
|
// libnghttp2 guarantees this can be parsed
|
2016-01-13 14:45:52 +01:00
|
|
|
req.fs.content_length = util::parse_uint(content_length->value);
|
2015-04-10 15:10:51 +02:00
|
|
|
}
|
|
|
|
|
2015-02-19 16:58:20 +01:00
|
|
|
// presence of mandatory header fields are guaranteed by libnghttp2.
|
2016-01-13 14:45:52 +01:00
|
|
|
auto authority = req.fs.header(http2::HD__AUTHORITY);
|
|
|
|
auto path = req.fs.header(http2::HD__PATH);
|
|
|
|
auto method = req.fs.header(http2::HD__METHOD);
|
|
|
|
auto scheme = req.fs.header(http2::HD__SCHEME);
|
2015-02-24 07:11:09 +01:00
|
|
|
|
2015-06-09 16:15:02 +02:00
|
|
|
auto method_token = http2::lookup_method_token(method->value);
|
|
|
|
if (method_token == -1) {
|
|
|
|
if (error_reply(downstream, 501) != 0) {
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-08-18 15:31:53 +02:00
|
|
|
auto faddr = handler_->get_upstream_addr();
|
|
|
|
|
|
|
|
// For HTTP/2 proxy, we require :authority.
|
2018-10-17 01:38:55 +02:00
|
|
|
if (method_token != HTTP_CONNECT && config->http2_proxy &&
|
|
|
|
faddr->alt_mode == UpstreamAltMode::NONE && !authority) {
|
2015-03-11 16:17:05 +01:00
|
|
|
rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
2015-02-24 07:11:09 +01:00
|
|
|
return 0;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
req.method = method_token;
|
2016-03-10 14:42:07 +01:00
|
|
|
if (scheme) {
|
|
|
|
req.scheme = scheme->value;
|
|
|
|
}
|
2016-01-13 14:45:52 +01:00
|
|
|
|
2015-09-03 16:36:49 +02:00
|
|
|
// nghttp2 library guarantees either :authority or host exist
|
|
|
|
if (!authority) {
|
2016-01-16 13:12:51 +01:00
|
|
|
req.no_authority = true;
|
2016-01-13 14:45:52 +01:00
|
|
|
authority = req.fs.header(http2::HD_HOST);
|
2015-09-03 16:36:49 +02:00
|
|
|
}
|
2016-01-13 14:45:52 +01:00
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
if (authority) {
|
|
|
|
req.authority = authority->value;
|
|
|
|
}
|
2015-09-03 16:36:49 +02:00
|
|
|
|
2015-07-11 09:12:35 +02:00
|
|
|
if (path) {
|
2016-03-24 16:07:21 +01:00
|
|
|
if (method_token == HTTP_OPTIONS &&
|
|
|
|
path->value == StringRef::from_lit("*")) {
|
2015-09-03 17:14:09 +02:00
|
|
|
// Server-wide OPTIONS request. Path is empty.
|
2018-10-17 01:38:55 +02:00
|
|
|
} else if (config->http2_proxy &&
|
|
|
|
faddr->alt_mode == UpstreamAltMode::NONE) {
|
2016-03-10 14:42:07 +01:00
|
|
|
req.path = path->value;
|
2015-07-11 10:50:58 +02:00
|
|
|
} else {
|
2016-03-10 14:42:07 +01:00
|
|
|
req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
|
|
|
|
path->value);
|
2015-07-11 10:50:58 +02:00
|
|
|
}
|
2015-07-11 09:12:35 +02:00
|
|
|
}
|
2014-07-03 12:59:10 +02:00
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
auto connect_proto = req.fs.header(http2::HD__PROTOCOL);
|
|
|
|
if (connect_proto) {
|
|
|
|
if (connect_proto->value != "websocket") {
|
|
|
|
if (error_reply(downstream, 400) != 0) {
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2018-10-17 02:50:29 +02:00
|
|
|
req.connect_proto = ConnectProto::WEBSOCKET;
|
2018-03-11 04:02:18 +01:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
|
2016-01-13 14:45:52 +01:00
|
|
|
req.http2_expect_body = true;
|
2016-09-22 12:02:57 +02:00
|
|
|
} else if (req.fs.content_length == -1) {
|
|
|
|
// If END_STREAM flag is set to HEADERS frame, we are sure that
|
|
|
|
// content-length is 0.
|
|
|
|
req.fs.content_length = 0;
|
2014-07-03 12:59:10 +02:00
|
|
|
}
|
|
|
|
|
2015-09-02 16:21:45 +02:00
|
|
|
downstream->inspect_http2_request();
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_request_state(DownstreamState::HEADER_COMPLETE);
|
2015-09-02 17:40:14 +02:00
|
|
|
|
2022-02-12 03:20:34 +01:00
|
|
|
if (config->http.require_http_scheme &&
|
|
|
|
!http::check_http_scheme(req.scheme, handler_->get_ssl() != nullptr)) {
|
|
|
|
if (error_reply(downstream, 400) != 0) {
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-09-03 17:54:41 +02:00
|
|
|
#ifdef HAVE_MRUBY
|
2022-02-12 03:20:34 +01:00
|
|
|
auto worker = handler_->get_worker();
|
2015-09-01 17:19:32 +02:00
|
|
|
auto mruby_ctx = worker->get_mruby_context();
|
|
|
|
|
2015-09-02 17:40:14 +02:00
|
|
|
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
|
|
|
if (error_reply(downstream, 500) != 0) {
|
|
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2015-09-03 17:54:41 +02:00
|
|
|
#endif // HAVE_MRUBY
|
2015-09-01 17:19:32 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-08-16 14:29:20 +02:00
|
|
|
downstream->disable_upstream_rtimer();
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_request_state(DownstreamState::MSG_COMPLETE);
|
2014-08-16 14:29:20 +02:00
|
|
|
}
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2015-09-02 16:21:45 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
start_downstream(downstream);
|
2014-08-16 14:29:20 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-04 17:07:00 +01:00
|
|
|
void Http2Upstream::start_downstream(Downstream *downstream) {
|
2016-01-13 14:45:52 +01:00
|
|
|
if (downstream_queue_.can_activate(downstream->request().authority)) {
|
2015-03-11 16:17:05 +01:00
|
|
|
initiate_downstream(downstream);
|
2014-12-04 17:07:00 +01:00
|
|
|
return;
|
2014-08-16 14:29:20 +02:00
|
|
|
}
|
2014-12-04 17:07:00 +01:00
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
downstream_queue_.mark_blocked(downstream);
|
2014-08-16 14:29:20 +02:00
|
|
|
}
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
void Http2Upstream::initiate_downstream(Downstream *downstream) {
|
2014-08-16 14:29:20 +02:00
|
|
|
int rv;
|
|
|
|
|
2022-05-14 10:44:53 +02:00
|
|
|
#ifdef HAVE_MRUBY
|
2019-01-19 03:12:05 +01:00
|
|
|
DownstreamConnection *dconn_ptr;
|
2022-05-14 10:44:53 +02:00
|
|
|
#endif // HAVE_MRUBY
|
2017-02-18 10:23:06 +01:00
|
|
|
|
2019-01-19 03:12:05 +01:00
|
|
|
for (;;) {
|
|
|
|
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, 502);
|
|
|
|
}
|
|
|
|
if (rv != 0) {
|
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2017-02-18 10:23:06 +01:00
|
|
|
|
2019-01-19 03:12:05 +01:00
|
|
|
downstream->set_request_state(DownstreamState::CONNECT_FAIL);
|
|
|
|
downstream_queue_.mark_failure(downstream);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2017-02-18 10:23:06 +01:00
|
|
|
|
2018-08-21 14:11:07 +02:00
|
|
|
#ifdef HAVE_MRUBY
|
2019-01-19 03:12:05 +01:00
|
|
|
dconn_ptr = dconn.get();
|
2018-08-21 14:11:07 +02:00
|
|
|
#endif // HAVE_MRUBY
|
2019-01-19 03:12:05 +01:00
|
|
|
rv = downstream->attach_downstream_connection(std::move(dconn));
|
|
|
|
if (rv == 0) {
|
|
|
|
break;
|
2014-07-02 17:12:16 +02:00
|
|
|
}
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2018-08-21 14:11:07 +02:00
|
|
|
|
|
|
|
#ifdef HAVE_MRUBY
|
|
|
|
const auto &group = dconn_ptr->get_downstream_addr_group();
|
2018-08-24 16:07:16 +02:00
|
|
|
if (group) {
|
2019-09-16 15:25:06 +02:00
|
|
|
const auto &mruby_ctx = group->shared_addr->mruby_ctx;
|
2018-08-24 16:07:16 +02:00
|
|
|
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
|
|
|
if (error_reply(downstream, 500) != 0) {
|
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2018-08-21 14:11:07 +02:00
|
|
|
|
2018-08-24 16:07:16 +02:00
|
|
|
downstream_queue_.mark_failure(downstream);
|
2018-08-21 14:11:07 +02:00
|
|
|
|
2018-08-24 16:07:16 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-08-21 14:11:07 +02:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2018-08-24 16:07:16 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-08-21 14:11:07 +02:00
|
|
|
}
|
|
|
|
#endif // HAVE_MRUBY
|
|
|
|
|
2014-01-16 15:41:13 +01:00
|
|
|
rv = downstream->push_request_headers();
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-08-16 14:29:20 +02:00
|
|
|
|
2017-04-01 07:04:55 +02:00
|
|
|
if (error_reply(downstream, 502) != 0) {
|
2015-03-11 16:17:05 +01:00
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
2014-07-02 17:12:16 +02:00
|
|
|
}
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
downstream_queue_.mark_failure(downstream);
|
2014-08-18 15:59:31 +02:00
|
|
|
|
2014-08-16 14:29:20 +02:00
|
|
|
return;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
downstream_queue_.mark_active(downstream);
|
2014-01-16 15:41:13 +01:00
|
|
|
|
2016-06-03 12:13:02 +02:00
|
|
|
auto &req = downstream->request();
|
|
|
|
if (!req.http2_expect_body) {
|
2016-06-16 17:00:37 +02:00
|
|
|
rv = downstream->end_upload_data();
|
|
|
|
if (rv != 0) {
|
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2016-06-03 12:13:02 +02:00
|
|
|
}
|
|
|
|
|
2014-08-16 14:29:20 +02:00
|
|
|
return;
|
2014-01-16 15:41:13 +01:00
|
|
|
}
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
void *user_data) {
|
2016-01-18 09:00:20 +01:00
|
|
|
if (get_config()->http2.upstream.debug.frame_debug) {
|
2014-02-09 10:47:26 +01:00
|
|
|
verbose_on_frame_recv_callback(session, frame, user_data);
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2016-10-09 10:18:43 +02:00
|
|
|
auto handler = upstream->get_client_handler();
|
2014-05-24 08:02:46 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (frame->hd.type) {
|
2014-01-27 14:13:41 +01:00
|
|
|
case NGHTTP2_DATA: {
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-08-09 11:47:45 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->disable_upstream_rtimer();
|
2014-05-27 17:26:27 +02:00
|
|
|
|
2016-06-16 17:00:37 +02:00
|
|
|
if (downstream->end_upload_data() != 0) {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
|
2016-06-17 15:32:15 +02:00
|
|
|
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2016-06-16 17:00:37 +02:00
|
|
|
}
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_request_state(DownstreamState::MSG_COMPLETE);
|
2014-01-27 14:13:41 +01:00
|
|
|
}
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2014-01-27 14:13:41 +01:00
|
|
|
}
|
2014-05-27 17:26:27 +02:00
|
|
|
case NGHTTP2_HEADERS: {
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream) {
|
2014-05-27 17:26:27 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->reset_upstream_rtimer();
|
|
|
|
|
2016-10-09 10:18:43 +02:00
|
|
|
handler->stop_read_timer();
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
return upstream->on_request_headers(downstream, frame);
|
2014-05-24 08:02:46 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->disable_upstream_rtimer();
|
|
|
|
|
2016-06-16 17:00:37 +02:00
|
|
|
if (downstream->end_upload_data() != 0) {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
|
2016-06-17 15:32:15 +02:00
|
|
|
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2016-06-16 17:00:37 +02:00
|
|
|
}
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_request_state(DownstreamState::MSG_COMPLETE);
|
2014-01-18 08:12:03 +01:00
|
|
|
}
|
2014-05-24 08:02:46 +02:00
|
|
|
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2014-01-18 08:12:03 +01:00
|
|
|
}
|
2013-10-30 16:44:23 +01:00
|
|
|
case NGHTTP2_SETTINGS:
|
2014-11-27 15:39:04 +01:00
|
|
|
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2013-10-30 16:44:23 +01:00
|
|
|
}
|
|
|
|
upstream->stop_settings_timer();
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2014-07-12 16:30:13 +02:00
|
|
|
case NGHTTP2_GOAWAY:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-12 16:30:13 +02:00
|
|
|
auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
|
|
|
|
frame->goaway.opaque_data_len);
|
|
|
|
|
|
|
|
ULOG(INFO, upstream) << "GOAWAY received: last-stream-id="
|
|
|
|
<< frame->goaway.last_stream_id
|
2014-11-27 15:39:04 +01:00
|
|
|
<< ", error_code=" << frame->goaway.error_code
|
|
|
|
<< ", debug_data=" << debug_data;
|
2014-07-12 16:30:13 +02:00
|
|
|
}
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
default:
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
|
|
|
int32_t stream_id, const uint8_t *data,
|
|
|
|
size_t len, void *user_data) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2018-08-22 15:32:25 +02:00
|
|
|
if (!downstream) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (upstream->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2014-07-02 16:56:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->reset_upstream_rtimer();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->push_upload_data_chunk(data, len) != 0) {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() != DownstreamState::MSG_COMPLETE) {
|
2016-06-17 15:32:15 +02:00
|
|
|
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (upstream->consume(stream_id, len) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2014-06-01 16:44:32 +02:00
|
|
|
return 0;
|
2013-10-29 16:00:58 +01:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2013-10-29 16:00:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-10-30 16:44:23 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
void *user_data) {
|
2016-01-18 09:00:20 +01:00
|
|
|
if (get_config()->http2.upstream.debug.frame_debug) {
|
2014-02-09 10:47:26 +01:00
|
|
|
verbose_on_frame_send_callback(session, frame, user_data);
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2015-04-07 15:13:01 +02:00
|
|
|
auto handler = upstream->get_client_handler();
|
2014-07-12 16:30:13 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (frame->hd.type) {
|
2015-04-08 08:00:50 +02:00
|
|
|
case NGHTTP2_DATA:
|
|
|
|
case NGHTTP2_HEADERS: {
|
|
|
|
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// RST_STREAM if request is still incomplete.
|
|
|
|
auto stream_id = frame->hd.stream_id;
|
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, stream_id));
|
|
|
|
|
2015-04-09 17:21:31 +02:00
|
|
|
if (!downstream) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-04-08 08:00:50 +02:00
|
|
|
// For tunneling, issue RST_STREAM to finish the stream.
|
|
|
|
if (downstream->get_upgraded() ||
|
|
|
|
nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, upstream)
|
|
|
|
<< "Send RST_STREAM to "
|
|
|
|
<< (downstream->get_upgraded() ? "tunneled " : "")
|
|
|
|
<< "stream stream_id=" << downstream->get_stream_id()
|
|
|
|
<< " to finish off incomplete request";
|
|
|
|
}
|
|
|
|
|
|
|
|
upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2014-07-12 16:30:13 +02:00
|
|
|
case NGHTTP2_SETTINGS:
|
2014-12-27 18:59:06 +01:00
|
|
|
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
|
|
|
|
upstream->start_settings_timer();
|
2013-10-30 16:44:23 +01:00
|
|
|
}
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2015-02-07 08:09:49 +01:00
|
|
|
case NGHTTP2_PUSH_PROMISE: {
|
2015-04-08 09:07:53 +02:00
|
|
|
auto promised_stream_id = frame->push_promise.promised_stream_id;
|
2015-11-15 16:12:54 +01:00
|
|
|
|
|
|
|
if (nghttp2_session_get_stream_user_data(session, promised_stream_id)) {
|
|
|
|
// In case of push from backend, downstream object was already
|
|
|
|
// created.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-15 16:02:44 +02:00
|
|
|
auto promised_downstream = std::make_unique<Downstream>(
|
2016-01-20 03:16:49 +01:00
|
|
|
upstream, handler->get_mcpool(), promised_stream_id);
|
|
|
|
auto &req = promised_downstream->request();
|
2015-04-08 09:07:53 +02:00
|
|
|
|
2016-01-11 09:36:47 +01:00
|
|
|
// As long as we use nghttp2_session_mem_send(), setting stream
|
|
|
|
// user data here should not fail. This is because this callback
|
|
|
|
// is called just after frame was serialized. So no worries about
|
|
|
|
// hanging Downstream.
|
2015-04-08 09:07:53 +02:00
|
|
|
nghttp2_session_set_stream_user_data(session, promised_stream_id,
|
2016-01-20 03:16:49 +01:00
|
|
|
promised_downstream.get());
|
2015-02-07 08:09:49 +01:00
|
|
|
|
2016-01-20 03:16:49 +01:00
|
|
|
promised_downstream->set_assoc_stream_id(frame->hd.stream_id);
|
|
|
|
promised_downstream->disable_upstream_rtimer();
|
2015-02-07 08:09:49 +01:00
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
req.http_major = 2;
|
|
|
|
req.http_minor = 0;
|
2015-02-07 08:09:49 +01:00
|
|
|
|
2016-09-22 12:02:57 +02:00
|
|
|
req.fs.content_length = 0;
|
|
|
|
req.http2_expect_body = false;
|
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
auto &promised_balloc = promised_downstream->get_block_allocator();
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
|
|
|
|
auto &nv = frame->push_promise.nva[i];
|
2016-03-10 16:50:27 +01:00
|
|
|
|
|
|
|
auto name =
|
|
|
|
make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen});
|
|
|
|
auto value =
|
|
|
|
make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen});
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
auto token = http2::lookup_token(nv.name, nv.namelen);
|
|
|
|
switch (token) {
|
|
|
|
case http2::HD__METHOD:
|
2016-03-10 16:50:27 +01:00
|
|
|
req.method = http2::lookup_method_token(value);
|
2015-02-07 08:09:49 +01:00
|
|
|
break;
|
|
|
|
case http2::HD__SCHEME:
|
2016-03-10 16:50:27 +01:00
|
|
|
req.scheme = value;
|
2015-02-07 08:09:49 +01:00
|
|
|
break;
|
|
|
|
case http2::HD__AUTHORITY:
|
2016-03-10 16:50:27 +01:00
|
|
|
req.authority = value;
|
2015-02-07 08:09:49 +01:00
|
|
|
break;
|
|
|
|
case http2::HD__PATH:
|
2016-03-10 16:50:27 +01:00
|
|
|
req.path = http2::rewrite_clean_path(promised_balloc, value);
|
2015-02-07 08:09:49 +01:00
|
|
|
break;
|
|
|
|
}
|
2016-03-10 16:50:27 +01:00
|
|
|
req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
|
|
|
token);
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
|
|
|
|
2016-01-20 03:16:49 +01:00
|
|
|
promised_downstream->inspect_http2_request();
|
2015-02-07 08:09:49 +01:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
promised_downstream->set_request_state(DownstreamState::MSG_COMPLETE);
|
2015-02-07 08:09:49 +01:00
|
|
|
|
|
|
|
// a bit weird but start_downstream() expects that given
|
|
|
|
// downstream is in pending queue.
|
2016-01-20 03:16:49 +01:00
|
|
|
auto ptr = promised_downstream.get();
|
|
|
|
upstream->add_pending_downstream(std::move(promised_downstream));
|
2015-09-06 08:21:36 +02:00
|
|
|
|
|
|
|
#ifdef HAVE_MRUBY
|
|
|
|
auto worker = handler->get_worker();
|
|
|
|
auto mruby_ctx = worker->get_mruby_context();
|
|
|
|
|
|
|
|
if (mruby_ctx->run_on_request_proc(ptr) != 0) {
|
|
|
|
if (upstream->error_reply(ptr, 500) != 0) {
|
|
|
|
upstream->rst_stream(ptr, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif // HAVE_MRUBY
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
upstream->start_downstream(ptr);
|
|
|
|
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
2014-07-12 16:30:13 +02:00
|
|
|
case NGHTTP2_GOAWAY:
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-12 16:30:13 +02:00
|
|
|
auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
|
|
|
|
frame->goaway.opaque_data_len);
|
|
|
|
|
|
|
|
ULOG(INFO, upstream) << "Sending GOAWAY: last-stream-id="
|
|
|
|
<< frame->goaway.last_stream_id
|
2014-11-27 15:39:04 +01:00
|
|
|
<< ", error_code=" << frame->goaway.error_code
|
|
|
|
<< ", debug_data=" << debug_data;
|
2014-07-12 16:30:13 +02:00
|
|
|
}
|
2015-02-21 09:08:03 +01:00
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
return 0;
|
2013-10-30 16:44:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2012-09-15 10:19:58 +02:00
|
|
|
namespace {
|
2013-08-29 14:51:58 +02:00
|
|
|
int on_frame_not_send_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
const nghttp2_frame *frame, int lib_error_code,
|
|
|
|
void *user_data) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2015-01-21 14:49:00 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, upstream) << "Failed to send control frame type="
|
|
|
|
<< static_cast<uint32_t>(frame->hd.type)
|
|
|
|
<< ", lib_error_code=" << lib_error_code << ":"
|
|
|
|
<< nghttp2_strerror(lib_error_code);
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (frame->hd.type == NGHTTP2_HEADERS &&
|
2015-01-17 11:33:30 +01:00
|
|
|
lib_error_code != NGHTTP2_ERR_STREAM_CLOSED &&
|
|
|
|
lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) {
|
2012-09-15 10:19:58 +02:00
|
|
|
// To avoid stream hanging around, issue RST_STREAM.
|
2015-03-11 16:17:05 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(
|
|
|
|
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream) {
|
2013-07-12 17:19:03 +02:00
|
|
|
upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
2012-09-15 10:19:58 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-29 14:51:58 +02:00
|
|
|
return 0;
|
2012-09-15 10:19:58 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-10-04 03:36:20 +02:00
|
|
|
namespace {
|
|
|
|
constexpr auto PADDING = std::array<uint8_t, 256>{};
|
|
|
|
} // namespace
|
|
|
|
|
2015-10-03 04:10:07 +02:00
|
|
|
namespace {
|
|
|
|
int send_data_callback(nghttp2_session *session, nghttp2_frame *frame,
|
|
|
|
const uint8_t *framehd, size_t length,
|
|
|
|
nghttp2_data_source *source, void *user_data) {
|
|
|
|
auto downstream = static_cast<Downstream *>(source->ptr);
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
|
|
|
|
auto body = downstream->get_response_buf();
|
|
|
|
|
|
|
|
auto wb = upstream->get_response_buf();
|
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
size_t padlen = 0;
|
2015-10-04 03:36:20 +02:00
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
wb->append(framehd, 9);
|
|
|
|
if (frame->data.padlen > 0) {
|
2015-10-04 03:36:20 +02:00
|
|
|
padlen = frame->data.padlen - 1;
|
2016-01-26 15:04:53 +01:00
|
|
|
wb->append(static_cast<uint8_t>(padlen));
|
2015-10-04 03:36:20 +02:00
|
|
|
}
|
2015-10-03 04:10:07 +02:00
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
body->remove(*wb, length);
|
2015-10-04 03:36:20 +02:00
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
wb->append(PADDING.data(), padlen);
|
2015-10-04 03:36:20 +02:00
|
|
|
|
2017-02-20 14:08:39 +01:00
|
|
|
if (body->rleft() == 0) {
|
|
|
|
downstream->disable_upstream_wtimer();
|
|
|
|
} else {
|
|
|
|
downstream->reset_upstream_wtimer();
|
|
|
|
}
|
2015-10-03 04:10:07 +02:00
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
if (length > 0 && downstream->resume_read(SHRPX_NO_BUFFER, length) != 0) {
|
2015-10-03 04:10:07 +02:00
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
|
2015-10-03 12:14:26 +02:00
|
|
|
// We have to add length here, so that we can log this amount of
|
|
|
|
// data transferred.
|
2016-01-14 15:54:28 +01:00
|
|
|
downstream->response_sent_body_length += length;
|
2015-10-03 04:10:07 +02:00
|
|
|
|
2016-09-08 15:49:36 +02:00
|
|
|
auto max_buffer_size = upstream->get_max_buffer_size();
|
|
|
|
|
|
|
|
return wb->rleft() >= max_buffer_size ? NGHTTP2_ERR_PAUSE : 0;
|
2015-10-03 04:10:07 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2013-02-27 14:39:44 +01:00
|
|
|
namespace {
|
2014-11-27 15:39:04 +01:00
|
|
|
uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
|
2014-07-03 16:00:19 +02:00
|
|
|
// NGHTTP2_REFUSED_STREAM is important because it tells upstream
|
|
|
|
// client to retry.
|
2014-11-27 15:39:04 +01:00
|
|
|
switch (downstream_error_code) {
|
2014-07-03 16:00:19 +02:00
|
|
|
case NGHTTP2_NO_ERROR:
|
|
|
|
case NGHTTP2_REFUSED_STREAM:
|
2014-08-23 10:34:56 +02:00
|
|
|
return downstream_error_code;
|
2014-07-03 16:00:19 +02:00
|
|
|
default:
|
|
|
|
return NGHTTP2_INTERNAL_ERROR;
|
2013-02-27 14:39:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2014-12-23 09:38:03 +01:00
|
|
|
namespace {
|
2014-12-27 18:59:06 +01:00
|
|
|
void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(w->data);
|
|
|
|
auto handler = upstream->get_client_handler();
|
|
|
|
ULOG(INFO, upstream) << "SETTINGS timeout";
|
|
|
|
if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
|
|
|
|
delete handler;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
handler->signal_write();
|
2014-12-23 09:38:03 +01:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-01-21 17:43:56 +01:00
|
|
|
namespace {
|
|
|
|
void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(w->data);
|
|
|
|
auto handler = upstream->get_client_handler();
|
|
|
|
upstream->submit_goaway();
|
|
|
|
handler->signal_write();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
|
|
|
|
auto upstream = static_cast<Http2Upstream *>(w->data);
|
|
|
|
upstream->check_shutdown();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void Http2Upstream::submit_goaway() {
|
|
|
|
auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_);
|
|
|
|
nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id,
|
|
|
|
NGHTTP2_NO_ERROR, nullptr, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Upstream::check_shutdown() {
|
2016-06-04 05:43:17 +02:00
|
|
|
auto worker = handler_->get_worker();
|
|
|
|
|
|
|
|
if (!worker->get_graceful_shutdown()) {
|
2015-01-21 17:43:56 +01:00
|
|
|
return;
|
|
|
|
}
|
2015-02-25 14:53:23 +01:00
|
|
|
|
2016-06-04 05:43:17 +02:00
|
|
|
ev_prepare_stop(handler_->get_loop(), &prep_);
|
2015-02-25 14:53:23 +01:00
|
|
|
|
2017-02-20 15:36:50 +01:00
|
|
|
start_graceful_shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Upstream::start_graceful_shutdown() {
|
|
|
|
int rv;
|
|
|
|
if (ev_is_active(&shutdown_timer_)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-06-04 05:43:17 +02:00
|
|
|
rv = nghttp2_submit_shutdown_notice(session_);
|
|
|
|
if (rv != 0) {
|
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_shutdown_notice() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
return;
|
2015-01-21 17:43:56 +01:00
|
|
|
}
|
2016-06-04 05:43:17 +02:00
|
|
|
|
|
|
|
handler_->signal_write();
|
|
|
|
|
|
|
|
ev_timer_start(handler_->get_loop(), &shutdown_timer_);
|
2015-01-21 17:43:56 +01:00
|
|
|
}
|
|
|
|
|
2015-02-24 07:21:10 +01:00
|
|
|
nghttp2_session_callbacks *create_http2_upstream_callbacks() {
|
2014-08-22 13:59:50 +02:00
|
|
|
int rv;
|
|
|
|
nghttp2_session_callbacks *callbacks;
|
|
|
|
|
2015-02-24 07:21:10 +01:00
|
|
|
rv = nghttp2_session_callbacks_new(&callbacks);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2015-02-24 07:21:10 +01:00
|
|
|
if (rv != 0) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_stream_close_callback(
|
|
|
|
callbacks, on_stream_close_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
|
|
|
|
on_frame_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
|
|
|
|
callbacks, on_data_chunk_recv_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
|
|
|
|
on_frame_send_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_frame_not_send_callback(
|
|
|
|
callbacks, on_frame_not_send_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2016-03-12 07:05:20 +01:00
|
|
|
nghttp2_session_callbacks_set_on_header_callback2(callbacks,
|
|
|
|
on_header_callback2);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2016-08-27 17:49:38 +02:00
|
|
|
nghttp2_session_callbacks_set_on_invalid_header_callback2(
|
|
|
|
callbacks, on_invalid_header_callback2);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_on_begin_headers_callback(
|
|
|
|
callbacks, on_begin_headers_callback);
|
2014-08-22 13:59:50 +02:00
|
|
|
|
2015-10-03 04:10:07 +02:00
|
|
|
nghttp2_session_callbacks_set_send_data_callback(callbacks,
|
|
|
|
send_data_callback);
|
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
|
|
|
|
if (config->padding) {
|
2014-11-27 15:39:04 +01:00
|
|
|
nghttp2_session_callbacks_set_select_padding_callback(
|
|
|
|
callbacks, http::select_padding_callback);
|
2014-02-11 09:23:22 +01:00
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
if (config->http2.upstream.debug.frame_debug) {
|
2017-11-19 08:47:39 +01:00
|
|
|
nghttp2_session_callbacks_set_error_callback2(callbacks,
|
|
|
|
verbose_error_callback);
|
2016-03-13 10:21:07 +01:00
|
|
|
}
|
|
|
|
|
2015-02-24 07:21:10 +01:00
|
|
|
return callbacks;
|
|
|
|
}
|
|
|
|
|
2016-06-03 12:13:02 +02:00
|
|
|
namespace {
|
|
|
|
size_t downstream_queue_size(Worker *worker) {
|
2016-06-04 09:23:50 +02:00
|
|
|
auto &downstreamconf = *worker->get_downstream_config();
|
2016-06-03 12:13:02 +02:00
|
|
|
|
|
|
|
if (get_config()->http2_proxy) {
|
2016-06-04 09:23:50 +02:00
|
|
|
return downstreamconf.connections_per_host;
|
2016-06-03 12:13:02 +02:00
|
|
|
}
|
|
|
|
|
2016-06-04 09:23:50 +02:00
|
|
|
return downstreamconf.connections_per_frontend;
|
2016-06-03 12:13:02 +02:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-02-24 07:21:10 +01:00
|
|
|
Http2Upstream::Http2Upstream(ClientHandler *handler)
|
2016-01-26 15:04:53 +01:00
|
|
|
: wb_(handler->get_worker()->get_mcpool()),
|
2016-06-03 12:13:02 +02:00
|
|
|
downstream_queue_(downstream_queue_size(handler->get_worker()),
|
|
|
|
!get_config()->http2_proxy),
|
2016-01-27 13:14:07 +01:00
|
|
|
handler_(handler),
|
2016-09-08 15:49:36 +02:00
|
|
|
session_(nullptr),
|
2017-02-20 15:36:50 +01:00
|
|
|
max_buffer_size_(MAX_BUFFER_SIZE),
|
|
|
|
num_requests_(0) {
|
2015-02-24 07:21:10 +01:00
|
|
|
int rv;
|
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
auto &http2conf = config->http2;
|
2016-01-18 09:00:20 +01:00
|
|
|
|
2016-06-02 16:47:41 +02:00
|
|
|
auto faddr = handler_->get_upstream_addr();
|
|
|
|
|
2018-10-17 01:38:55 +02:00
|
|
|
rv =
|
|
|
|
nghttp2_session_server_new2(&session_, http2conf.upstream.callbacks, this,
|
|
|
|
faddr->alt_mode != UpstreamAltMode::NONE
|
|
|
|
? http2conf.upstream.alt_mode_option
|
|
|
|
: http2conf.upstream.option);
|
2014-04-04 14:57:47 +02:00
|
|
|
|
2013-07-26 12:33:25 +02:00
|
|
|
assert(rv == 0);
|
|
|
|
|
|
|
|
flow_control_ = true;
|
2012-06-09 18:36:30 +02:00
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
// TODO Maybe call from outside?
|
2022-06-15 16:43:03 +02:00
|
|
|
std::array<nghttp2_settings_entry, 5> entry;
|
|
|
|
size_t nentry = 3;
|
2016-09-12 15:53:02 +02:00
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
|
2016-02-27 16:06:40 +01:00
|
|
|
entry[0].value = http2conf.upstream.max_concurrent_streams;
|
2012-06-09 18:36:30 +02:00
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
2018-10-17 01:38:55 +02:00
|
|
|
if (faddr->alt_mode != UpstreamAltMode::NONE) {
|
2016-06-02 16:47:41 +02:00
|
|
|
entry[1].value = (1u << 31) - 1;
|
|
|
|
} else {
|
2016-09-09 14:05:37 +02:00
|
|
|
entry[1].value = http2conf.upstream.window_size;
|
2016-06-02 16:47:41 +02:00
|
|
|
}
|
2012-06-09 18:36:30 +02:00
|
|
|
|
2022-06-15 16:43:03 +02:00
|
|
|
entry[2].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
|
|
|
|
entry[2].value = 1;
|
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
if (!config->http2_proxy) {
|
|
|
|
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL;
|
|
|
|
entry[nentry].value = 1;
|
|
|
|
++nentry;
|
|
|
|
}
|
|
|
|
|
2016-09-12 15:53:02 +02:00
|
|
|
if (http2conf.upstream.decoder_dynamic_table_size !=
|
|
|
|
NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
|
|
|
|
entry[nentry].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
|
|
|
|
entry[nentry].value = http2conf.upstream.decoder_dynamic_table_size;
|
|
|
|
++nentry;
|
|
|
|
}
|
|
|
|
|
2015-02-05 16:06:01 +01:00
|
|
|
rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
|
2016-09-12 15:53:02 +02:00
|
|
|
nentry);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-04-03 06:20:50 +02:00
|
|
|
ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
}
|
2013-11-20 16:15:17 +01:00
|
|
|
|
2021-08-04 08:02:20 +02:00
|
|
|
auto window_size = faddr->alt_mode != UpstreamAltMode::NONE
|
|
|
|
? std::numeric_limits<int32_t>::max()
|
|
|
|
: http2conf.upstream.optimize_window_size
|
|
|
|
? std::min(http2conf.upstream.connection_window_size,
|
|
|
|
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE)
|
|
|
|
: http2conf.upstream.connection_window_size;
|
2016-06-02 16:47:41 +02:00
|
|
|
|
2016-09-09 14:05:37 +02:00
|
|
|
rv = nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
|
|
|
|
window_size);
|
2014-04-03 06:20:50 +02:00
|
|
|
|
2016-09-09 14:05:37 +02:00
|
|
|
if (rv != 0) {
|
|
|
|
ULOG(ERROR, this)
|
|
|
|
<< "nghttp2_session_set_local_window_size() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2014-04-03 06:20:50 +02:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
// We wait for SETTINGS ACK at least 10 seconds.
|
2016-05-21 07:13:57 +02:00
|
|
|
ev_timer_init(&settings_timer_, settings_timeout_cb,
|
|
|
|
http2conf.upstream.timeout.settings, 0.);
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
settings_timer_.data = this;
|
|
|
|
|
2015-01-21 17:43:56 +01:00
|
|
|
// timer for 2nd GOAWAY. HTTP/2 spec recommend 1 RTT. We wait for
|
|
|
|
// 2 seconds.
|
|
|
|
ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 2., 0);
|
|
|
|
shutdown_timer_.data = this;
|
|
|
|
|
|
|
|
ev_prepare_init(&prep_, prepare_cb);
|
|
|
|
prep_.data = this;
|
|
|
|
ev_prepare_start(handler_->get_loop(), &prep_);
|
|
|
|
|
2016-09-08 15:49:36 +02:00
|
|
|
#if defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
|
|
|
|
if (http2conf.upstream.optimize_write_buffer_size) {
|
|
|
|
auto conn = handler_->get_connection();
|
|
|
|
conn->tls_dyn_rec_warmup_threshold = 0;
|
|
|
|
|
|
|
|
uint32_t pollout_thres = 1;
|
|
|
|
rv = setsockopt(conn->fd, IPPROTO_TCP, TCP_NOTSENT_LOWAT, &pollout_thres,
|
|
|
|
static_cast<socklen_t>(sizeof(pollout_thres)));
|
|
|
|
|
|
|
|
if (rv != 0) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
auto error = errno;
|
|
|
|
LOG(INFO) << "setsockopt(TCP_NOTSENT_LOWAT, " << pollout_thres
|
|
|
|
<< ") failed: errno=" << error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // defined(TCP_INFO) && defined(TCP_NOTSENT_LOWAT)
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->reset_upstream_read_timeout(
|
2016-10-08 04:34:23 +02:00
|
|
|
config->conn.upstream.timeout.http2_read);
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
handler_->signal_write();
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
Http2Upstream::~Http2Upstream() {
|
2013-07-12 17:19:03 +02:00
|
|
|
nghttp2_session_del(session_);
|
2015-01-21 17:43:56 +01:00
|
|
|
ev_prepare_stop(handler_->get_loop(), &prep_);
|
|
|
|
ev_timer_stop(handler_->get_loop(), &shutdown_timer_);
|
2014-12-27 18:59:06 +01:00
|
|
|
ev_timer_stop(handler_->get_loop(), &settings_timer_);
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::on_read() {
|
2014-01-18 08:50:52 +01:00
|
|
|
ssize_t rv = 0;
|
2014-12-27 18:59:06 +01:00
|
|
|
auto rb = handler_->get_rb();
|
2015-02-13 14:41:50 +01:00
|
|
|
auto rlimit = handler_->get_rlimit();
|
2014-01-18 08:50:52 +01:00
|
|
|
|
2015-01-29 14:47:37 +01:00
|
|
|
if (rb->rleft()) {
|
2017-01-08 08:41:02 +01:00
|
|
|
rv = nghttp2_session_mem_recv(session_, rb->pos(), rb->rleft());
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv < 0) {
|
2015-04-05 15:35:40 +02:00
|
|
|
if (rv != NGHTTP2_ERR_BAD_CLIENT_MAGIC) {
|
2016-05-21 03:29:11 +02:00
|
|
|
ULOG(ERROR, this) << "nghttp2_session_mem_recv() returned error: "
|
2015-02-01 16:20:44 +01:00
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
}
|
2014-06-01 14:01:01 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-01-29 14:47:37 +01:00
|
|
|
// nghttp2_session_mem_recv should consume all input bytes on
|
|
|
|
// success.
|
|
|
|
assert(static_cast<size_t>(rv) == rb->rleft());
|
|
|
|
rb->reset();
|
2015-02-13 14:41:50 +01:00
|
|
|
rlimit->startw();
|
2014-01-18 08:50:52 +01:00
|
|
|
}
|
2012-06-04 20:11:43 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
if (nghttp2_session_want_read(session_) == 0 &&
|
2015-10-02 15:42:46 +02:00
|
|
|
nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
|
2014-12-27 18:59:06 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
|
|
|
}
|
2014-12-23 09:38:03 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->signal_write();
|
2014-12-23 09:38:03 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// After this function call, downstream may be deleted.
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Upstream::on_write() {
|
2016-09-08 15:49:36 +02:00
|
|
|
int rv;
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
auto &http2conf = config->http2;
|
2016-09-08 15:49:36 +02:00
|
|
|
|
|
|
|
if ((http2conf.upstream.optimize_write_buffer_size ||
|
|
|
|
http2conf.upstream.optimize_window_size) &&
|
|
|
|
handler_->get_ssl()) {
|
|
|
|
auto conn = handler_->get_connection();
|
|
|
|
TCPHint hint;
|
|
|
|
rv = conn->get_tcp_hint(&hint);
|
|
|
|
if (rv == 0) {
|
|
|
|
if (http2conf.upstream.optimize_write_buffer_size) {
|
|
|
|
max_buffer_size_ = std::min(MAX_BUFFER_SIZE, hint.write_buffer_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (http2conf.upstream.optimize_window_size) {
|
|
|
|
auto faddr = handler_->get_upstream_addr();
|
2018-10-17 01:38:55 +02:00
|
|
|
if (faddr->alt_mode == UpstreamAltMode::NONE) {
|
2016-09-09 14:05:37 +02:00
|
|
|
auto window_size = std::min(http2conf.upstream.connection_window_size,
|
|
|
|
static_cast<int32_t>(hint.rwin * 2));
|
2016-09-08 15:49:36 +02:00
|
|
|
|
|
|
|
rv = nghttp2_session_set_local_window_size(
|
|
|
|
session_, NGHTTP2_FLAG_NONE, 0, window_size);
|
|
|
|
if (rv != 0) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this)
|
|
|
|
<< "nghttp2_session_set_local_window_size() with window_size="
|
|
|
|
<< window_size << " failed: " << nghttp2_strerror(rv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
for (;;) {
|
2016-09-08 15:49:36 +02:00
|
|
|
if (wb_.rleft() >= max_buffer_size_) {
|
2015-10-04 03:36:20 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-02-18 15:23:11 +01:00
|
|
|
const uint8_t *data;
|
|
|
|
auto datalen = nghttp2_session_mem_send(session_, &data);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (datalen < 0) {
|
2014-02-18 15:23:11 +01:00
|
|
|
ULOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
|
|
|
|
<< nghttp2_strerror(datalen);
|
|
|
|
return -1;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (datalen == 0) {
|
2014-02-18 15:23:11 +01:00
|
|
|
break;
|
|
|
|
}
|
2016-01-26 15:04:53 +01:00
|
|
|
wb_.append(data, datalen);
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-03-03 13:18:24 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (nghttp2_session_want_read(session_) == 0 &&
|
2015-10-02 15:42:46 +02:00
|
|
|
nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-02-18 15:23:11 +01:00
|
|
|
ULOG(INFO, this) << "No more read/write for this HTTP2 session";
|
2012-07-18 18:59:55 +02:00
|
|
|
}
|
2014-02-18 15:23:11 +01:00
|
|
|
return -1;
|
2012-07-18 18:59:55 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
2014-02-18 15:23:11 +01:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
|
2013-08-31 17:23:07 +02:00
|
|
|
auto downstream = dconn->get_downstream();
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_RESET) {
|
2013-02-09 15:20:29 +01:00
|
|
|
// The downstream stream was reset (canceled). In this case,
|
|
|
|
// RST_STREAM to the upstream and delete downstream connection
|
|
|
|
// here. Deleting downstream will be taken place at
|
|
|
|
// on_stream_close_callback.
|
2014-12-27 18:59:06 +01:00
|
|
|
rst_stream(downstream,
|
|
|
|
infer_upstream_rst_stream_error_code(
|
|
|
|
downstream->get_response_rst_stream_error_code()));
|
2014-08-18 17:16:51 +02:00
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
// dconn was deleted
|
2014-06-01 16:44:32 +02:00
|
|
|
dconn = nullptr;
|
2018-10-17 03:03:32 +02:00
|
|
|
} else if (downstream->get_response_state() ==
|
|
|
|
DownstreamState::MSG_BAD_HEADER) {
|
2015-01-19 15:44:23 +01:00
|
|
|
if (error_reply(downstream, 502) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
// dconn was deleted
|
|
|
|
dconn = nullptr;
|
2013-02-27 14:39:44 +01:00
|
|
|
} else {
|
2014-06-01 16:44:32 +02:00
|
|
|
auto rv = downstream->on_read();
|
2015-02-04 13:15:58 +01:00
|
|
|
if (rv == SHRPX_ERR_EOF) {
|
2019-09-07 11:20:24 +02:00
|
|
|
if (downstream->get_request_header_sent()) {
|
|
|
|
return downstream_eof(dconn);
|
|
|
|
}
|
|
|
|
return SHRPX_ERR_RETRY;
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
2015-09-03 19:46:35 +02:00
|
|
|
if (rv == SHRPX_ERR_DCONN_CANCELED) {
|
|
|
|
downstream->pop_downstream_connection();
|
2015-09-04 15:34:40 +02:00
|
|
|
handler_->signal_write();
|
2015-09-03 19:46:35 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2015-02-04 13:15:58 +01:00
|
|
|
if (rv != SHRPX_ERR_NETWORK) {
|
2014-12-27 18:59:06 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
DCLOG(INFO, dconn) << "HTTP parser failure";
|
2013-02-27 14:39:44 +01:00
|
|
|
}
|
2012-07-16 16:29:48 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2015-07-22 14:41:16 +02:00
|
|
|
|
|
|
|
if (downstream->can_detach_downstream_connection()) {
|
2015-01-30 17:11:55 +01:00
|
|
|
// Keep-alive
|
|
|
|
downstream->detach_downstream_connection();
|
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
handler_->signal_write();
|
|
|
|
|
2012-06-12 14:56:41 +02:00
|
|
|
// At this point, downstream may be deleted.
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Upstream::downstream_write(DownstreamConnection *dconn) {
|
|
|
|
int rv;
|
|
|
|
rv = dconn->on_write();
|
2015-02-04 13:15:58 +01:00
|
|
|
if (rv == SHRPX_ERR_NETWORK) {
|
2014-12-27 18:59:06 +01:00
|
|
|
return downstream_error(dconn, Downstream::EVENT_ERROR);
|
2012-11-21 19:13:30 +01:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
if (rv != 0) {
|
2016-12-25 13:22:34 +01:00
|
|
|
return rv;
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
|
2013-08-31 17:23:07 +02:00
|
|
|
auto downstream = dconn->get_downstream();
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
|
2014-06-01 16:44:32 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
// Delete downstream connection. If we don't delete it here, it will
|
|
|
|
// be pooled in on_stream_close_callback.
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
// dconn was deleted
|
|
|
|
dconn = nullptr;
|
2022-01-13 21:21:56 +01:00
|
|
|
// downstream will be deleted in on_stream_close_callback.
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
|
2014-12-27 18:59:06 +01:00
|
|
|
// Server may indicate the end of the request by EOF
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-12-27 18:59:06 +01:00
|
|
|
ULOG(INFO, this) << "Downstream body was ended by EOF";
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_response_state(DownstreamState::MSG_COMPLETE);
|
2014-12-27 18:59:06 +01:00
|
|
|
|
|
|
|
// For tunneled connection, MSG_COMPLETE signals
|
|
|
|
// downstream_data_read_callback to send RST_STREAM after pending
|
|
|
|
// response body is sent. This is needed to ensure that RST_STREAM
|
|
|
|
// is sent after all pending data are sent.
|
|
|
|
on_downstream_body_complete(downstream);
|
2018-10-17 03:03:32 +02:00
|
|
|
} else if (downstream->get_response_state() !=
|
|
|
|
DownstreamState::MSG_COMPLETE) {
|
2014-12-27 18:59:06 +01:00
|
|
|
// If stream was not closed, then we set MSG_COMPLETE and let
|
|
|
|
// on_stream_close_callback delete downstream.
|
|
|
|
if (error_reply(downstream, 502) != 0) {
|
|
|
|
return -1;
|
2014-06-01 16:44:32 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
}
|
|
|
|
handler_->signal_write();
|
|
|
|
// At this point, downstream may be deleted.
|
|
|
|
return 0;
|
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
|
|
|
|
auto downstream = dconn->get_downstream();
|
|
|
|
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
if (events & Downstream::EVENT_ERROR) {
|
|
|
|
DCLOG(INFO, dconn) << "Downstream network/general error";
|
|
|
|
} else {
|
|
|
|
DCLOG(INFO, dconn) << "Timeout";
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
if (downstream->get_upgraded()) {
|
|
|
|
DCLOG(INFO, dconn) << "Note: this is tunnel connection";
|
2014-06-01 16:44:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
// Delete downstream connection. If we don't delete it here, it will
|
|
|
|
// be pooled in on_stream_close_callback.
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
// dconn was deleted
|
|
|
|
dconn = nullptr;
|
2014-06-01 16:44:32 +02:00
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2014-12-27 18:59:06 +01:00
|
|
|
// For SSL tunneling, we issue RST_STREAM. For other types of
|
|
|
|
// stream, we don't have to do anything since response was
|
|
|
|
// complete.
|
|
|
|
if (downstream->get_upgraded()) {
|
|
|
|
rst_stream(downstream, NGHTTP2_NO_ERROR);
|
|
|
|
}
|
|
|
|
} else {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::HEADER_COMPLETE) {
|
2014-11-27 15:39:04 +01:00
|
|
|
if (downstream->get_upgraded()) {
|
2014-12-27 18:59:06 +01:00
|
|
|
on_downstream_body_complete(downstream);
|
|
|
|
} else {
|
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
2014-06-01 16:44:32 +02:00
|
|
|
}
|
2012-06-04 20:11:43 +02:00
|
|
|
} else {
|
2014-12-27 18:59:06 +01:00
|
|
|
unsigned int status;
|
|
|
|
if (events & Downstream::EVENT_TIMEOUT) {
|
2019-09-02 17:29:01 +02:00
|
|
|
if (downstream->get_request_header_sent()) {
|
|
|
|
status = 504;
|
|
|
|
} else {
|
|
|
|
status = 408;
|
|
|
|
}
|
2012-07-11 09:20:16 +02:00
|
|
|
} else {
|
2014-12-27 18:59:06 +01:00
|
|
|
status = 502;
|
|
|
|
}
|
|
|
|
if (error_reply(downstream, status) != 0) {
|
|
|
|
return -1;
|
2012-07-16 16:29:48 +02:00
|
|
|
}
|
2014-06-01 16:44:32 +02:00
|
|
|
}
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_response_state(DownstreamState::MSG_COMPLETE);
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->signal_write();
|
|
|
|
// At this point, downstream may be deleted.
|
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id()
|
|
|
|
<< " with error_code=" << error_code;
|
2012-06-09 18:36:30 +02:00
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
int rv;
|
2013-10-25 15:50:24 +02:00
|
|
|
rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
|
|
|
|
downstream->get_stream_id(), error_code);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv < NGHTTP2_ERR_FATAL) {
|
2013-07-12 17:19:03 +02:00
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2017-03-15 15:13:54 +01:00
|
|
|
return -1;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2012-07-27 15:11:13 +02:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::terminate_session(uint32_t error_code) {
|
2013-10-30 16:44:23 +01:00
|
|
|
int rv;
|
2013-12-25 16:23:07 +01:00
|
|
|
rv = nghttp2_session_terminate_session(session_, error_code);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2013-10-30 16:44:23 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
namespace {
|
2013-11-04 09:53:57 +01:00
|
|
|
ssize_t downstream_data_read_callback(nghttp2_session *session,
|
2014-11-27 15:39:04 +01:00
|
|
|
int32_t stream_id, uint8_t *buf,
|
|
|
|
size_t length, uint32_t *data_flags,
|
2013-11-04 09:53:57 +01:00
|
|
|
nghttp2_data_source *source,
|
2014-11-27 15:39:04 +01:00
|
|
|
void *user_data) {
|
2015-03-08 08:48:25 +01:00
|
|
|
int rv;
|
2014-11-27 15:39:04 +01:00
|
|
|
auto downstream = static_cast<Downstream *>(source->ptr);
|
2014-12-27 18:59:06 +01:00
|
|
|
auto body = downstream->get_response_buf();
|
2012-06-04 16:48:31 +02:00
|
|
|
assert(body);
|
2016-09-08 15:49:36 +02:00
|
|
|
auto upstream = static_cast<Http2Upstream *>(user_data);
|
2014-04-03 11:54:15 +02:00
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
const auto &resp = downstream->response();
|
2015-01-17 09:37:32 +01:00
|
|
|
|
2015-10-03 04:10:07 +02:00
|
|
|
auto nread = std::min(body->rleft(), length);
|
2016-09-08 15:49:36 +02:00
|
|
|
|
|
|
|
auto max_buffer_size = upstream->get_max_buffer_size();
|
|
|
|
|
|
|
|
auto buffer = upstream->get_response_buf();
|
|
|
|
|
|
|
|
if (max_buffer_size <
|
|
|
|
std::min(nread, static_cast<size_t>(256)) + 9 + buffer->rleft()) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, upstream) << "Buffer is almost full. Skip write DATA";
|
|
|
|
}
|
|
|
|
return NGHTTP2_ERR_PAUSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
nread = std::min(nread, max_buffer_size - 9 - buffer->rleft());
|
|
|
|
|
2015-10-03 04:10:07 +02:00
|
|
|
auto body_empty = body->rleft() == nread;
|
|
|
|
|
|
|
|
*data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
|
2014-11-22 09:32:59 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (body_empty &&
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2014-08-09 11:47:45 +02:00
|
|
|
|
2014-11-17 16:03:52 +01:00
|
|
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (!downstream->get_upgraded()) {
|
2016-01-13 16:37:45 +01:00
|
|
|
const auto &trailers = resp.fs.trailers();
|
2015-03-08 08:48:25 +01:00
|
|
|
if (!trailers.empty()) {
|
|
|
|
std::vector<nghttp2_nv> nva;
|
|
|
|
nva.reserve(trailers.size());
|
2017-04-25 16:41:56 +02:00
|
|
|
http2::copy_headers_to_nva_nocopy(nva, trailers, http2::HDOP_STRIP_ALL);
|
2015-03-08 09:32:01 +01:00
|
|
|
if (!nva.empty()) {
|
|
|
|
rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
|
|
|
|
nva.size());
|
|
|
|
if (rv != 0) {
|
|
|
|
if (nghttp2_is_fatal(rv)) {
|
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
|
2015-03-08 08:48:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-02-10 18:05:11 +01:00
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-05-16 14:42:30 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (nread == 0 && ((*data_flags) & NGHTTP2_DATA_FLAG_EOF) == 0) {
|
2017-02-20 14:08:39 +01:00
|
|
|
downstream->disable_upstream_wtimer();
|
2014-08-21 14:22:16 +02:00
|
|
|
return NGHTTP2_ERR_DEFERRED;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-08-21 14:22:16 +02:00
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
return nread;
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2015-09-03 15:29:16 +02:00
|
|
|
int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|
|
|
size_t bodylen) {
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
nghttp2_data_provider data_prd, *data_prd_ptr = nullptr;
|
|
|
|
|
|
|
|
if (bodylen) {
|
|
|
|
data_prd.source.ptr = downstream;
|
|
|
|
data_prd.read_callback = downstream_data_read_callback;
|
|
|
|
data_prd_ptr = &data_prd;
|
|
|
|
}
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
const auto &resp = downstream->response();
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
auto &httpconf = config->http;
|
2016-01-13 16:37:45 +01:00
|
|
|
|
2016-03-12 10:00:50 +01:00
|
|
|
auto &balloc = downstream->get_block_allocator();
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
const auto &headers = resp.fs.headers();
|
2015-09-03 19:46:35 +02:00
|
|
|
auto nva = std::vector<nghttp2_nv>();
|
2015-09-03 15:29:16 +02:00
|
|
|
// 2 for :status and server
|
2016-02-13 14:31:38 +01:00
|
|
|
nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
|
2015-09-03 19:46:35 +02:00
|
|
|
|
2016-03-19 15:41:21 +01:00
|
|
|
auto response_status = http2::stringify_status(balloc, resp.http_status);
|
2015-09-03 15:29:16 +02:00
|
|
|
|
2016-03-12 10:00:50 +01:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
|
|
|
|
2015-09-03 15:29:16 +02:00
|
|
|
for (auto &kv : headers) {
|
|
|
|
if (kv.name.empty() || kv.name[0] == ':') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (kv.token) {
|
|
|
|
case http2::HD_CONNECTION:
|
|
|
|
case http2::HD_KEEP_ALIVE:
|
|
|
|
case http2::HD_PROXY_CONNECTION:
|
|
|
|
case http2::HD_TE:
|
|
|
|
case http2::HD_TRANSFER_ENCODING:
|
|
|
|
case http2::HD_UPGRADE:
|
|
|
|
continue;
|
|
|
|
}
|
2015-11-05 14:48:54 +01:00
|
|
|
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
|
2015-09-03 15:29:16 +02:00
|
|
|
}
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
if (!resp.fs.header(http2::HD_SERVER)) {
|
2016-10-08 04:34:23 +02:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("server", config->http.server_name));
|
2015-09-03 15:29:16 +02:00
|
|
|
}
|
|
|
|
|
2016-02-13 14:31:38 +01:00
|
|
|
for (auto &p : httpconf.add_response_headers) {
|
|
|
|
nva.push_back(http2::make_nv_nocopy(p.name, p.value));
|
|
|
|
}
|
|
|
|
|
2015-09-03 15:29:16 +02:00
|
|
|
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
|
|
|
|
nva.data(), nva.size(), data_prd_ptr);
|
|
|
|
if (nghttp2_is_fatal(rv)) {
|
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto buf = downstream->get_response_buf();
|
|
|
|
|
|
|
|
buf->append(body, bodylen);
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_response_state(DownstreamState::MSG_COMPLETE);
|
2015-09-03 15:29:16 +02:00
|
|
|
|
2017-02-20 14:08:39 +01:00
|
|
|
if (data_prd_ptr) {
|
|
|
|
downstream->reset_upstream_wtimer();
|
|
|
|
}
|
|
|
|
|
2015-09-03 15:29:16 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-09-08 07:29:18 +02:00
|
|
|
int Http2Upstream::error_reply(Downstream *downstream,
|
2014-11-27 15:39:04 +01:00
|
|
|
unsigned int status_code) {
|
2012-06-04 16:48:31 +02:00
|
|
|
int rv;
|
2016-01-13 16:37:45 +01:00
|
|
|
auto &resp = downstream->response();
|
|
|
|
|
2016-03-12 10:00:50 +01:00
|
|
|
auto &balloc = downstream->get_block_allocator();
|
|
|
|
|
2016-03-19 15:41:21 +01:00
|
|
|
auto html = http::create_error_html(balloc, status_code);
|
2016-01-13 16:37:45 +01:00
|
|
|
resp.http_status = status_code;
|
2014-12-27 18:59:06 +01:00
|
|
|
auto body = downstream->get_response_buf();
|
2016-01-17 09:04:16 +01:00
|
|
|
body->append(html);
|
2018-10-17 03:03:32 +02:00
|
|
|
downstream->set_response_state(DownstreamState::MSG_COMPLETE);
|
2012-07-27 15:11:13 +02:00
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
nghttp2_data_provider data_prd;
|
2012-06-04 16:48:31 +02:00
|
|
|
data_prd.source.ptr = downstream;
|
2013-11-04 09:53:57 +01:00
|
|
|
data_prd.read_callback = downstream_data_read_callback;
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2015-09-07 16:11:23 +02:00
|
|
|
auto lgconf = log_config();
|
|
|
|
lgconf->update_tstamp(std::chrono::system_clock::now());
|
|
|
|
|
2016-03-19 15:41:21 +01:00
|
|
|
auto response_status = http2::stringify_status(balloc, status_code);
|
2016-03-12 10:00:50 +01:00
|
|
|
auto content_length = util::make_string_ref_uint(balloc, html.size());
|
2017-01-11 12:22:55 +01:00
|
|
|
auto date = make_string_ref(balloc, lgconf->tstamp->time_http);
|
2015-11-05 14:48:54 +01:00
|
|
|
|
2016-03-12 10:00:50 +01:00
|
|
|
auto nva = std::array<nghttp2_nv, 5>{
|
|
|
|
{http2::make_nv_ls_nocopy(":status", response_status),
|
|
|
|
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
|
2016-10-02 14:22:02 +02:00
|
|
|
http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
|
2016-03-12 10:00:50 +01:00
|
|
|
http2::make_nv_ls_nocopy("content-length", content_length),
|
|
|
|
http2::make_nv_ls_nocopy("date", date)}};
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2013-12-08 13:19:33 +01:00
|
|
|
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
|
|
|
|
nva.data(), nva.size(), &data_prd);
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv < NGHTTP2_ERR_FATAL) {
|
2013-07-12 17:19:03 +02:00
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2015-01-21 14:55:00 +01:00
|
|
|
return -1;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-07-02 16:56:26 +02:00
|
|
|
|
2017-02-20 14:08:39 +01:00
|
|
|
downstream->reset_upstream_wtimer();
|
|
|
|
|
2012-07-27 15:11:13 +02:00
|
|
|
return 0;
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2015-11-12 16:53:29 +01:00
|
|
|
void Http2Upstream::add_pending_downstream(
|
|
|
|
std::unique_ptr<Downstream> downstream) {
|
2014-08-18 15:59:31 +02:00
|
|
|
downstream_queue_.add_pending(std::move(downstream));
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Upstream::remove_downstream(Downstream *downstream) {
|
|
|
|
if (downstream->accesslog_ready()) {
|
2014-11-18 16:56:44 +01:00
|
|
|
handler_->write_accesslog(downstream);
|
|
|
|
}
|
|
|
|
|
2015-03-11 16:17:05 +01:00
|
|
|
nghttp2_session_set_stream_user_data(session_, downstream->get_stream_id(),
|
|
|
|
nullptr);
|
|
|
|
|
|
|
|
auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream);
|
2014-08-16 14:29:20 +02:00
|
|
|
|
2014-12-04 17:07:00 +01:00
|
|
|
if (next_downstream) {
|
2015-03-11 16:17:05 +01:00
|
|
|
initiate_downstream(next_downstream);
|
2014-12-04 17:07:00 +01:00
|
|
|
}
|
2016-06-23 17:04:39 +02:00
|
|
|
|
|
|
|
if (downstream_queue_.get_downstreams() == nullptr) {
|
|
|
|
// There is no downstream at the moment. Start idle timer now.
|
|
|
|
handler_->repeat_read_timer();
|
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
// WARNING: Never call directly or indirectly nghttp2_session_send or
|
|
|
|
// nghttp2_session_recv. These calls may delete downstream.
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
|
2014-07-23 16:32:57 +02:00
|
|
|
int rv;
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
const auto &req = downstream->request();
|
2016-01-13 16:37:45 +01:00
|
|
|
auto &resp = downstream->response();
|
2016-01-13 14:45:52 +01:00
|
|
|
|
2016-03-12 10:00:50 +01:00
|
|
|
auto &balloc = downstream->get_block_allocator();
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
if (downstream->get_non_final_response()) {
|
2014-07-23 16:32:57 +02:00
|
|
|
DLOG(INFO, downstream) << "HTTP non-final response header";
|
|
|
|
} else {
|
|
|
|
DLOG(INFO, downstream) << "HTTP response header completed";
|
|
|
|
}
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
auto &httpconf = config->http;
|
2016-01-18 09:00:20 +01:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
if (!config->http2_proxy && !httpconf.no_location_rewrite) {
|
2016-01-13 14:45:52 +01:00
|
|
|
downstream->rewrite_location_response_header(req.scheme);
|
2013-12-28 09:02:43 +01:00
|
|
|
}
|
2014-07-12 11:55:08 +02:00
|
|
|
|
2015-09-03 17:54:41 +02:00
|
|
|
#ifdef HAVE_MRUBY
|
2015-09-02 18:32:15 +02:00
|
|
|
if (!downstream->get_non_final_response()) {
|
2018-08-21 14:11:07 +02:00
|
|
|
auto dconn = downstream->get_downstream_connection();
|
|
|
|
const auto &group = dconn->get_downstream_addr_group();
|
2018-08-24 16:07:16 +02:00
|
|
|
if (group) {
|
2019-09-16 15:25:06 +02:00
|
|
|
const auto &dmruby_ctx = group->shared_addr->mruby_ctx;
|
2018-08-21 14:11:07 +02:00
|
|
|
|
2018-08-24 16:07:16 +02:00
|
|
|
if (dmruby_ctx->run_on_response_proc(downstream) != 0) {
|
|
|
|
if (error_reply(downstream, 500) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
// Returning -1 will signal deletion of dconn.
|
2018-08-21 14:11:07 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2018-08-24 16:07:16 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2018-08-21 14:11:07 +02:00
|
|
|
}
|
|
|
|
|
2015-09-02 18:32:15 +02:00
|
|
|
auto worker = handler_->get_worker();
|
|
|
|
auto mruby_ctx = worker->get_mruby_context();
|
|
|
|
|
|
|
|
if (mruby_ctx->run_on_response_proc(downstream) != 0) {
|
|
|
|
if (error_reply(downstream, 500) != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2015-09-12 11:33:07 +02:00
|
|
|
// Returning -1 will signal deletion of dconn.
|
2015-09-02 18:32:15 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2015-09-02 18:32:15 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2015-09-03 17:54:41 +02:00
|
|
|
#endif // HAVE_MRUBY
|
2015-09-02 18:32:15 +02:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
auto &http2conf = config->http2;
|
2016-04-10 11:58:54 +02:00
|
|
|
|
|
|
|
// We need some conditions that must be fulfilled to initiate server
|
|
|
|
// push.
|
|
|
|
//
|
|
|
|
// * Server push is disabled for http2 proxy or client proxy, since
|
|
|
|
// incoming headers are mixed origins. We don't know how to
|
|
|
|
// reliably determine the authority yet.
|
|
|
|
//
|
|
|
|
// * We need non-final response or 200 response code for associated
|
|
|
|
// resource. This is too restrictive, we will review this later.
|
|
|
|
//
|
|
|
|
// * We requires GET or POST for associated resource. Probably we
|
|
|
|
// don't want to push for HEAD request. Not sure other methods
|
|
|
|
// are also eligible for push.
|
|
|
|
if (!http2conf.no_server_push &&
|
|
|
|
nghttp2_session_get_remote_settings(session_,
|
|
|
|
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
|
2016-10-08 04:34:23 +02:00
|
|
|
!config->http2_proxy && (downstream->get_stream_id() % 2) &&
|
2016-04-10 11:58:54 +02:00
|
|
|
resp.fs.header(http2::HD_LINK) &&
|
|
|
|
(downstream->get_non_final_response() || resp.http_status == 200) &&
|
|
|
|
(req.method == HTTP_GET || req.method == HTTP_POST)) {
|
|
|
|
|
|
|
|
if (prepare_push_promise(downstream) != 0) {
|
|
|
|
// Continue to send response even if push was failed.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-28 13:36:04 +01:00
|
|
|
auto nva = std::vector<nghttp2_nv>();
|
2021-08-26 13:59:32 +02:00
|
|
|
// 6 means :status and possible server, via, x-http2-push, alt-svc,
|
|
|
|
// and set-cookie (for affinity cookie) header field.
|
|
|
|
nva.reserve(resp.fs.headers().size() + 6 +
|
2016-01-18 09:00:20 +01:00
|
|
|
httpconf.add_response_headers.size());
|
2015-11-05 14:48:54 +01:00
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
if (downstream->get_non_final_response()) {
|
|
|
|
auto response_status = http2::stringify_status(balloc, resp.http_status);
|
2013-08-27 17:09:46 +02:00
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
2016-03-12 10:00:50 +01:00
|
|
|
|
2017-04-25 16:41:56 +02:00
|
|
|
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(),
|
|
|
|
http2::HDOP_STRIP_ALL);
|
2015-11-13 15:59:36 +01:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-25 17:40:06 +02:00
|
|
|
log_response_headers(downstream, nva);
|
|
|
|
}
|
|
|
|
|
2014-07-23 16:32:57 +02:00
|
|
|
rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE,
|
|
|
|
downstream->get_stream_id(), nullptr,
|
|
|
|
nva.data(), nva.size(), nullptr);
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
resp.fs.clear_headers();
|
2014-07-23 16:32:57 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-07-23 16:32:57 +02:00
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_headers() failed";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
auto striphd_flags = http2::HDOP_STRIP_ALL & ~http2::HDOP_STRIP_VIA;
|
|
|
|
StringRef response_status;
|
|
|
|
|
2018-10-17 02:50:29 +02:00
|
|
|
if (req.connect_proto == ConnectProto::WEBSOCKET && resp.http_status == 101) {
|
2018-03-11 04:02:18 +01:00
|
|
|
response_status = http2::stringify_status(balloc, 200);
|
|
|
|
striphd_flags |= http2::HDOP_STRIP_SEC_WEBSOCKET_ACCEPT;
|
|
|
|
} else {
|
|
|
|
response_status = http2::stringify_status(balloc, resp.http_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
|
|
|
|
|
|
|
|
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers(), striphd_flags);
|
2015-11-13 15:59:36 +01:00
|
|
|
|
2016-10-08 04:34:23 +02:00
|
|
|
if (!config->http2_proxy && !httpconf.no_server_rewrite) {
|
2016-10-02 14:22:02 +02:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
|
2014-08-14 15:45:21 +02:00
|
|
|
} else {
|
2016-01-13 16:37:45 +01:00
|
|
|
auto server = resp.fs.header(http2::HD_SERVER);
|
2015-01-04 15:22:39 +01:00
|
|
|
if (server) {
|
2015-11-05 14:48:54 +01:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value));
|
2014-08-14 15:45:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 04:02:18 +01:00
|
|
|
if (!req.regular_connect_method() || !downstream->get_upgraded()) {
|
2017-10-25 17:45:22 +02:00
|
|
|
auto affinity_cookie = downstream->get_affinity_cookie_to_send();
|
|
|
|
if (affinity_cookie) {
|
|
|
|
auto dconn = downstream->get_downstream_connection();
|
|
|
|
assert(dconn);
|
|
|
|
auto &group = dconn->get_downstream_addr_group();
|
|
|
|
auto &shared_addr = group->shared_addr;
|
|
|
|
auto &cookieconf = shared_addr->affinity.cookie;
|
2017-11-21 14:24:38 +01:00
|
|
|
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);
|
2017-10-25 17:45:22 +02:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("set-cookie", cookie_str));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 13:59:32 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
auto via = resp.fs.header(http2::HD_VIA);
|
2016-01-18 09:00:20 +01:00
|
|
|
if (httpconf.no_via) {
|
2015-01-04 15:22:39 +01:00
|
|
|
if (via) {
|
2015-11-05 14:48:54 +01:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2013-08-27 17:09:46 +02:00
|
|
|
} else {
|
2016-03-12 10:36:05 +01:00
|
|
|
// we don't create more than 16 bytes in
|
|
|
|
// http::create_via_header_value.
|
|
|
|
size_t len = 16;
|
2015-01-04 15:22:39 +01:00
|
|
|
if (via) {
|
2016-03-12 10:36:05 +01:00
|
|
|
len += via->value.size() + 2;
|
2013-01-09 14:01:25 +01:00
|
|
|
}
|
2016-03-12 10:36:05 +01:00
|
|
|
|
|
|
|
auto iov = make_byte_ref(balloc, len + 1);
|
|
|
|
auto p = iov.base;
|
|
|
|
if (via) {
|
|
|
|
p = std::copy(std::begin(via->value), std::end(via->value), p);
|
|
|
|
p = util::copy_lit(p, ", ");
|
|
|
|
}
|
|
|
|
p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
|
|
|
|
*p = '\0';
|
|
|
|
|
|
|
|
nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
|
2012-06-06 19:29:00 +02:00
|
|
|
}
|
2014-04-26 07:56:08 +02:00
|
|
|
|
2016-01-18 09:00:20 +01:00
|
|
|
for (auto &p : httpconf.add_response_headers) {
|
2016-02-13 14:19:05 +01:00
|
|
|
nva.push_back(http2::make_nv_nocopy(p.name, p.value));
|
2014-04-26 07:56:08 +02:00
|
|
|
}
|
|
|
|
|
2015-09-13 17:27:53 +02:00
|
|
|
if (downstream->get_stream_id() % 2 == 0) {
|
|
|
|
// This header field is basically for human on client side to
|
|
|
|
// figure out that the resource is pushed.
|
|
|
|
nva.push_back(http2::make_nv_ll("x-http2-push", "1"));
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-07-25 17:40:06 +02:00
|
|
|
log_response_headers(downstream, nva);
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2013-11-17 15:52:19 +01:00
|
|
|
|
2016-01-18 09:00:20 +01:00
|
|
|
if (http2conf.upstream.debug.dump.response_header) {
|
|
|
|
http2::dump_nv(http2conf.upstream.debug.dump.response_header, nva.data(),
|
|
|
|
nva.size());
|
2013-11-17 15:52:19 +01:00
|
|
|
}
|
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
nghttp2_data_provider data_prd;
|
2012-06-04 16:48:31 +02:00
|
|
|
data_prd.source.ptr = downstream;
|
2013-11-04 09:53:57 +01:00
|
|
|
data_prd.read_callback = downstream_data_read_callback;
|
2012-06-04 16:48:31 +02:00
|
|
|
|
2014-07-25 16:13:27 +02:00
|
|
|
nghttp2_data_provider *data_prdptr;
|
|
|
|
|
2016-04-27 16:00:36 +02:00
|
|
|
if (downstream->expect_response_body() ||
|
|
|
|
downstream->expect_response_trailer()) {
|
2014-07-25 16:13:27 +02:00
|
|
|
data_prdptr = &data_prd;
|
|
|
|
} else {
|
|
|
|
data_prdptr = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-05-21 17:53:02 +02:00
|
|
|
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
|
|
|
|
nva.data(), nva.size(), data_prdptr);
|
|
|
|
if (rv != 0) {
|
|
|
|
ULOG(FATAL, this) << "nghttp2_submit_response() failed";
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:08:39 +01:00
|
|
|
if (data_prdptr) {
|
|
|
|
downstream->reset_upstream_wtimer();
|
|
|
|
}
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
// WARNING: Never call directly or indirectly nghttp2_session_send or
|
|
|
|
// nghttp2_session_recv. These calls may delete downstream.
|
2013-07-26 12:38:54 +02:00
|
|
|
int Http2Upstream::on_downstream_body(Downstream *downstream,
|
2014-04-03 11:54:15 +02:00
|
|
|
const uint8_t *data, size_t len,
|
2014-11-27 15:39:04 +01:00
|
|
|
bool flush) {
|
2014-12-27 18:59:06 +01:00
|
|
|
auto body = downstream->get_response_buf();
|
|
|
|
body->append(data, len);
|
2014-04-03 11:54:15 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (flush) {
|
2014-04-03 11:54:15 +02:00
|
|
|
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
2014-08-09 11:47:45 +02:00
|
|
|
|
|
|
|
downstream->ensure_upstream_wtimer();
|
2014-04-03 11:54:15 +02:00
|
|
|
}
|
2012-06-04 20:11:43 +02:00
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
// WARNING: Never call directly or indirectly nghttp2_session_send or
|
|
|
|
// nghttp2_session_recv. These calls may delete downstream.
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::on_downstream_body_complete(Downstream *downstream) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2012-12-09 11:15:14 +01:00
|
|
|
DLOG(INFO, downstream) << "HTTP response completed";
|
2012-06-04 16:48:31 +02:00
|
|
|
}
|
2015-01-17 11:33:30 +01:00
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
auto &resp = downstream->response();
|
|
|
|
|
2016-01-14 15:20:44 +01:00
|
|
|
if (!downstream->validate_response_recv_body_length()) {
|
2015-01-17 11:33:30 +01:00
|
|
|
rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
|
2016-01-13 16:37:45 +01:00
|
|
|
resp.connection_close = true;
|
2015-01-17 11:33:30 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-12 17:19:03 +02:00
|
|
|
nghttp2_session_resume_data(session_, downstream->get_stream_id());
|
2014-08-09 11:47:45 +02:00
|
|
|
downstream->ensure_upstream_wtimer();
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
bool Http2Upstream::get_flow_control() const { return flow_control_; }
|
2012-06-09 18:36:30 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Upstream::pause_read(IOCtrlReason reason) {}
|
2012-11-18 13:23:13 +01:00
|
|
|
|
2014-08-21 14:22:16 +02:00
|
|
|
int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
2014-11-27 15:39:04 +01:00
|
|
|
size_t consumed) {
|
|
|
|
if (get_flow_control()) {
|
|
|
|
if (consume(downstream->get_stream_id(), consumed) != 0) {
|
2014-07-25 14:26:03 +02:00
|
|
|
return -1;
|
2013-02-08 13:46:58 +01:00
|
|
|
}
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2016-01-14 15:36:47 +01:00
|
|
|
auto &req = downstream->request();
|
|
|
|
|
|
|
|
req.consume(consumed);
|
2013-02-08 13:46:58 +01:00
|
|
|
}
|
2014-07-25 14:26:03 +02:00
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->signal_write();
|
|
|
|
return 0;
|
2012-11-20 17:29:39 +01:00
|
|
|
}
|
2012-11-18 13:23:13 +01:00
|
|
|
|
2014-06-27 15:34:54 +02:00
|
|
|
int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
|
2014-11-27 15:39:04 +01:00
|
|
|
unsigned int status_code) {
|
2014-06-27 15:34:54 +02:00
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = error_reply(downstream, status_code);
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-06-27 15:34:54 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->signal_write();
|
|
|
|
return 0;
|
2014-06-27 15:34:54 +02:00
|
|
|
}
|
|
|
|
|
2017-02-18 10:23:06 +01:00
|
|
|
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();
|
2018-03-11 04:02:18 +01:00
|
|
|
if (req.regular_connect_method() || req.scheme != "http") {
|
2017-02-18 10:23:06 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::consume(int32_t stream_id, size_t len) {
|
2014-07-25 14:26:03 +02:00
|
|
|
int rv;
|
2014-07-02 16:07:46 +02:00
|
|
|
|
2016-06-02 16:47:41 +02:00
|
|
|
auto faddr = handler_->get_upstream_addr();
|
|
|
|
|
2018-10-17 01:38:55 +02:00
|
|
|
if (faddr->alt_mode != UpstreamAltMode::NONE) {
|
2016-06-02 16:47:41 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-25 14:26:03 +02:00
|
|
|
rv = nghttp2_session_consume(session_, stream_id, len);
|
2014-07-02 16:07:46 +02:00
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
if (rv != 0) {
|
2014-11-08 02:51:56 +01:00
|
|
|
ULOG(WARN, this) << "nghttp2_session_consume() returned error: "
|
|
|
|
<< nghttp2_strerror(rv);
|
2014-07-25 14:26:03 +02:00
|
|
|
return -1;
|
2014-07-02 16:07:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-12 16:53:29 +01:00
|
|
|
void Http2Upstream::log_response_headers(
|
|
|
|
Downstream *downstream, const std::vector<nghttp2_nv> &nva) const {
|
2014-07-25 17:40:06 +02:00
|
|
|
std::stringstream ss;
|
2014-11-27 15:39:04 +01:00
|
|
|
for (auto &nv : nva) {
|
2016-03-28 15:22:26 +02:00
|
|
|
ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
|
|
|
|
<< StringRef{nv.value, nv.valuelen} << "\n";
|
2014-07-25 17:40:06 +02:00
|
|
|
}
|
|
|
|
ULOG(INFO, this) << "HTTP response headers. stream_id="
|
2016-10-15 11:36:04 +02:00
|
|
|
<< downstream->get_stream_id() << "\n"
|
|
|
|
<< ss.str();
|
2014-07-25 17:40:06 +02:00
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
int Http2Upstream::on_timeout(Downstream *downstream) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
2014-08-09 11:47:45 +02:00
|
|
|
ULOG(INFO, this) << "Stream timeout stream_id="
|
|
|
|
<< downstream->get_stream_id();
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:08:39 +01:00
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
handler_->signal_write();
|
2014-08-09 11:47:45 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 15:39:04 +01:00
|
|
|
void Http2Upstream::on_handler_delete() {
|
2015-03-11 16:17:05 +01:00
|
|
|
for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) {
|
2018-10-17 03:10:48 +02:00
|
|
|
if (d->get_dispatch_state() == DispatchState::ACTIVE &&
|
2015-03-11 16:17:05 +01:00
|
|
|
d->accesslog_ready()) {
|
|
|
|
handler_->write_accesslog(d);
|
2014-11-23 09:24:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-11-18 17:59:09 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
int Http2Upstream::on_downstream_reset(Downstream *downstream, bool no_retry) {
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
int rv;
|
|
|
|
|
2018-10-17 03:10:48 +02:00
|
|
|
if (downstream->get_dispatch_state() != DispatchState::ACTIVE) {
|
2016-09-14 15:16:07 +02:00
|
|
|
// This is error condition when we failed push_request_headers()
|
|
|
|
// in initiate_downstream(). Otherwise, we have
|
2018-10-17 03:10:48 +02:00
|
|
|
// DispatchState::ACTIVE state, or we did not set
|
2016-09-14 15:16:07 +02:00
|
|
|
// DownstreamConnection.
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
handler_->signal_write();
|
2015-03-11 16:17:05 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
if (!downstream->request_submission_ready()) {
|
2018-10-17 03:03:32 +02:00
|
|
|
if (downstream->get_response_state() == DownstreamState::MSG_COMPLETE) {
|
2016-12-24 14:54:22 +01:00
|
|
|
// We have got all response body already. Send it off.
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
return 0;
|
|
|
|
}
|
2016-09-14 15:16:07 +02:00
|
|
|
// pushed stream is handled here
|
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
2015-01-21 15:30:48 +01:00
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
handler_->signal_write();
|
2015-02-02 17:47:04 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2016-04-02 16:11:03 +02:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
downstream->pop_downstream_connection();
|
2015-01-21 15:30:48 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
downstream->add_retry();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
std::unique_ptr<DownstreamConnection> dconn;
|
2016-04-02 16:11:03 +02:00
|
|
|
|
2017-02-18 10:23:06 +01:00
|
|
|
rv = 0;
|
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
if (no_retry || downstream->no_more_retry()) {
|
|
|
|
goto fail;
|
|
|
|
}
|
2015-02-20 11:23:52 +01:00
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
// downstream connection is clean; we can retry with new
|
|
|
|
// downstream connection.
|
2016-04-12 16:30:52 +02:00
|
|
|
|
2019-01-19 03:12:05 +01:00
|
|
|
for (;;) {
|
|
|
|
auto dconn = handler_->get_downstream_connection(rv, downstream);
|
|
|
|
if (!dconn) {
|
|
|
|
goto fail;
|
|
|
|
}
|
2015-02-20 11:23:52 +01:00
|
|
|
|
2019-01-19 03:12:05 +01:00
|
|
|
rv = downstream->attach_downstream_connection(std::move(dconn));
|
|
|
|
if (rv == 0) {
|
|
|
|
break;
|
|
|
|
}
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
}
|
|
|
|
|
2016-09-14 15:16:07 +02:00
|
|
|
rv = downstream->push_request_headers();
|
|
|
|
if (rv != 0) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
2017-02-18 10:23:06 +01:00
|
|
|
if (rv == SHRPX_ERR_TLS_REQUIRED) {
|
|
|
|
rv = on_downstream_abort_request_with_https_redirect(downstream);
|
|
|
|
} else {
|
2017-04-01 07:04:55 +02:00
|
|
|
rv = on_downstream_abort_request(downstream, 502);
|
2017-02-18 10:23:06 +01:00
|
|
|
}
|
|
|
|
if (rv != 0) {
|
2016-09-14 15:16:07 +02:00
|
|
|
rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
|
|
|
|
}
|
|
|
|
downstream->pop_downstream_connection();
|
|
|
|
|
2014-12-27 18:59:06 +01:00
|
|
|
handler_->signal_write();
|
nghttpx: Check HTTP/2 downstream connection after certain idle time
Previously when requests are issued to HTTP/2 downstream connection,
but it turns out that connection is down, handlers of those requests
are deleted. In some situations, we only know connection is down when
we write something to network, so we'd like to handle this kind of
situation in more robust manner. In this change, certain seconds
passed after last network activity, we first issue PING frame to
downstream connection before issuing new HTTP request. If writing
PING frame is failed, it means connection was lost. In this case,
instead of deleting handler, pending requests are migrated to new
HTTP2/ downstream connection, so that it can continue without
affecting upstream connection.
2014-12-08 17:30:15 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
|
|
|
|
int rv;
|
2015-09-05 15:47:07 +02:00
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
const auto &req = downstream->request();
|
2018-01-04 14:05:25 +01:00
|
|
|
auto &resp = downstream->response();
|
2016-01-13 14:45:52 +01:00
|
|
|
|
2016-03-03 16:33:35 +01:00
|
|
|
auto base = http2::get_pure_path_component(req.path);
|
|
|
|
if (base.empty()) {
|
2015-09-05 15:47:07 +02:00
|
|
|
return 0;
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
2015-09-05 15:47:07 +02:00
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
auto &balloc = downstream->get_block_allocator();
|
|
|
|
|
2016-01-13 16:37:45 +01:00
|
|
|
for (auto &kv : resp.fs.headers()) {
|
2015-02-08 06:07:01 +01:00
|
|
|
if (kv.token != http2::HD_LINK) {
|
2015-02-07 08:09:49 +01:00
|
|
|
continue;
|
|
|
|
}
|
2016-03-25 15:51:42 +01:00
|
|
|
for (auto &link : http2::parse_link_header(kv.value)) {
|
2016-03-10 14:42:07 +01:00
|
|
|
StringRef scheme, authority, path;
|
2015-09-05 11:59:19 +02:00
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
rv = http2::construct_push_component(balloc, scheme, authority, path,
|
|
|
|
base, link.uri);
|
2015-09-05 15:47:07 +02:00
|
|
|
if (rv != 0) {
|
|
|
|
continue;
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
2015-09-05 11:59:19 +02:00
|
|
|
|
|
|
|
if (scheme.empty()) {
|
2016-03-10 14:42:07 +01:00
|
|
|
scheme = req.scheme;
|
2015-09-05 11:59:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (authority.empty()) {
|
2016-03-10 14:42:07 +01:00
|
|
|
authority = req.authority;
|
2015-09-05 11:59:19 +02:00
|
|
|
}
|
|
|
|
|
2018-01-04 14:05:25 +01:00
|
|
|
if (resp.is_resource_pushed(scheme, authority, path)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
rv = submit_push_promise(scheme, authority, path, downstream);
|
2015-02-07 08:09:49 +01:00
|
|
|
if (rv != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
2018-01-04 14:05:25 +01:00
|
|
|
|
|
|
|
resp.resource_pushed(scheme, authority, path);
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
int Http2Upstream::submit_push_promise(const StringRef &scheme,
|
|
|
|
const StringRef &authority,
|
|
|
|
const StringRef &path,
|
2015-02-07 08:09:49 +01:00
|
|
|
Downstream *downstream) {
|
2016-01-13 14:45:52 +01:00
|
|
|
const auto &req = downstream->request();
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
std::vector<nghttp2_nv> nva;
|
2015-09-13 17:26:50 +02:00
|
|
|
// 4 for :method, :scheme, :path and :authority
|
2016-01-13 14:45:52 +01:00
|
|
|
nva.reserve(4 + req.fs.headers().size());
|
2015-03-01 03:03:48 +01:00
|
|
|
|
2022-01-13 21:21:56 +01:00
|
|
|
// just use "GET" for now
|
2015-03-01 03:03:48 +01:00
|
|
|
nva.push_back(http2::make_nv_ll(":method", "GET"));
|
2016-03-10 14:42:07 +01:00
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
|
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":path", path));
|
|
|
|
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
|
2015-03-01 03:03:48 +01:00
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
for (auto &kv : req.fs.headers()) {
|
2015-02-08 06:07:01 +01:00
|
|
|
switch (kv.token) {
|
2015-02-08 06:23:22 +01:00
|
|
|
// TODO generate referer
|
|
|
|
case http2::HD__AUTHORITY:
|
|
|
|
case http2::HD__SCHEME:
|
2015-03-01 03:03:48 +01:00
|
|
|
case http2::HD__METHOD:
|
|
|
|
case http2::HD__PATH:
|
|
|
|
continue;
|
2015-02-08 06:23:22 +01:00
|
|
|
case http2::HD_ACCEPT_ENCODING:
|
|
|
|
case http2::HD_ACCEPT_LANGUAGE:
|
|
|
|
case http2::HD_CACHE_CONTROL:
|
|
|
|
case http2::HD_HOST:
|
|
|
|
case http2::HD_USER_AGENT:
|
2015-11-05 14:48:54 +01:00
|
|
|
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
|
2015-02-08 06:23:22 +01:00
|
|
|
break;
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-16 13:44:36 +01:00
|
|
|
auto promised_stream_id = nghttp2_submit_push_promise(
|
|
|
|
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
|
|
|
|
nva.size(), nullptr);
|
2015-02-07 08:09:49 +01:00
|
|
|
|
2015-11-16 13:44:36 +01:00
|
|
|
if (promised_stream_id < 0) {
|
2015-02-07 08:09:49 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
|
2015-11-16 13:44:36 +01:00
|
|
|
<< nghttp2_strerror(promised_stream_id);
|
2015-02-07 08:09:49 +01:00
|
|
|
}
|
2015-11-16 13:44:36 +01:00
|
|
|
if (nghttp2_is_fatal(promised_stream_id)) {
|
2015-02-07 08:09:49 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-08 09:25:21 +01:00
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
std::stringstream ss;
|
|
|
|
for (auto &nv : nva) {
|
2016-03-28 15:22:26 +02:00
|
|
|
ss << TTY_HTTP_HD << StringRef{nv.name, nv.namelen} << TTY_RST << ": "
|
|
|
|
<< StringRef{nv.value, nv.valuelen} << "\n";
|
2015-02-08 09:25:21 +01:00
|
|
|
}
|
2015-11-16 13:44:36 +01:00
|
|
|
ULOG(INFO, this) << "HTTP push request headers. promised_stream_id="
|
2016-10-15 11:36:04 +02:00
|
|
|
<< promised_stream_id << "\n"
|
|
|
|
<< ss.str();
|
2015-02-08 09:25:21 +01:00
|
|
|
}
|
|
|
|
|
2015-02-07 08:09:49 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-15 16:12:54 +01:00
|
|
|
bool Http2Upstream::push_enabled() const {
|
2016-10-08 04:34:23 +02:00
|
|
|
auto config = get_config();
|
|
|
|
return !(config->http2.no_server_push ||
|
2015-11-15 16:12:54 +01:00
|
|
|
nghttp2_session_get_remote_settings(
|
|
|
|
session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
|
2016-10-08 04:34:23 +02:00
|
|
|
config->http2_proxy);
|
2015-11-15 16:12:54 +01:00
|
|
|
}
|
|
|
|
|
2016-03-03 16:38:43 +01:00
|
|
|
int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
|
2015-09-05 15:47:07 +02:00
|
|
|
int rv;
|
|
|
|
|
2016-04-16 11:52:14 +02:00
|
|
|
if (uri.empty() || !push_enabled() ||
|
|
|
|
(downstream->get_stream_id() % 2) == 0) {
|
2015-09-05 15:47:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
const auto &req = downstream->request();
|
|
|
|
|
2016-03-03 16:33:35 +01:00
|
|
|
auto base = http2::get_pure_path_component(req.path);
|
|
|
|
if (base.empty()) {
|
2015-09-05 15:47:07 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
auto &balloc = downstream->get_block_allocator();
|
|
|
|
|
|
|
|
StringRef scheme, authority, path;
|
2015-09-05 15:47:07 +02:00
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
rv = http2::construct_push_component(balloc, scheme, authority, path, base,
|
|
|
|
uri);
|
2015-09-05 15:47:07 +02:00
|
|
|
if (rv != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scheme.empty()) {
|
2016-03-10 14:42:07 +01:00
|
|
|
scheme = req.scheme;
|
2015-09-05 15:47:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (authority.empty()) {
|
2016-03-10 14:42:07 +01:00
|
|
|
authority = req.authority;
|
2015-09-05 15:47:07 +02:00
|
|
|
}
|
|
|
|
|
2018-01-04 14:05:25 +01:00
|
|
|
auto &resp = downstream->response();
|
|
|
|
|
|
|
|
if (resp.is_resource_pushed(scheme, authority, path)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-10 14:42:07 +01:00
|
|
|
rv = submit_push_promise(scheme, authority, path, downstream);
|
2015-09-05 15:47:07 +02:00
|
|
|
|
|
|
|
if (rv != 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2018-01-04 14:05:25 +01:00
|
|
|
resp.resource_pushed(scheme, authority, path);
|
|
|
|
|
2015-09-05 15:47:07 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-10-02 15:42:46 +02:00
|
|
|
int Http2Upstream::response_riovec(struct iovec *iov, int iovcnt) const {
|
|
|
|
if (iovcnt == 0 || wb_.rleft() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
return wb_.riovec(iov, iovcnt);
|
2015-10-02 15:42:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Upstream::response_drain(size_t n) { wb_.drain(n); }
|
|
|
|
|
|
|
|
bool Http2Upstream::response_empty() const { return wb_.rleft() == 0; }
|
|
|
|
|
2016-01-26 15:04:53 +01:00
|
|
|
DefaultMemchunks *Http2Upstream::get_response_buf() { return &wb_; }
|
2015-10-02 15:42:46 +02:00
|
|
|
|
2015-11-15 16:12:54 +01:00
|
|
|
Downstream *
|
|
|
|
Http2Upstream::on_downstream_push_promise(Downstream *downstream,
|
|
|
|
int32_t promised_stream_id) {
|
|
|
|
// promised_stream_id is for backend HTTP/2 session, not for
|
|
|
|
// frontend.
|
|
|
|
auto promised_downstream =
|
2018-10-15 16:02:44 +02:00
|
|
|
std::make_unique<Downstream>(this, handler_->get_mcpool(), 0);
|
2016-01-13 14:45:52 +01:00
|
|
|
auto &promised_req = promised_downstream->request();
|
|
|
|
|
2015-11-15 16:12:54 +01:00
|
|
|
promised_downstream->set_downstream_stream_id(promised_stream_id);
|
2016-01-20 03:16:49 +01:00
|
|
|
// Set associated stream in frontend
|
|
|
|
promised_downstream->set_assoc_stream_id(downstream->get_stream_id());
|
2015-11-15 16:12:54 +01:00
|
|
|
|
|
|
|
promised_downstream->disable_upstream_rtimer();
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
promised_req.http_major = 2;
|
|
|
|
promised_req.http_minor = 0;
|
2015-11-15 16:12:54 +01:00
|
|
|
|
2016-09-22 12:02:57 +02:00
|
|
|
promised_req.fs.content_length = 0;
|
|
|
|
promised_req.http2_expect_body = false;
|
|
|
|
|
2015-11-15 16:12:54 +01:00
|
|
|
auto ptr = promised_downstream.get();
|
|
|
|
add_pending_downstream(std::move(promised_downstream));
|
|
|
|
downstream_queue_.mark_active(ptr);
|
|
|
|
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Http2Upstream::on_downstream_push_promise_complete(
|
|
|
|
Downstream *downstream, Downstream *promised_downstream) {
|
|
|
|
std::vector<nghttp2_nv> nva;
|
|
|
|
|
2016-01-13 14:45:52 +01:00
|
|
|
const auto &promised_req = promised_downstream->request();
|
|
|
|
const auto &headers = promised_req.fs.headers();
|
2015-11-15 16:12:54 +01:00
|
|
|
|
|
|
|
nva.reserve(headers.size());
|
|
|
|
|
|
|
|
for (auto &kv : headers) {
|
2016-03-10 14:42:07 +01:00
|
|
|
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
|
2015-11-15 16:12:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
auto promised_stream_id = nghttp2_submit_push_promise(
|
|
|
|
session_, NGHTTP2_FLAG_NONE, downstream->get_stream_id(), nva.data(),
|
|
|
|
nva.size(), promised_downstream);
|
|
|
|
if (promised_stream_id < 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
promised_downstream->set_stream_id(promised_stream_id);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Http2Upstream::cancel_premature_downstream(
|
|
|
|
Downstream *promised_downstream) {
|
|
|
|
if (LOG_ENABLED(INFO)) {
|
|
|
|
ULOG(INFO, this) << "Remove premature promised stream "
|
|
|
|
<< promised_downstream;
|
|
|
|
}
|
|
|
|
downstream_queue_.remove_and_get_blocked(promised_downstream, false);
|
|
|
|
}
|
|
|
|
|
2016-09-08 15:49:36 +02:00
|
|
|
size_t Http2Upstream::get_max_buffer_size() const { return max_buffer_size_; }
|
|
|
|
|
2012-06-04 16:48:31 +02:00
|
|
|
} // namespace shrpx
|