Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Tatsuhiro Tsujikawa | 957abacf99 | |
Tatsuhiro Tsujikawa | 83d362c6d2 | |
Tatsuhiro Tsujikawa | a76d0723b5 | |
Tatsuhiro Tsujikawa | db2f612a30 | |
Tatsuhiro Tsujikawa | 7ffc239b5f | |
Tatsuhiro Tsujikawa | bc886a0e0d | |
Tatsuhiro Tsujikawa | a3a14a9cde |
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
|
|
Loading…
Reference in New Issue