Compare commits

...

7 Commits

Author SHA1 Message Date
Tatsuhiro Tsujikawa 957abacf99 Bump up version number to 1.39.2, LT revision to 32:0:18 2019-08-13 17:11:14 +09:00
Tatsuhiro Tsujikawa 83d362c6d2 Don't read too greedily 2019-08-13 17:07:20 +09:00
Tatsuhiro Tsujikawa a76d0723b5 Add nghttp2_option_set_max_outbound_ack 2019-08-13 17:06:58 +09:00
Tatsuhiro Tsujikawa db2f612a30 nghttpx: Fix request stall
Fix request stall if backend connection is reused and buffer is full.
2019-08-13 17:05:11 +09:00
Tatsuhiro Tsujikawa 7ffc239b5f Bump up version number to 1.39.1 2019-06-11 23:20:14 +09:00
Tatsuhiro Tsujikawa bc886a0e0d Fix FPE with default backend 2019-06-11 23:19:43 +09:00
Tatsuhiro Tsujikawa a3a14a9cde Fix log-level is not set with cmd-line or configuration file 2019-06-11 23:19:43 +09:00
19 changed files with 121 additions and 24 deletions

View File

@ -24,13 +24,13 @@
cmake_minimum_required(VERSION 3.0)
# XXX using 1.8.90 instead of 1.9.0-DEV
project(nghttp2 VERSION 1.39.0)
project(nghttp2 VERSION 1.39.2)
# See versioning rule:
# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
set(LT_CURRENT 31)
set(LT_REVISION 4)
set(LT_AGE 17)
set(LT_CURRENT 32)
set(LT_REVISION 0)
set(LT_AGE 18)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
include(Version)

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.39.0], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.39.2], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
@ -44,9 +44,9 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 31)
AC_SUBST(LT_REVISION, 4)
AC_SUBST(LT_AGE, 17)
AC_SUBST(LT_CURRENT, 32)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_AGE, 18)
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`

View File

@ -67,6 +67,7 @@ APIDOCS= \
nghttp2_option_set_no_recv_client_magic.rst \
nghttp2_option_set_peer_max_concurrent_streams.rst \
nghttp2_option_set_user_recv_extension_type.rst \
nghttp2_option_set_max_outbound_ack.rst \
nghttp2_pack_settings_payload.rst \
nghttp2_priority_spec_check_default.rst \
nghttp2_priority_spec_default_init.rst \

View File

@ -625,6 +625,35 @@ func TestH1H1HTTPSRedirectPort(t *testing.T) {
}
}
// TestH1H1POSTRequests tests that server can handle 2 requests with
// request body.
func TestH1H1POSTRequests(t *testing.T) {
st := newServerTester(nil, t, noopHandler)
defer st.Close()
res, err := st.http1(requestParam{
name: "TestH1H1POSTRequestsNo1",
body: make([]byte, 1),
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("res.status: %v; want %v", got, want)
}
res, err = st.http1(requestParam{
name: "TestH1H1POSTRequestsNo2",
body: make([]byte, 65536),
})
if err != nil {
t.Fatalf("Error st.http1() = %v", err)
}
if got, want := res.status, 200; got != want {
t.Errorf("res.status: %v; want %v", got, want)
}
}
// // TestH1H2ConnectFailure tests that server handles the situation that
// // connection attempt to HTTP/2 backend failed.
// func TestH1H2ConnectFailure(t *testing.T) {

View File

@ -662,7 +662,9 @@ func cloneHeader(h http.Header) http.Header {
return h2
}
func noopHandler(w http.ResponseWriter, r *http.Request) {}
func noopHandler(w http.ResponseWriter, r *http.Request) {
ioutil.ReadAll(r.Body)
}
type APIResponse struct {
Status string `json:"status,omitempty"`

View File

@ -2648,6 +2648,17 @@ nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
int val);
/**
* @function
*
* This function sets the maximum number of outgoing SETTINGS ACK and
* PING ACK frames retained in :type:`nghttp2_session` object. If
* more than those frames are retained, the peer is considered to be
* misbehaving and session will be closed. The default value is 1000.
*/
NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
size_t val);
/**
* @function
*

View File

@ -116,3 +116,8 @@ void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS;
option->no_closed_streams = val;
}
void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) {
option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK;
option->max_outbound_ack = val;
}

View File

@ -66,6 +66,7 @@ typedef enum {
NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8,
NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
} nghttp2_option_flag;
/**
@ -80,6 +81,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE
*/
size_t max_deflate_dynamic_table_size;
/**
* NGHTTP2_OPT_MAX_OUTBOUND_ACK
*/
size_t max_outbound_ack;
/**
* Bitwise OR of nghttp2_option_flag to determine that which fields
* are specified.

View File

@ -457,6 +457,7 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->remote_settings.max_concurrent_streams = 100;
(*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
(*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM;
if (option) {
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
@ -516,6 +517,10 @@ static int session_new(nghttp2_session **session_ptr,
option->no_closed_streams) {
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS;
}
if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) {
(*session_ptr)->max_outbound_ack = option->max_outbound_ack;
}
}
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
@ -6857,7 +6862,7 @@ int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
mem = &session->mem;
if ((flags & NGHTTP2_FLAG_ACK) &&
session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
session->obq_flood_counter_ >= session->max_outbound_ack) {
return NGHTTP2_ERR_FLOODED;
}
@ -7002,7 +7007,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
if (session->obq_flood_counter_ >= session->max_outbound_ack) {
return NGHTTP2_ERR_FLOODED;
}
}

View File

@ -97,7 +97,7 @@ typedef struct {
response frames are stacked up, which leads to memory exhaustion.
The value selected here is arbitrary, but safe value and if we have
these frames in this number, it is considered suspicious. */
#define NGHTTP2_MAX_OBQ_FLOOD_ITEM 10000
#define NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM 1000
/* The default value of maximum number of concurrent streams. */
#define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu
@ -258,8 +258,12 @@ struct nghttp2_session {
size_t num_idle_streams;
/* The number of bytes allocated for nvbuf */
size_t nvbuflen;
/* Counter for detecting flooding in outbound queue */
/* Counter for detecting flooding in outbound queue. If it exceeds
max_outbound_ack, session will be closed. */
size_t obq_flood_counter_;
/* The maximum number of outgoing SETTINGS ACK and PING ACK in
outbound queue. */
size_t max_outbound_ack;
/* The maximum length of header block to send. Calculated by the
same way as nghttp2_hd_deflate_bound() does. */
size_t max_send_header_block_length;

View File

@ -650,6 +650,7 @@ int Http2Handler::read_clear() {
}
return -1;
}
break;
}
return write_(*this);
@ -775,6 +776,7 @@ int Http2Handler::read_tls() {
}
return -1;
}
break;
}
fin:

View File

@ -2898,6 +2898,8 @@ int process_options(Config *config,
assert(include_set.empty());
}
Log::set_severity_level(config->logging.severity);
auto &loggingconf = config->logging;
if (loggingconf.access.syslog || loggingconf.error.syslog) {
@ -3202,7 +3204,6 @@ void reload_config(WorkerProcess *wp) {
// configuration can be obtained from get_config().
auto old_config = replace_config(std::move(new_config));
Log::set_severity_level(get_config()->logging.severity);
auto pid = fork_worker_process(ipc_fd, iaddrs);
@ -3210,7 +3211,6 @@ void reload_config(WorkerProcess *wp) {
LOG(ERROR) << "Failed to process new configuration";
new_config = replace_config(std::move(old_config));
Log::set_severity_level(get_config()->logging.severity);
close_not_inherited_fd(new_config.get(), iaddrs);
return;

View File

@ -111,6 +111,7 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
int ClientHandler::noop() { return 0; }
int ClientHandler::read_clear() {
auto should_break = false;
rb_.ensure_chunk();
for (;;) {
if (rb_.rleft() && on_read() != 0) {
@ -123,7 +124,7 @@ int ClientHandler::read_clear() {
return 0;
}
if (!ev_is_active(&conn_.rev)) {
if (!ev_is_active(&conn_.rev) || should_break) {
return 0;
}
@ -141,6 +142,7 @@ int ClientHandler::read_clear() {
}
rb_.write(nread);
should_break = true;
}
}
@ -205,6 +207,8 @@ int ClientHandler::tls_handshake() {
}
int ClientHandler::read_tls() {
auto should_break = false;
ERR_clear_error();
rb_.ensure_chunk();
@ -221,7 +225,7 @@ int ClientHandler::read_tls() {
return 0;
}
if (!ev_is_active(&conn_.rev)) {
if (!ev_is_active(&conn_.rev) || should_break) {
return 0;
}
@ -239,6 +243,7 @@ int ClientHandler::read_tls() {
}
rb_.write(nread);
should_break = true;
}
}

View File

@ -3994,6 +3994,8 @@ int configure_downstream_group(Config *config, bool http2_proxy,
addr.host = StringRef::from_lit(DEFAULT_DOWNSTREAM_HOST);
addr.port = DEFAULT_DOWNSTREAM_PORT;
addr.proto = Proto::HTTP1;
addr.weight = 1;
addr.group_weight = 1;
DownstreamAddrGroupConfig g(StringRef::from_lit("/"));
g.addrs.push_back(std::move(addr));

View File

@ -144,7 +144,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
request_header_sent_(false),
accesslog_written_(false),
new_affinity_cookie_(false),
blocked_request_data_eof_(false) {
blocked_request_data_eof_(false),
expect_100_continue_(false) {
auto &timeoutconf = get_config()->http2.timeout;
@ -857,6 +858,11 @@ void Downstream::inspect_http1_request() {
chunked_request_ = true;
}
}
auto expect = req_.fs.header(http2::HD_EXPECT);
expect_100_continue_ =
expect &&
util::strieq(expect->value, StringRef::from_lit("100-continue"));
}
void Downstream::inspect_http1_response() {
@ -1159,4 +1165,8 @@ void Downstream::set_blocked_request_data_eof(bool f) {
void Downstream::set_ws_key(const StringRef &key) { ws_key_ = key; }
bool Downstream::get_expect_100_continue() const {
return expect_100_continue_;
}
} // namespace shrpx

View File

@ -511,6 +511,8 @@ public:
void set_ws_key(const StringRef &key);
bool get_expect_100_continue() const;
enum {
EVENT_ERROR = 0x1,
EVENT_TIMEOUT = 0x2,
@ -602,6 +604,8 @@ private:
// true if eof is received from client before sending header fields
// to backend.
bool blocked_request_data_eof_;
// true if request contains "expect: 100-continue" header field.
bool expect_100_continue_;
};
} // namespace shrpx

View File

@ -694,7 +694,8 @@ int HttpDownstreamConnection::push_request_headers() {
// enables us to send headers and data in one writev system call.
if (req.method == HTTP_CONNECT ||
downstream_->get_blocked_request_buf()->rleft() ||
(!req.http2_expect_body && req.fs.content_length == 0)) {
(!req.http2_expect_body && req.fs.content_length == 0) ||
downstream_->get_expect_100_continue()) {
signal_write();
}
@ -1177,6 +1178,19 @@ int HttpDownstreamConnection::write_first() {
auto buf = downstream_->get_blocked_request_buf();
buf->reset();
// upstream->resume_read() might be called in
// write_tls()/write_clear(), but before blocked_request_buf_ is
// reset. So upstream read might still be blocked. Let's do it
// again here.
auto input = downstream_->get_request_buf();
if (input->rleft() == 0) {
auto upstream = downstream_->get_upstream();
auto &req = downstream_->request();
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
req.unconsumed_body_length);
}
return 0;
}

View File

@ -505,9 +505,7 @@ int htp_hdrs_completecb(llhttp_t *htp) {
// and let them decide whether responds with 100 Continue or not.
// For alternative mode, we have no backend, so just send 100
// Continue here to make the client happy.
auto expect = req.fs.header(http2::HD_EXPECT);
if (expect &&
util::strieq(expect->value, StringRef::from_lit("100-continue"))) {
if (downstream->get_expect_100_continue()) {
auto output = downstream->get_response_buf();
constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n");
output->append(res);

View File

@ -10002,7 +10002,7 @@ void test_nghttp2_session_flooding(void) {
buf = &bufs.head->buf;
for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
CU_ASSERT(
(ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
@ -10024,7 +10024,7 @@ void test_nghttp2_session_flooding(void) {
buf = &bufs.head->buf;
for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) {
CU_ASSERT(
(ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));