From f9a50333d254fa4ec8bedcd1cc22f21ace789a96 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 28 Apr 2015 23:12:27 +0900 Subject: [PATCH 01/35] Fix doc for nghttp2_select_next_protocol We have to return this error code to notify OpenSSL that we have not filled any values in |*out| and |*outlen|. --- lib/includes/nghttp2/nghttp2.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 9f456e50..039bc473 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -3562,7 +3562,10 @@ NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs, * { * int rv; * rv = nghttp2_select_next_protocol(out, outlen, in, inlen); - * if(rv == 1) { + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { * ((MyType*)arg)->http2_selected = 1; * } * return SSL_TLSEXT_ERR_OK; From 552f67546654ef48c8acf67166f84c787d31959d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 29 Apr 2015 21:10:59 +0900 Subject: [PATCH 02/35] nghttpx: Add --header-field-buffer and --max-header-fields options --- src/shrpx.cc | 22 +++++++++++ src/shrpx_config.cc | 11 ++++++ src/shrpx_config.h | 4 ++ src/shrpx_downstream.h | 3 -- src/shrpx_http2_session.cc | 11 ++++-- src/shrpx_http2_upstream.cc | 11 ++++-- src/shrpx_http_downstream_connection.cc | 50 ++++++++++++++++++------- src/shrpx_https_upstream.cc | 46 ++++++++++++++++------- 8 files changed, 121 insertions(+), 37 deletions(-) diff --git a/src/shrpx.cc b/src/shrpx.cc index 6d9e1a7a..49593e45 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -912,6 +912,8 @@ void fill_default_config() { mod_config()->fetch_ocsp_response_file = strcopy(PKGDATADIR "/fetch-ocsp-response"); mod_config()->no_ocsp = false; + mod_config()->header_field_buffer = 64 * 1024; + mod_config()->max_header_fields = 100; } } // namespace @@ -1336,6 +1338,16 @@ HTTP: won't replace anything already set. This option can be used several times to specify multiple header fields. Example: --add-response-header="foo: bar" + --header-field-buffer= + Set maximum buffer size for incoming HTTP header field + list. This is the sum of header name and value in + bytes. + Default: )" + << util::utos_with_unit(get_config()->header_field_buffer) << R"( + --max-header-fields= + Set maximum number of incoming HTTP header fields, which + appear in one request or response header field list. + Default: )" << get_config()->max_header_fields << R"( Debug: --frontend-http2-dump-request-header= @@ -1496,6 +1508,8 @@ int main(int argc, char **argv) { {"fetch-ocsp-response-file", required_argument, &flag, 77}, {"ocsp-update-interval", required_argument, &flag, 78}, {"no-ocsp", no_argument, &flag, 79}, + {"header-field-buffer", required_argument, &flag, 80}, + {"max-header-fields", required_argument, &flag, 81}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -1846,6 +1860,14 @@ int main(int argc, char **argv) { // --no-ocsp cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes"); break; + case 80: + // --header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, optarg); + break; + case 81: + // --max-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, optarg); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 2fc93ec9..9e0ac424 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -150,6 +150,8 @@ const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] = const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[] = "fetch-ocsp-response-file"; const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval"; const char SHRPX_OPT_NO_OCSP[] = "no-ocsp"; +const char SHRPX_OPT_HEADER_FIELD_BUFFER[] = "header-field-buffer"; +const char SHRPX_OPT_MAX_HEADER_FIELDS[] = "max-header-fields"; namespace { Config *config = nullptr; @@ -1212,6 +1214,15 @@ int parse_config(const char *opt, const char *optarg) { return 0; } + if (util::strieq(opt, SHRPX_OPT_HEADER_FIELD_BUFFER)) { + return parse_uint_with_unit(&mod_config()->header_field_buffer, opt, + optarg); + } + + if (util::strieq(opt, SHRPX_OPT_MAX_HEADER_FIELDS)) { + return parse_uint(&mod_config()->max_header_fields, opt, optarg); + } + if (util::strieq(opt, "conf")) { LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index d3c46a5b..4856fb8e 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -139,6 +139,8 @@ extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[]; extern const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[]; extern const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[]; extern const char SHRPX_OPT_NO_OCSP[]; +extern const char SHRPX_OPT_HEADER_FIELD_BUFFER[]; +extern const char SHRPX_OPT_MAX_HEADER_FIELDS[]; union sockaddr_union { sockaddr_storage storage; @@ -282,6 +284,8 @@ struct Config { size_t rlimit_nofile; size_t downstream_request_buffer_size; size_t downstream_response_buffer_size; + size_t header_field_buffer; + size_t max_header_fields; // Bit mask to disable SSL/TLS protocol versions. This will be // passed to SSL_CTX_set_options(). long int tls_proto_mask; diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 718c8bd2..72276090 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -286,9 +286,6 @@ public: // Change the priority of downstream int change_priority(int32_t pri); - // Maximum buffer size for header name/value pairs. - static constexpr size_t MAX_HEADERS_SUM = 128 * 1024; - bool get_rst_stream_after_end_stream() const; void set_rst_stream_after_end_stream(bool f); diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 89e12dd6..2e8568ed 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -733,10 +733,15 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, auto trailer = frame->headers.cat != NGHTTP2_HCAT_RESPONSE && !downstream->get_expect_final_response(); - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { + if (downstream->get_response_headers_sum() + namelen + valuelen > + get_config()->header_field_buffer || + downstream->get_response_headers().size() >= + get_config()->max_header_fields) { if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); + DLOG(INFO, downstream) + << "Too large or many header field size=" + << downstream->get_response_headers_sum() + namelen + valuelen + << ", num=" << downstream->get_response_headers().size() + 1; } if (trailer) { diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index fcb91829..f14adcd6 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -202,14 +202,19 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, return 0; } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { + if (downstream->get_request_headers_sum() + namelen + valuelen > + get_config()->header_field_buffer || + downstream->get_request_headers().size() >= + get_config()->max_header_fields) { if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { return 0; } if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); + ULOG(INFO, upstream) << "Too large or many header field size=" + << downstream->get_request_headers_sum() + namelen + + valuelen << ", num=" + << downstream->get_request_headers().size() + 1; } // just ignore header fields if this is trailer part. diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 329fdce5..6e5c5295 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -582,10 +582,29 @@ int htp_hdrs_completecb(http_parser *htp) { namespace { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { auto downstream = static_cast(htp->data); + + if (downstream->get_response_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "Too large header block size=" + << downstream->get_response_headers_sum() + len; + } + return -1; + } + if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_header_key_prev()) { downstream->append_last_response_header_key(data, len); } else { + if (downstream->get_response_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too many header field num=" + << downstream->get_response_headers().size() + 1; + } + return -1; + } downstream->add_response_header(std::string(data, len), ""); } } else { @@ -593,16 +612,18 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { if (downstream->get_response_trailer_key_prev()) { downstream->append_last_response_trailer_key(data, len); } else { + if (downstream->get_response_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) + << "Too many header field num=" + << downstream->get_response_headers().size() + 1; + } + return -1; + } downstream->add_response_trailer(std::string(data, len), ""); } } - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); - } - return -1; - } return 0; } } // namespace @@ -610,6 +631,14 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { namespace { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { auto downstream = static_cast(htp->data); + if (downstream->get_response_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + DLOG(INFO, downstream) << "Too large header block size=" + << downstream->get_response_headers_sum() + len; + } + return -1; + } if (downstream->get_response_state() == Downstream::INITIAL) { if (downstream->get_response_header_key_prev()) { downstream->set_last_response_header_value(data, len); @@ -623,13 +652,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { downstream->append_last_response_trailer_value(data, len); } } - if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "Too large header block size=" - << downstream->get_response_headers_sum(); - } - return -1; - } return 0; } } // namespace diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 6a6ed2f2..ca5d3a08 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -87,10 +87,26 @@ namespace { int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); auto downstream = upstream->get_downstream(); + if (downstream->get_request_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << downstream->get_request_headers_sum() + len; + } + return -1; + } if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_header_key_prev()) { downstream->append_last_request_header_key(data, len); } else { + if (downstream->get_request_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too many header field num=" + << downstream->get_request_headers().size() + 1; + } + return -1; + } downstream->add_request_header(std::string(data, len), ""); } } else { @@ -98,16 +114,17 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { if (downstream->get_request_trailer_key_prev()) { downstream->append_last_request_trailer_key(data, len); } else { + if (downstream->get_request_headers().size() >= + get_config()->max_header_fields) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too many header field num=" + << downstream->get_request_headers().size() + 1; + } + return -1; + } downstream->add_request_trailer(std::string(data, len), ""); } } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); - } - return -1; - } return 0; } } // namespace @@ -116,6 +133,14 @@ namespace { int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); auto downstream = upstream->get_downstream(); + if (downstream->get_request_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large header block size=" + << downstream->get_request_headers_sum() + len; + } + return -1; + } if (downstream->get_request_state() == Downstream::INITIAL) { if (downstream->get_request_header_key_prev()) { downstream->set_last_request_header_value(data, len); @@ -129,13 +154,6 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { downstream->append_last_request_trailer_value(data, len); } } - if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Too large header block size=" - << downstream->get_request_headers_sum(); - } - return -1; - } return 0; } } // namespace From 8c6f9e899f3b6ce82a315b0312111111c2a84b34 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 29 Apr 2015 21:27:36 +0900 Subject: [PATCH 03/35] nghttpx: Enforce header field buffer limit for SPDY frontend --- src/shrpx_spdy_upstream.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index bf49c28f..a14b0ed6 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -163,6 +163,19 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, << downstream->get_stream_id() << "\n" << ss.str(); } + size_t num_headers = 0; + size_t header_buffer = 0; + for (size_t i = 0; nv[i]; i += 2) { + ++num_headers; + header_buffer += strlen(nv[i]) + strlen(nv[i + 1]); + } + + if (header_buffer > get_config()->header_field_buffer || + num_headers > get_config()->max_header_fields) { + upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); + return; + } + for (size_t i = 0; nv[i]; i += 2) { downstream->add_request_header(nv[i], nv[i + 1]); } @@ -428,6 +441,12 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler) rv = spdylay_session_server_new(&session_, version, &callbacks, this); assert(rv == 0); + uint32_t max_buffer = 65536; + rv = spdylay_session_set_option(session_, + SPDYLAY_OPT_MAX_RECV_CTRL_FRAME_BUFFER, + &max_buffer, sizeof(max_buffer)); + assert(rv == 0); + if (version >= SPDYLAY_PROTO_SPDY3) { int val = 1; flow_control_ = true; From ea8a566d98ebe25bebcb563cad9b25af394a48dc Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 29 Apr 2015 21:39:46 +0900 Subject: [PATCH 04/35] nghttpx: Send 431 if header field size exceeded the configuration limit --- src/shrpx_downstream.h | 4 ++++ src/shrpx_https_upstream.cc | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 72276090..0388adea 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -193,6 +193,10 @@ public: // header contains invalid header field. We can safely send error // response (502) to a client. MSG_BAD_HEADER, + // header fields in HTTP/1 request exceed the configuration limit. + // This state is only transitioned from INITIAL state, and solely + // used to signal 431 status code to the client. + HTTP1_REQUEST_HEADER_TOO_LARGE, }; void set_request_state(int state); int get_request_state() const; diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index ca5d3a08..b3f1a029 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -93,6 +93,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { ULOG(INFO, upstream) << "Too large header block size=" << downstream->get_request_headers_sum() + len; } + if (downstream->get_request_state() == Downstream::INITIAL) { + downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); + } return -1; } if (downstream->get_request_state() == Downstream::INITIAL) { @@ -105,6 +108,8 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) { ULOG(INFO, upstream) << "Too many header field num=" << downstream->get_request_headers().size() + 1; } + downstream->set_request_state( + Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); return -1; } downstream->add_request_header(std::string(data, len), ""); @@ -139,6 +144,9 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) { ULOG(INFO, upstream) << "Too large header block size=" << downstream->get_request_headers_sum() + len; } + if (downstream->get_request_state() == Downstream::INITIAL) { + downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); + } return -1; } if (downstream->get_request_state() == Downstream::INITIAL) { @@ -425,9 +433,15 @@ int HttpsUpstream::on_read() { unsigned int status_code; - if (downstream && - downstream->get_request_state() == Downstream::CONNECT_FAIL) { - status_code = 503; + if (downstream) { + if (downstream->get_request_state() == Downstream::CONNECT_FAIL) { + status_code = 503; + } else if (downstream->get_request_state() == + Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE) { + status_code = 431; + } else { + status_code = 400; + } } else { status_code = 400; } From 9dc52595932b26e0aff6d2dbc6250d565ba90735 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 29 Apr 2015 22:23:25 +0900 Subject: [PATCH 05/35] nghttpx: Take into account request URI in header size in https frontend --- src/shrpx_downstream.cc | 4 ++++ src/shrpx_downstream.h | 1 + src/shrpx_https_upstream.cc | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 87e8ff58..60428783 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -1211,4 +1211,8 @@ void Downstream::detach_blocked_link(BlockedLink *l) { blocked_link_ = nullptr; } +void Downstream::add_request_headers_sum(size_t amount) { + request_headers_sum_ += amount; +} + } // namespace shrpx diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 0388adea..f761b6a0 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -143,6 +143,7 @@ public: void set_request_method(std::string method); const std::string &get_request_method() const; void set_request_path(std::string path); + void add_request_headers_sum(size_t amount); void set_request_start_time(std::chrono::high_resolution_clock::time_point time); const std::chrono::high_resolution_clock::time_point & diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index b3f1a029..e620a5da 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -78,6 +78,17 @@ namespace { int htp_uricb(http_parser *htp, const char *data, size_t len) { auto upstream = static_cast(htp->data); auto downstream = upstream->get_downstream(); + if (downstream->get_request_headers_sum() + len > + get_config()->header_field_buffer) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) << "Too large URI size=" + << downstream->get_request_headers_sum() + len; + } + assert(downstream->get_request_state() == Downstream::INITIAL); + downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE); + return -1; + } + downstream->add_request_headers_sum(len); downstream->append_request_path(data, len); return 0; } From 026521b0971d1a70ac4490c565660446b9b44780 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 29 Apr 2015 22:54:25 +0900 Subject: [PATCH 06/35] integration: Add tests for --header-field-buffer and --max-header-fields --- integration-tests/nghttpx_http1_test.go | 66 +++++++++++++++++++++++++ integration-tests/nghttpx_http2_test.go | 40 +++++++++++++++ integration-tests/nghttpx_spdy_test.go | 40 +++++++++++++++ integration-tests/server_tester.go | 14 +++++- 4 files changed, 159 insertions(+), 1 deletion(-) diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go index 80d3b1ca..c8d58709 100644 --- a/integration-tests/nghttpx_http1_test.go +++ b/integration-tests/nghttpx_http1_test.go @@ -246,6 +246,72 @@ func TestH1H1RequestTrailer(t *testing.T) { } } +// TestH1H1HeaderFieldBufferPath tests that request with request path +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBufferPath(t *testing.T) { + // The value 100 is chosen so that sum of header fields bytes + // does not exceed it. We use > 100 bytes URI to exceed this + // limit. + st := newServerTester([]string{"--header-field-buffer=100"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBufferPath", + path: "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, 431; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestH1H1HeaderFieldBuffer(t *testing.T) { + st := newServerTester([]string{"--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, 431; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH1H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestH1H1HeaderFields(t *testing.T) { + st := newServerTester([]string{"--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.http1(requestParam{ + name: "TestH1H1HeaderFields", + header: []hpack.HeaderField{ + // Add extra header field to ensure that + // header field limit exceeds + pair("Connection", "close"), + }, + }) + if err != nil { + t.Fatalf("Error st.http1() = %v", err) + } + if got, want := res.status, 431; got != want { + t.Errorf("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) { diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index 89cd353a..0a3115f3 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -558,6 +558,46 @@ func TestH2H1RequestTrailer(t *testing.T) { } } +// TestH2H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestH2H1HeaderFieldBuffer(t *testing.T) { + st := newServerTester([]string{"--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, 431; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + +// TestH2H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestH2H1HeaderFields(t *testing.T) { + st := newServerTester([]string{"--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.http2(requestParam{ + name: "TestH2H1HeaderFields", + // we have at least 4 pseudo-header fields sent, and + // that ensures that buffer limit exceeds. + }) + if err != nil { + t.Fatalf("Error st.http2() = %v", err) + } + if got, want := res.status, 431; got != want { + t.Errorf("status: %v; want %v", got, want) + } +} + // TestH2H1Upgrade tests HTTP Upgrade to HTTP/2 func TestH2H1Upgrade(t *testing.T) { st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {}) diff --git a/integration-tests/nghttpx_spdy_test.go b/integration-tests/nghttpx_spdy_test.go index 0b289397..232e1da8 100644 --- a/integration-tests/nghttpx_spdy_test.go +++ b/integration-tests/nghttpx_spdy_test.go @@ -170,6 +170,46 @@ func TestS3H1NoVia(t *testing.T) { } } +// TestS3H1HeaderFieldBuffer tests that request with header fields +// larger than configured buffer size is rejected. +func TestS3H1HeaderFieldBuffer(t *testing.T) { + st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.spdy(requestParam{ + name: "TestS3H1HeaderFieldBuffer", + }) + if err != nil { + t.Fatalf("Error st.spdy() = %v", err) + } + if got, want := res.spdyRstErrCode, spdy.InternalError; got != want { + t.Errorf("res.spdyRstErrCode: %v; want %v", got, want) + } +} + +// TestS3H1HeaderFields tests that request with header fields more +// than configured number is rejected. +func TestS3H1HeaderFields(t *testing.T) { + st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--max-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) { + t.Fatal("execution path should not be here") + }) + defer st.Close() + + res, err := st.spdy(requestParam{ + name: "TestS3H1HeaderFields", + // we have at least 5 pseudo-header fields sent, and + // that ensures that buffer limit exceeds. + }) + if err != nil { + t.Fatalf("Error st.spdy() = %v", err) + } + if got, want := res.spdyRstErrCode, spdy.InternalError; got != want { + t.Errorf("res.spdyRstErrCode: %v; want %v", got, want) + } +} + // TestS3H2ConnectFailure tests that server handles the situation that // connection attempt to HTTP/2 backend failed. func TestS3H2ConnectFailure(t *testing.T) { diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go index 3a4b5568..87e593b8 100644 --- a/integration-tests/server_tester.go +++ b/integration-tests/server_tester.go @@ -297,7 +297,19 @@ func (st *serverTester) http1(rp requestParam) (*serverResponse, error) { body = cbr } } - req, err := http.NewRequest(method, st.url, body) + + reqURL := st.url + + if rp.path != "" { + u, err := url.Parse(st.url) + if err != nil { + st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) + } + u.Path = rp.path + reqURL = u.String() + } + + req, err := http.NewRequest(method, reqURL, body) if err != nil { return nil, err } From a62778d6b05ad4af45242b83bdb56001f01600db Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Thu, 30 Apr 2015 00:28:18 +0200 Subject: [PATCH 07/35] fix comma at end of enumerator list [-Wpedantic] --- examples/tiny-nghttpd.c | 2 +- lib/nghttp2_hd.h | 2 +- lib/nghttp2_option.h | 2 +- lib/nghttp2_outbound_item.h | 2 +- lib/nghttp2_session.h | 6 +++--- lib/nghttp2_stream.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/tiny-nghttpd.c b/examples/tiny-nghttpd.c index a7677ee1..e11df975 100644 --- a/examples/tiny-nghttpd.c +++ b/examples/tiny-nghttpd.c @@ -190,7 +190,7 @@ typedef enum { NGHTTP2_TOKEN__METHOD, NGHTTP2_TOKEN__PATH, NGHTTP2_TOKEN__SCHEME, - NGHTTP2_TOKEN_HOST, + NGHTTP2_TOKEN_HOST } nghttp2_token; /* Inspired by h2o header lookup. https://github.com/h2o/h2o */ diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h index 4b76d1ce..1b9ca7e9 100644 --- a/lib/nghttp2_hd.h +++ b/lib/nghttp2_hd.h @@ -109,7 +109,7 @@ typedef enum { NGHTTP2_TOKEN_CONNECTION, NGHTTP2_TOKEN_KEEP_ALIVE, NGHTTP2_TOKEN_PROXY_CONNECTION, - NGHTTP2_TOKEN_UPGRADE, + NGHTTP2_TOKEN_UPGRADE } nghttp2_token; typedef enum { diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index db94be55..a79d0e77 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -58,7 +58,7 @@ typedef enum { */ NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1, NGHTTP2_OPT_RECV_CLIENT_PREFACE = 1 << 2, - NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, + NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3 } nghttp2_option_flag; /** diff --git a/lib/nghttp2_outbound_item.h b/lib/nghttp2_outbound_item.h index 110e0577..0be5cb76 100644 --- a/lib/nghttp2_outbound_item.h +++ b/lib/nghttp2_outbound_item.h @@ -81,7 +81,7 @@ typedef enum { /* indicates that this GOAWAY is just a notification for graceful shutdown. No nghttp2_session.goaway_flags should be updated on the reaction to this frame. */ - NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2, + NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2 } nghttp2_goaway_aux_flag; /* struct used for GOAWAY frame */ diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index fb08c652..0f5a4eee 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -47,7 +47,7 @@ typedef enum { NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0, NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE = 1 << 1, - NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2, + NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2 } nghttp2_optmask; typedef enum { @@ -84,7 +84,7 @@ typedef enum { NGHTTP2_IB_READ_PAD_DATA, NGHTTP2_IB_READ_DATA, NGHTTP2_IB_IGN_DATA, - NGHTTP2_IB_IGN_ALL, + NGHTTP2_IB_IGN_ALL } nghttp2_inbound_state; #define NGHTTP2_INBOUND_NUM_IV 7 @@ -136,7 +136,7 @@ typedef enum { /* Flag means GOAWAY was sent */ NGHTTP2_GOAWAY_SENT = 0x4, /* Flag means GOAWAY was received */ - NGHTTP2_GOAWAY_RECV = 0x8, + NGHTTP2_GOAWAY_RECV = 0x8 } nghttp2_goaway_flag; struct nghttp2_session { diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 65a7f10d..684459ba 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -133,7 +133,7 @@ typedef enum { /* "http" or "https" scheme */ NGHTTP2_HTTP_FLAG_SCHEME_HTTP = 1 << 12, /* set if final response is expected */ - NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13, + NGHTTP2_HTTP_FLAG_EXPECT_FINAL_RESPONSE = 1 << 13 } nghttp2_http_flag; typedef enum { From 8f0899a1902c56606e15495c9dc1d2c10aa0c882 Mon Sep 17 00:00:00 2001 From: Viacheslav Biriukov Date: Thu, 30 Apr 2015 18:22:51 +0300 Subject: [PATCH 08/35] mages to images typo --- doc/nghttp.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/nghttp.1 b/doc/nghttp.1 index 6167c112..ae965c76 100644 --- a/doc/nghttp.1 +++ b/doc/nghttp.1 @@ -269,7 +269,7 @@ to its resource type. .sp For CSS, and Javascript files inside "head" element, they depend on stream 3 with the weight 2. The Javascript files outside "head" -element depend on stream 5 with the weight 2. The mages depend on +element depend on stream 5 with the weight 2. The images depend on stream 11 with the weight 12. The other resources (e.g., icon) depend on stream 11 with the weight 2. .SH SEE ALSO From 7b3a33a3134254629cd88ae398ae09d2866ba972 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Fri, 1 May 2015 13:29:43 +0900 Subject: [PATCH 09/35] README.rst: Fix formatting and remove trailing spaces --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3dee09b4..3bade963 100644 --- a/README.rst +++ b/README.rst @@ -187,7 +187,7 @@ https://nghttp2.org/documentation/ Unit tests ---------- -Unit tests are done by simply running `make check`. +Unit tests are done by simply running ``make check``. Integration tests ----------------- @@ -1273,7 +1273,7 @@ original creator(s) or those who have been assigned copyright by the original author(s). By submitting a patch to the nghttp2 project, you (or your employer, as -the case may be) agree to assign the copyright of your submission to us. +the case may be) agree to assign the copyright of your submission to us. .. the above really needs to be reworded to pass legal muster. We will credit you for your changes as far as possible, to give credit but also to keep a trace From 4bba4bf66c08734f8fb181cf464b9889d4114cb9 Mon Sep 17 00:00:00 2001 From: es Date: Wed, 29 Apr 2015 15:29:37 -0700 Subject: [PATCH 10/35] update h2load to give connect time and ttfb stats finer statistics for h2load: update per comments from tatsuhiro-t finer stats for h2load: fixed formatting --- src/h2load.cc | 171 +++++++++++++++++++++++++++++++++++++++++--------- src/h2load.h | 26 +++++++- 2 files changed, 163 insertions(+), 34 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 5daff7b0..1dd3fd36 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -150,7 +150,7 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) { Client::Client(Worker *worker, size_t req_todo) : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0), - state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0), + state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo), req_started(0), req_done(0), fd(-1) { ev_io_init(&wev, writecb, 0, EV_WRITE); ev_io_init(&rev, readcb, 0, EV_READ); @@ -165,6 +165,8 @@ int Client::do_read() { return readfn(*this); } int Client::do_write() { return writefn(*this); } int Client::connect() { + record_start_time(&worker->stats); + while (next_addr) { auto addr = next_addr; next_addr = next_addr->ai_next; @@ -469,6 +471,8 @@ int Client::connection_made() { session->on_connect(); + record_connect_time(&worker->stats); + auto nreq = std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); @@ -519,6 +523,11 @@ int Client::read_clear() { if (on_read(buf, nread) != 0) { return -1; } + + if (!first_byte_received) { + first_byte_received = true; + record_time_to_first_byte(&worker->stats); + } } return 0; @@ -641,6 +650,11 @@ int Client::read_tls() { if (on_read(buf, rv) != 0) { return -1; } + + if (!first_byte_received) { + first_byte_received = true; + record_time_to_first_byte(&worker->stats); + } } } @@ -691,6 +705,18 @@ void Client::record_request_time(RequestStat *req_stat) { req_stat->request_time = std::chrono::steady_clock::now(); } +void Client::record_start_time(Stats *stat) { + stat->start_times.push_back(std::chrono::steady_clock::now()); +} + +void Client::record_connect_time(Stats *stat) { + stat->connect_times.push_back(std::chrono::steady_clock::now()); +} + +void Client::record_time_to_first_byte(Stats *stat) { + stat->time_to_first_bytes.push_back(std::chrono::steady_clock::now()); +} + void Client::signal_write() { ev_io_start(worker->loop, &wev); } Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients, @@ -733,19 +759,38 @@ void Worker::run() { namespace { double within_sd(const std::vector> &workers, const std::chrono::microseconds &mean, - const std::chrono::microseconds &sd, size_t n) { + const std::chrono::microseconds &sd, size_t n, TimeStatType type) { auto upper = mean.count() + sd.count(); auto lower = mean.count() - sd.count(); size_t m = 0; for (const auto &w : workers) { - for (const auto &req_stat : w->stats.req_stats) { - if (!req_stat.completed) { - continue; + if (type == STAT_REQUEST) { + for (const auto &req_stat : w->stats.req_stats) { + if (!req_stat.completed) { + continue; + } + auto t = std::chrono::duration_cast(req_stat.stream_close_time - req_stat.request_time); + if (lower <= t.count() && t.count() <= upper) { + ++m; + } } - auto t = std::chrono::duration_cast( - req_stat.stream_close_time - req_stat.request_time); - if (lower <= t.count() && t.count() <= upper) { - ++m; + } else { + const auto &stat = w->stats; + for (unsigned int i = 0; i < stat.start_times.size(); i++) { + if (i >= stat.connect_times.size() || i >= stat.time_to_first_bytes.size()) { + continue; //rule out cases where we started but didn't connect or get the first byte (errors) + } + std::chrono::microseconds t; + if (type == STAT_CONNECT) { + t = std::chrono::duration_cast(stat.connect_times[i] - stat.start_times[i]); + } else if (type == STAT_FIRST_BYTE) { + t = std::chrono::duration_cast(stat.time_to_first_bytes[i] - stat.start_times[i]); + } else { + return -1; + } + if (lower <= t.count() && t.count() <= upper) { + ++m; + } } } } @@ -757,43 +802,95 @@ namespace { TimeStats process_time_stats(const std::vector> &workers) { auto ts = TimeStats(); - int64_t sum = 0; + int64_t request_sum = 0; + int64_t connect_sum = 0; + int64_t ttfb_sum = 0; + size_t m = 0; size_t n = 0; - ts.time_min = std::chrono::microseconds::max(); - ts.time_max = std::chrono::microseconds::min(); - ts.within_sd = 0.; + ts.request_time_min = std::chrono::microseconds::max(); + ts.request_time_max = std::chrono::microseconds::min(); + ts.request_within_sd = 0.; + + ts.connect_time_min = std::chrono::microseconds::max(); + ts.connect_time_max = std::chrono::microseconds::min(); + ts.connect_within_sd = 0.; + + ts.ttfb_min = std::chrono::microseconds::max(); + ts.ttfb_max = std::chrono::microseconds::min(); + ts.ttfb_within_sd = 0.; // standard deviation calculated using Rapid calculation method: // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods - double a = 0, q = 0; + double request_a = 0, request_q = 0; + double connect_a = 0, connect_q = 0; + double ttfb_a = 0, ttfb_q = 0; for (const auto &w : workers) { + const auto stat = w->stats; for (const auto &req_stat : w->stats.req_stats) { if (!req_stat.completed) { continue; } ++n; - auto t = std::chrono::duration_cast( + auto request_t = std::chrono::duration_cast( req_stat.stream_close_time - req_stat.request_time); - ts.time_min = std::min(ts.time_min, t); - ts.time_max = std::max(ts.time_max, t); - sum += t.count(); + ts.request_time_min = std::min(ts.request_time_min, request_t); + ts.request_time_max = std::max(ts.request_time_max, request_t); + request_sum += request_t.count(); - auto na = a + (t.count() - a) / n; - q = q + (t.count() - a) * (t.count() - na); - a = na; + auto request_na = request_a + (request_t.count() - request_a) / n; + request_q = request_q + (request_t.count() - request_a) * (request_t.count() - request_na); + request_a = request_na; + } + for (unsigned int i = 0; i < stat.start_times.size(); i++) { + if (i >= stat.connect_times.size() || i >= stat.time_to_first_bytes.size()) { + continue; //rule out cases where we started but didn't connect or get the first byte (errors) + } + ++m; + auto connect_t = std::chrono::duration_cast( + stat.connect_times[i] - stat.start_times[i]); + ts.connect_time_min = std::min(ts.connect_time_min, connect_t); + ts.connect_time_max = std::max(ts.connect_time_max, connect_t); + connect_sum += connect_t.count(); + + auto ttfb_t = std::chrono::duration_cast( + stat.time_to_first_bytes[i] - stat.start_times[i]); + ts.ttfb_min = std::min(ts.ttfb_min, ttfb_t); + ts.ttfb_max = std::max(ts.ttfb_max, ttfb_t); + ttfb_sum += ttfb_t.count(); + + auto connect_na = connect_a + (connect_t.count() - connect_a) / m; + connect_q = connect_q + (connect_t.count() - connect_a) * (connect_t.count() - connect_na); + connect_a = connect_na; + + auto ttfb_na = ttfb_a + (ttfb_t.count() - ttfb_a) / m; + ttfb_q = ttfb_q + (ttfb_t.count() - ttfb_a) * (ttfb_t.count() - ttfb_na); + ttfb_a = ttfb_na; } } if (n == 0) { - ts.time_max = ts.time_min = std::chrono::microseconds::zero(); + ts.request_time_max = ts.request_time_min = std::chrono::microseconds::zero(); + ts.connect_time_max = ts.connect_time_min = std::chrono::microseconds::zero(); + ts.ttfb_max = ts.ttfb_min = std::chrono::microseconds::zero(); return ts; } - ts.time_mean = std::chrono::microseconds(sum / n); - ts.time_sd = std::chrono::microseconds( - static_cast(sqrt(q / n))); + ts.request_time_mean = std::chrono::microseconds(request_sum / n); + ts.request_time_sd = std::chrono::microseconds( + static_cast(sqrt(request_q / n))); - ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n); + ts.connect_time_mean = std::chrono::microseconds(connect_sum / m); + ts.connect_time_sd = std::chrono::microseconds( + static_cast(sqrt(connect_q / m))); + + ts.ttfb_mean = std::chrono::microseconds(ttfb_sum / m); + ts.ttfb_sd = std::chrono::microseconds( + static_cast(sqrt(ttfb_q / m))); + + + ts.request_within_sd = within_sd(workers, ts.request_time_mean, ts.request_time_sd, n, STAT_REQUEST); + ts.connect_within_sd = within_sd(workers, ts.connect_time_mean, ts.connect_time_sd, m, STAT_CONNECT); + ts.ttfb_within_sd = within_sd(workers, ts.ttfb_mean, ts.ttfb_sd, m, STAT_FIRST_BYTE); return ts; } } // namespace @@ -1440,12 +1537,24 @@ traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head << " bytes headers, " << stats.bytes_body << R"( bytes data min max mean sd +/- sd time for request: )" << std::setw(10) - << util::format_duration(time_stats.time_min) << " " - << std::setw(10) << util::format_duration(time_stats.time_max) + << util::format_duration(time_stats.request_time_min) << " " + << std::setw(10) << util::format_duration(time_stats.request_time_max) << " " << std::setw(10) - << util::format_duration(time_stats.time_mean) << " " - << std::setw(10) << util::format_duration(time_stats.time_sd) - << std::setw(9) << util::dtos(time_stats.within_sd) << "%" + << util::format_duration(time_stats.request_time_mean) << " " + << std::setw(10) << util::format_duration(time_stats.request_time_sd) + << std::setw(9) << util::dtos(time_stats.request_within_sd) << "%" << "\ntime for connect: " << std::setw(10) + << util::format_duration(time_stats.connect_time_min) << " " + << std::setw(10) << util::format_duration(time_stats.connect_time_max) + << " " << std::setw(10) + << util::format_duration(time_stats.connect_time_mean) << " " + << std::setw(10) << util::format_duration(time_stats.connect_time_sd) + << std::setw(9) << util::dtos(time_stats.connect_within_sd) << "%" << "\ntime to 1st byte: " << std::setw(10) + << util::format_duration(time_stats.ttfb_min) << " " + << std::setw(10) << util::format_duration(time_stats.ttfb_max) + << " " << std::setw(10) + << util::format_duration(time_stats.ttfb_mean) << " " + << std::setw(10) << util::format_duration(time_stats.ttfb_sd) + << std::setw(9) << util::dtos(time_stats.ttfb_within_sd) << "%" << std::endl; SSL_CTX_free(ssl_ctx); diff --git a/src/h2load.h b/src/h2load.h index 6a837c1a..9df35f5e 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -87,7 +87,7 @@ struct RequestStat { std::chrono::steady_clock::time_point request_time; // time point when stream was closed std::chrono::steady_clock::time_point stream_close_time; - // upload data length sent so far + // upload data length sent so far int64_t data_offset; // true if stream was successfully closed. This means stream was // not reset, but it does not mean HTTP level error (e.g., 404). @@ -96,11 +96,21 @@ struct RequestStat { struct TimeStats { // time for request: max, min, mean and sd (standard deviation) - std::chrono::microseconds time_max, time_min, time_mean, time_sd; + std::chrono::microseconds request_time_max, request_time_min, request_time_mean, request_time_sd; // percentage of number of requests inside mean -/+ sd - double within_sd; + double request_within_sd; + // time for connect: max, min, mean and sd (standard deviation) + std::chrono::microseconds connect_time_max, connect_time_min, connect_time_mean, connect_time_sd; + // percentage of number of connects inside mean -/+ sd + double connect_within_sd; + // time to first byte: max, min, mean and sd (standard deviation) + std::chrono::microseconds ttfb_max, ttfb_min, ttfb_mean, ttfb_sd; + // percentage of number of connects inside mean -/+ sd + double ttfb_within_sd; }; +enum TimeStatType { STAT_REQUEST, STAT_CONNECT, STAT_FIRST_BYTE }; + struct Stats { Stats(size_t req_todo); // The total number of requests @@ -132,6 +142,12 @@ struct Stats { std::array status; // The statistics per request std::vector req_stats; + // time connect starts + std::vector start_times; + // time to connect + std::vector connect_times; + // time to first byte + std::vector time_to_first_bytes; }; enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED }; @@ -171,6 +187,7 @@ struct Client { addrinfo *next_addr; size_t reqidx; ClientState state; + bool first_byte_received; // The number of requests this client has to issue. size_t req_todo; // The number of requests this client has issued so far. @@ -215,6 +232,9 @@ struct Client { void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat); void record_request_time(RequestStat *req_stat); + void record_start_time(Stats *stat); + void record_connect_time(Stats *stat); + void record_time_to_first_byte(Stats *stat); void signal_write(); }; From 3137dc4a704f341b65058651fac844f80a6c30f9 Mon Sep 17 00:00:00 2001 From: Alex Nalivko Date: Sat, 2 May 2015 19:33:04 +0000 Subject: [PATCH 11/35] h2load_spdy_session errno include --- src/h2load_spdy_session.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc index 99fa1de7..60258b76 100644 --- a/src/h2load_spdy_session.cc +++ b/src/h2load_spdy_session.cc @@ -25,6 +25,7 @@ #include "h2load_spdy_session.h" #include +#include #include "h2load.h" #include "util.h" From b4e8bea4b5816b55770e8ac8b962ad12182db5f3 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 3 May 2015 16:47:32 +0900 Subject: [PATCH 12/35] clang-format --- src/h2load.cc | 88 ++++++++++++++++++++++++++++++++------------------- src/h2load.h | 10 +++--- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 9737ef77..75b3fa78 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -150,8 +150,8 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) { Client::Client(Worker *worker, size_t req_todo) : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0), - state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo), req_started(0), req_done(0), - fd(-1) { + state(CLIENT_IDLE), first_byte_received(false), req_todo(req_todo), + req_started(0), req_done(0), fd(-1) { ev_io_init(&wev, writecb, 0, EV_WRITE); ev_io_init(&rev, readcb, 0, EV_READ); @@ -472,7 +472,7 @@ int Client::connection_made() { session->on_connect(); record_connect_time(&worker->stats); - + auto nreq = std::min(req_todo - req_started, (size_t)config.max_concurrent_streams); @@ -523,11 +523,11 @@ int Client::read_clear() { if (on_read(buf, nread) != 0) { return -1; } - + if (!first_byte_received) { first_byte_received = true; record_time_to_first_byte(&worker->stats); - } + } } return 0; @@ -650,11 +650,11 @@ int Client::read_tls() { if (on_read(buf, rv) != 0) { return -1; } - + if (!first_byte_received) { first_byte_received = true; record_time_to_first_byte(&worker->stats); - } + } } } @@ -759,7 +759,8 @@ void Worker::run() { namespace { double within_sd(const std::vector> &workers, const std::chrono::microseconds &mean, - const std::chrono::microseconds &sd, size_t n, TimeStatType type) { + const std::chrono::microseconds &sd, size_t n, + TimeStatType type) { auto upper = mean.count() + sd.count(); auto lower = mean.count() - sd.count(); size_t m = 0; @@ -769,7 +770,8 @@ double within_sd(const std::vector> &workers, if (!req_stat.completed) { continue; } - auto t = std::chrono::duration_cast(req_stat.stream_close_time - req_stat.request_time); + auto t = std::chrono::duration_cast( + req_stat.stream_close_time - req_stat.request_time); if (lower <= t.count() && t.count() <= upper) { ++m; } @@ -777,14 +779,18 @@ double within_sd(const std::vector> &workers, } else { const auto &stat = w->stats; for (unsigned int i = 0; i < stat.start_times.size(); i++) { - if (i >= stat.connect_times.size() || i >= stat.time_to_first_bytes.size()) { - continue; //rule out cases where we started but didn't connect or get the first byte (errors) + if (i >= stat.connect_times.size() || + i >= stat.time_to_first_bytes.size()) { + continue; // rule out cases where we started but didn't connect or get + // the first byte (errors) } std::chrono::microseconds t; if (type == STAT_CONNECT) { - t = std::chrono::duration_cast(stat.connect_times[i] - stat.start_times[i]); + t = std::chrono::duration_cast( + stat.connect_times[i] - stat.start_times[i]); } else if (type == STAT_FIRST_BYTE) { - t = std::chrono::duration_cast(stat.time_to_first_bytes[i] - stat.start_times[i]); + t = std::chrono::duration_cast( + stat.time_to_first_bytes[i] - stat.start_times[i]); } else { return -1; } @@ -839,28 +845,34 @@ process_time_stats(const std::vector> &workers) { request_sum += request_t.count(); auto request_na = request_a + (request_t.count() - request_a) / n; - request_q = request_q + (request_t.count() - request_a) * (request_t.count() - request_na); + request_q = + request_q + + (request_t.count() - request_a) * (request_t.count() - request_na); request_a = request_na; } for (unsigned int i = 0; i < stat.start_times.size(); i++) { - if (i >= stat.connect_times.size() || i >= stat.time_to_first_bytes.size()) { - continue; //rule out cases where we started but didn't connect or get the first byte (errors) + if (i >= stat.connect_times.size() || + i >= stat.time_to_first_bytes.size()) { + continue; // rule out cases where we started but didn't connect or get + // the first byte (errors) } ++m; auto connect_t = std::chrono::duration_cast( - stat.connect_times[i] - stat.start_times[i]); + stat.connect_times[i] - stat.start_times[i]); ts.connect_time_min = std::min(ts.connect_time_min, connect_t); ts.connect_time_max = std::max(ts.connect_time_max, connect_t); connect_sum += connect_t.count(); auto ttfb_t = std::chrono::duration_cast( - stat.time_to_first_bytes[i] - stat.start_times[i]); + stat.time_to_first_bytes[i] - stat.start_times[i]); ts.ttfb_min = std::min(ts.ttfb_min, ttfb_t); ts.ttfb_max = std::max(ts.ttfb_max, ttfb_t); ttfb_sum += ttfb_t.count(); auto connect_na = connect_a + (connect_t.count() - connect_a) / m; - connect_q = connect_q + (connect_t.count() - connect_a) * (connect_t.count() - connect_na); + connect_q = + connect_q + + (connect_t.count() - connect_a) * (connect_t.count() - connect_na); connect_a = connect_na; auto ttfb_na = ttfb_a + (ttfb_t.count() - ttfb_a) / m; @@ -869,8 +881,10 @@ process_time_stats(const std::vector> &workers) { } } if (n == 0) { - ts.request_time_max = ts.request_time_min = std::chrono::microseconds::zero(); - ts.connect_time_max = ts.connect_time_min = std::chrono::microseconds::zero(); + ts.request_time_max = ts.request_time_min = + std::chrono::microseconds::zero(); + ts.connect_time_max = ts.connect_time_min = + std::chrono::microseconds::zero(); ts.ttfb_max = ts.ttfb_min = std::chrono::microseconds::zero(); return ts; } @@ -887,10 +901,12 @@ process_time_stats(const std::vector> &workers) { ts.ttfb_sd = std::chrono::microseconds( static_cast(sqrt(ttfb_q / m))); - - ts.request_within_sd = within_sd(workers, ts.request_time_mean, ts.request_time_sd, n, STAT_REQUEST); - ts.connect_within_sd = within_sd(workers, ts.connect_time_mean, ts.connect_time_sd, m, STAT_CONNECT); - ts.ttfb_within_sd = within_sd(workers, ts.ttfb_mean, ts.ttfb_sd, m, STAT_FIRST_BYTE); + ts.request_within_sd = within_sd(workers, ts.request_time_mean, + ts.request_time_sd, n, STAT_REQUEST); + ts.connect_within_sd = within_sd(workers, ts.connect_time_mean, + ts.connect_time_sd, m, STAT_CONNECT); + ts.ttfb_within_sd = + within_sd(workers, ts.ttfb_mean, ts.ttfb_sd, m, STAT_FIRST_BYTE); return ts; } } // namespace @@ -1540,17 +1556,23 @@ traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head min max mean sd +/- sd time for request: )" << std::setw(10) << util::format_duration(time_stats.request_time_min) << " " - << std::setw(10) << util::format_duration(time_stats.request_time_max) - << " " << std::setw(10) + << std::setw(10) + << util::format_duration(time_stats.request_time_max) << " " + << std::setw(10) << util::format_duration(time_stats.request_time_mean) << " " - << std::setw(10) << util::format_duration(time_stats.request_time_sd) - << std::setw(9) << util::dtos(time_stats.request_within_sd) << "%" << "\ntime for connect: " << std::setw(10) + << std::setw(10) + << util::format_duration(time_stats.request_time_sd) << std::setw(9) + << util::dtos(time_stats.request_within_sd) << "%" + << "\ntime for connect: " << std::setw(10) << util::format_duration(time_stats.connect_time_min) << " " - << std::setw(10) << util::format_duration(time_stats.connect_time_max) - << " " << std::setw(10) + << std::setw(10) + << util::format_duration(time_stats.connect_time_max) << " " + << std::setw(10) << util::format_duration(time_stats.connect_time_mean) << " " - << std::setw(10) << util::format_duration(time_stats.connect_time_sd) - << std::setw(9) << util::dtos(time_stats.connect_within_sd) << "%" << "\ntime to 1st byte: " << std::setw(10) + << std::setw(10) + << util::format_duration(time_stats.connect_time_sd) << std::setw(9) + << util::dtos(time_stats.connect_within_sd) << "%" + << "\ntime to 1st byte: " << std::setw(10) << util::format_duration(time_stats.ttfb_min) << " " << std::setw(10) << util::format_duration(time_stats.ttfb_max) << " " << std::setw(10) diff --git a/src/h2load.h b/src/h2load.h index 9df35f5e..2f2f1e10 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -87,7 +87,7 @@ struct RequestStat { std::chrono::steady_clock::time_point request_time; // time point when stream was closed std::chrono::steady_clock::time_point stream_close_time; - // upload data length sent so far + // upload data length sent so far int64_t data_offset; // true if stream was successfully closed. This means stream was // not reset, but it does not mean HTTP level error (e.g., 404). @@ -96,11 +96,13 @@ struct RequestStat { struct TimeStats { // time for request: max, min, mean and sd (standard deviation) - std::chrono::microseconds request_time_max, request_time_min, request_time_mean, request_time_sd; + std::chrono::microseconds request_time_max, request_time_min, + request_time_mean, request_time_sd; // percentage of number of requests inside mean -/+ sd double request_within_sd; // time for connect: max, min, mean and sd (standard deviation) - std::chrono::microseconds connect_time_max, connect_time_min, connect_time_mean, connect_time_sd; + std::chrono::microseconds connect_time_max, connect_time_min, + connect_time_mean, connect_time_sd; // percentage of number of connects inside mean -/+ sd double connect_within_sd; // time to first byte: max, min, mean and sd (standard deviation) @@ -142,7 +144,7 @@ struct Stats { std::array status; // The statistics per request std::vector req_stats; - // time connect starts + // time connect starts std::vector start_times; // time to connect std::vector connect_times; From cc46d363c5ec3047786a17f590fd85fa92d622ab Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 4 May 2015 11:48:21 +0900 Subject: [PATCH 13/35] h2load: Refactor statistics hanlding to scale more upcoming new metrics --- src/h2load.cc | 257 +++++++++++++++++++------------------------------- src/h2load.h | 34 +++---- 2 files changed, 112 insertions(+), 179 deletions(-) diff --git a/src/h2load.cc b/src/h2load.cc index 75b3fa78..8e1fa822 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -526,7 +526,7 @@ int Client::read_clear() { if (!first_byte_received) { first_byte_received = true; - record_time_to_first_byte(&worker->stats); + record_ttfb(&worker->stats); } } @@ -653,7 +653,7 @@ int Client::read_tls() { if (!first_byte_received) { first_byte_received = true; - record_time_to_first_byte(&worker->stats); + record_ttfb(&worker->stats); } } } @@ -713,8 +713,8 @@ void Client::record_connect_time(Stats *stat) { stat->connect_times.push_back(std::chrono::steady_clock::now()); } -void Client::record_time_to_first_byte(Stats *stat) { - stat->time_to_first_bytes.push_back(std::chrono::steady_clock::now()); +void Client::record_ttfb(Stats *stat) { + stat->ttfbs.push_back(std::chrono::steady_clock::now()); } void Client::signal_write() { ev_io_start(worker->loop, &wev); } @@ -757,157 +757,99 @@ void Worker::run() { } namespace { -double within_sd(const std::vector> &workers, - const std::chrono::microseconds &mean, - const std::chrono::microseconds &sd, size_t n, - TimeStatType type) { - auto upper = mean.count() + sd.count(); - auto lower = mean.count() - sd.count(); - size_t m = 0; - for (const auto &w : workers) { - if (type == STAT_REQUEST) { - for (const auto &req_stat : w->stats.req_stats) { - if (!req_stat.completed) { - continue; - } - auto t = std::chrono::duration_cast( - req_stat.stream_close_time - req_stat.request_time); - if (lower <= t.count() && t.count() <= upper) { - ++m; - } - } - } else { - const auto &stat = w->stats; - for (unsigned int i = 0; i < stat.start_times.size(); i++) { - if (i >= stat.connect_times.size() || - i >= stat.time_to_first_bytes.size()) { - continue; // rule out cases where we started but didn't connect or get - // the first byte (errors) - } - std::chrono::microseconds t; - if (type == STAT_CONNECT) { - t = std::chrono::duration_cast( - stat.connect_times[i] - stat.start_times[i]); - } else if (type == STAT_FIRST_BYTE) { - t = std::chrono::duration_cast( - stat.time_to_first_bytes[i] - stat.start_times[i]); - } else { - return -1; - } - if (lower <= t.count() && t.count() <= upper) { - ++m; - } - } - } +// Returns percentage of number of samples within mean +/- sd. +template +double within_sd(const std::vector &samples, const Duration &mean, + const Duration &sd) { + if (samples.size() == 0) { + return 0.0; } - return (m / static_cast(n)) * 100; + auto lower = mean - sd; + auto upper = mean + sd; + auto m = std::count_if( + std::begin(samples), std::end(samples), + [&lower, &upper](const Duration &t) { return lower <= t && t <= upper; }); + return (m / static_cast(samples.size())) * 100; +} +} // namespace + +namespace { +// Computes statistics using |samples|. The min, max, mean, sd, and +// percentage of number of samples within mean +/- sd are computed. +template +TimeStat compute_time_stat(const std::vector &samples) { + if (samples.size() == 0) { + return {Duration::zero(), Duration::zero(), Duration::zero(), + Duration::zero(), 0.0}; + } + // standard deviation calculated using Rapid calculation method: + // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods + double a = 0, q = 0; + size_t n = 0; + int64_t sum = 0; + auto res = TimeStat{Duration::max(), Duration::min()}; + for (const auto &t : samples) { + ++n; + res.min = std::min(res.min, t); + res.max = std::max(res.max, t); + sum += t.count(); + + auto na = a + (t.count() - a) / n; + q += (t.count() - a) * (t.count() - na); + a = na; + } + + res.mean = Duration(sum / n); + res.sd = Duration(static_cast(sqrt(q / n))); + res.within_sd = within_sd(samples, res.mean, res.sd); + + return res; } } // namespace namespace { TimeStats process_time_stats(const std::vector> &workers) { - auto ts = TimeStats(); - int64_t request_sum = 0; - int64_t connect_sum = 0; - int64_t ttfb_sum = 0; - size_t m = 0; - size_t n = 0; - - ts.request_time_min = std::chrono::microseconds::max(); - ts.request_time_max = std::chrono::microseconds::min(); - ts.request_within_sd = 0.; - - ts.connect_time_min = std::chrono::microseconds::max(); - ts.connect_time_max = std::chrono::microseconds::min(); - ts.connect_within_sd = 0.; - - ts.ttfb_min = std::chrono::microseconds::max(); - ts.ttfb_max = std::chrono::microseconds::min(); - ts.ttfb_within_sd = 0.; - - // standard deviation calculated using Rapid calculation method: - // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods - double request_a = 0, request_q = 0; - double connect_a = 0, connect_q = 0; - double ttfb_a = 0, ttfb_q = 0; + size_t nrequest_times = 0, nttfb_times = 0; + for (const auto &w : workers) { + nrequest_times += w->stats.req_stats.size(); + nttfb_times += w->stats.ttfbs.size(); + } + + std::vector request_times; + request_times.reserve(nrequest_times); + std::vector connect_times, ttfb_times; + connect_times.reserve(nttfb_times); + ttfb_times.reserve(nttfb_times); + for (const auto &w : workers) { - const auto stat = w->stats; for (const auto &req_stat : w->stats.req_stats) { if (!req_stat.completed) { continue; } - ++n; - auto request_t = std::chrono::duration_cast( - req_stat.stream_close_time - req_stat.request_time); - ts.request_time_min = std::min(ts.request_time_min, request_t); - ts.request_time_max = std::max(ts.request_time_max, request_t); - request_sum += request_t.count(); - - auto request_na = request_a + (request_t.count() - request_a) / n; - request_q = - request_q + - (request_t.count() - request_a) * (request_t.count() - request_na); - request_a = request_na; + request_times.push_back( + std::chrono::duration_cast( + req_stat.stream_close_time - req_stat.request_time)); } - for (unsigned int i = 0; i < stat.start_times.size(); i++) { - if (i >= stat.connect_times.size() || - i >= stat.time_to_first_bytes.size()) { - continue; // rule out cases where we started but didn't connect or get - // the first byte (errors) - } - ++m; - auto connect_t = std::chrono::duration_cast( - stat.connect_times[i] - stat.start_times[i]); - ts.connect_time_min = std::min(ts.connect_time_min, connect_t); - ts.connect_time_max = std::max(ts.connect_time_max, connect_t); - connect_sum += connect_t.count(); - auto ttfb_t = std::chrono::duration_cast( - stat.time_to_first_bytes[i] - stat.start_times[i]); - ts.ttfb_min = std::min(ts.ttfb_min, ttfb_t); - ts.ttfb_max = std::max(ts.ttfb_max, ttfb_t); - ttfb_sum += ttfb_t.count(); + const auto &stat = w->stats; + // rule out cases where we started but didn't connect or get the + // first byte (errors). We will get connect event before FFTB. + assert(stat.start_times.size() >= stat.ttfbs.size()); + assert(stat.connect_times.size() >= stat.ttfbs.size()); + for (size_t i = 0; i < stat.ttfbs.size(); ++i) { + connect_times.push_back( + std::chrono::duration_cast( + stat.connect_times[i] - stat.start_times[i])); - auto connect_na = connect_a + (connect_t.count() - connect_a) / m; - connect_q = - connect_q + - (connect_t.count() - connect_a) * (connect_t.count() - connect_na); - connect_a = connect_na; - - auto ttfb_na = ttfb_a + (ttfb_t.count() - ttfb_a) / m; - ttfb_q = ttfb_q + (ttfb_t.count() - ttfb_a) * (ttfb_t.count() - ttfb_na); - ttfb_a = ttfb_na; + ttfb_times.push_back( + std::chrono::duration_cast( + stat.ttfbs[i] - stat.start_times[i])); } } - if (n == 0) { - ts.request_time_max = ts.request_time_min = - std::chrono::microseconds::zero(); - ts.connect_time_max = ts.connect_time_min = - std::chrono::microseconds::zero(); - ts.ttfb_max = ts.ttfb_min = std::chrono::microseconds::zero(); - return ts; - } - ts.request_time_mean = std::chrono::microseconds(request_sum / n); - ts.request_time_sd = std::chrono::microseconds( - static_cast(sqrt(request_q / n))); - - ts.connect_time_mean = std::chrono::microseconds(connect_sum / m); - ts.connect_time_sd = std::chrono::microseconds( - static_cast(sqrt(connect_q / m))); - - ts.ttfb_mean = std::chrono::microseconds(ttfb_sum / m); - ts.ttfb_sd = std::chrono::microseconds( - static_cast(sqrt(ttfb_q / m))); - - ts.request_within_sd = within_sd(workers, ts.request_time_mean, - ts.request_time_sd, n, STAT_REQUEST); - ts.connect_within_sd = within_sd(workers, ts.connect_time_mean, - ts.connect_time_sd, m, STAT_CONNECT); - ts.ttfb_within_sd = - within_sd(workers, ts.ttfb_mean, ts.ttfb_sd, m, STAT_FIRST_BYTE); - return ts; + return {compute_time_stat(request_times), compute_time_stat(connect_times), + compute_time_stat(ttfb_times)}; } } // namespace @@ -1521,7 +1463,7 @@ int main(int argc, char **argv) { } } - auto time_stats = process_time_stats(workers); + auto ts = process_time_stats(workers); // Requests which have not been issued due to connection errors, are // counted towards req_failed and req_error. @@ -1554,32 +1496,23 @@ status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, " traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head << " bytes headers, " << stats.bytes_body << R"( bytes data min max mean sd +/- sd -time for request: )" << std::setw(10) - << util::format_duration(time_stats.request_time_min) << " " - << std::setw(10) - << util::format_duration(time_stats.request_time_max) << " " - << std::setw(10) - << util::format_duration(time_stats.request_time_mean) << " " - << std::setw(10) - << util::format_duration(time_stats.request_time_sd) << std::setw(9) - << util::dtos(time_stats.request_within_sd) << "%" +time for request: )" << std::setw(10) << util::format_duration(ts.request.min) + << " " << std::setw(10) << util::format_duration(ts.request.max) + << " " << std::setw(10) << util::format_duration(ts.request.mean) + << " " << std::setw(10) << util::format_duration(ts.request.sd) + << std::setw(9) << util::dtos(ts.request.within_sd) << "%" << "\ntime for connect: " << std::setw(10) - << util::format_duration(time_stats.connect_time_min) << " " - << std::setw(10) - << util::format_duration(time_stats.connect_time_max) << " " - << std::setw(10) - << util::format_duration(time_stats.connect_time_mean) << " " - << std::setw(10) - << util::format_duration(time_stats.connect_time_sd) << std::setw(9) - << util::dtos(time_stats.connect_within_sd) << "%" + << util::format_duration(ts.connect.min) << " " << std::setw(10) + << util::format_duration(ts.connect.max) << " " << std::setw(10) + << util::format_duration(ts.connect.mean) << " " << std::setw(10) + << util::format_duration(ts.connect.sd) << std::setw(9) + << util::dtos(ts.connect.within_sd) << "%" << "\ntime to 1st byte: " << std::setw(10) - << util::format_duration(time_stats.ttfb_min) << " " - << std::setw(10) << util::format_duration(time_stats.ttfb_max) - << " " << std::setw(10) - << util::format_duration(time_stats.ttfb_mean) << " " - << std::setw(10) << util::format_duration(time_stats.ttfb_sd) - << std::setw(9) << util::dtos(time_stats.ttfb_within_sd) << "%" - << std::endl; + << util::format_duration(ts.ttfb.min) << " " << std::setw(10) + << util::format_duration(ts.ttfb.max) << " " << std::setw(10) + << util::format_duration(ts.ttfb.mean) << " " << std::setw(10) + << util::format_duration(ts.ttfb.sd) << std::setw(9) + << util::dtos(ts.ttfb.within_sd) << "%" << std::endl; SSL_CTX_free(ssl_ctx); diff --git a/src/h2load.h b/src/h2load.h index 2f2f1e10..e731a305 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -94,21 +94,21 @@ struct RequestStat { bool completed; }; +template +struct TimeStat { + // min, max, mean and sd (standard deviation) + Duration min, max, mean, sd; + // percentage of samples inside mean -/+ sd + double within_sd; +}; + struct TimeStats { - // time for request: max, min, mean and sd (standard deviation) - std::chrono::microseconds request_time_max, request_time_min, - request_time_mean, request_time_sd; - // percentage of number of requests inside mean -/+ sd - double request_within_sd; - // time for connect: max, min, mean and sd (standard deviation) - std::chrono::microseconds connect_time_max, connect_time_min, - connect_time_mean, connect_time_sd; - // percentage of number of connects inside mean -/+ sd - double connect_within_sd; - // time to first byte: max, min, mean and sd (standard deviation) - std::chrono::microseconds ttfb_max, ttfb_min, ttfb_mean, ttfb_sd; - // percentage of number of connects inside mean -/+ sd - double ttfb_within_sd; + // time for request + TimeStat request; + // time for connect + TimeStat connect; + // time to first byte (TTFB) + TimeStat ttfb; }; enum TimeStatType { STAT_REQUEST, STAT_CONNECT, STAT_FIRST_BYTE }; @@ -148,8 +148,8 @@ struct Stats { std::vector start_times; // time to connect std::vector connect_times; - // time to first byte - std::vector time_to_first_bytes; + // time to first byte (TTFB) + std::vector ttfbs; }; enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED }; @@ -236,7 +236,7 @@ struct Client { void record_request_time(RequestStat *req_stat); void record_start_time(Stats *stat); void record_connect_time(Stats *stat); - void record_time_to_first_byte(Stats *stat); + void record_ttfb(Stats *stat); void signal_write(); }; From 1ab707713f1bd53a2bea5691111455793c0be590 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 4 May 2015 22:45:34 +0900 Subject: [PATCH 14/35] nghttpx: Accept reference instead of pointer by upstream_accesslog --- src/shrpx_client_handler.cc | 59 ++++++++++++++++++------------------- src/shrpx_log.cc | 34 ++++++++++----------- src/shrpx_log.h | 3 +- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 48037a54..063c8388 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -691,26 +691,26 @@ void ClientHandler::start_immediate_shutdown() { } void ClientHandler::write_accesslog(Downstream *downstream) { - LogSpec lgsp = { - downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(), + upstream_accesslog( + get_config()->accesslog_format, + LogSpec{ + downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(), - downstream->get_request_path().empty() - ? downstream->get_request_http2_authority().c_str() - : downstream->get_request_path().c_str(), + downstream->get_request_path().empty() + ? downstream->get_request_http2_authority().c_str() + : downstream->get_request_path().c_str(), - alpn_.c_str(), + alpn_.c_str(), - std::chrono::system_clock::now(), // time_now - downstream->get_request_start_time(), // request_start_time - std::chrono::high_resolution_clock::now(), // request_end_time + std::chrono::system_clock::now(), // time_now + downstream->get_request_start_time(), // request_start_time + std::chrono::high_resolution_clock::now(), // request_end_time - downstream->get_request_major(), downstream->get_request_minor(), - downstream->get_response_http_status(), - downstream->get_response_sent_bodylen(), port_.c_str(), - get_config()->port, get_config()->pid, - }; - - upstream_accesslog(get_config()->accesslog_format, &lgsp); + downstream->get_request_major(), downstream->get_request_minor(), + downstream->get_response_http_status(), + downstream->get_response_sent_bodylen(), port_.c_str(), + get_config()->port, get_config()->pid, + }); } void ClientHandler::write_accesslog(int major, int minor, unsigned int status, @@ -718,20 +718,19 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status, auto time_now = std::chrono::system_clock::now(); auto highres_now = std::chrono::high_resolution_clock::now(); - LogSpec lgsp = { - nullptr, ipaddr_.c_str(), - "-", // method - "-", // path, - alpn_.c_str(), time_now, - highres_now, // request_start_time TODO is - // there a better value? - highres_now, // request_end_time - major, minor, // major, minor - status, body_bytes_sent, port_.c_str(), - get_config()->port, get_config()->pid, - }; - - upstream_accesslog(get_config()->accesslog_format, &lgsp); + upstream_accesslog(get_config()->accesslog_format, + LogSpec{ + nullptr, ipaddr_.c_str(), + "-", // method + "-", // path, + alpn_.c_str(), time_now, + highres_now, // request_start_time TODO is + // there a better value? + highres_now, // request_end_time + major, minor, // major, minor + status, body_bytes_sent, port_.c_str(), + get_config()->port, get_config()->pid, + }); } ClientHandler::WriteBuf *ClientHandler::get_wb() { return &wb_; } diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc index 421dcb06..621aad8e 100644 --- a/src/shrpx_log.cc +++ b/src/shrpx_log.cc @@ -158,7 +158,8 @@ std::pair copy(const char *src, size_t avail, } } // namespace -void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { +void upstream_accesslog(const std::vector &lfv, + const LogSpec &lgsp) { auto lgconf = log_config(); if (lgconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) { @@ -167,12 +168,12 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { char buf[4096]; - auto downstream = lgsp->downstream; + auto downstream = lgsp.downstream; auto p = buf; auto avail = sizeof(buf) - 2; - lgconf->update_tstamp(lgsp->time_now); + lgconf->update_tstamp(lgsp.time_now); auto &time_local = lgconf->time_local_str; auto &time_iso8601 = lgconf->time_iso8601_str; @@ -182,7 +183,7 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { std::tie(p, avail) = copy(lf.value.get(), avail, p); break; case SHRPX_LOGF_REMOTE_ADDR: - std::tie(p, avail) = copy(lgsp->remote_addr, avail, p); + std::tie(p, avail) = copy(lgsp.remote_addr, avail, p); break; case SHRPX_LOGF_TIME_LOCAL: std::tie(p, avail) = copy(time_local.c_str(), avail, p); @@ -191,22 +192,22 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { std::tie(p, avail) = copy(time_iso8601.c_str(), avail, p); break; case SHRPX_LOGF_REQUEST: - std::tie(p, avail) = copy(lgsp->method, avail, p); + std::tie(p, avail) = copy(lgsp.method, avail, p); std::tie(p, avail) = copy(" ", avail, p); - std::tie(p, avail) = copy(lgsp->path, avail, p); + std::tie(p, avail) = copy(lgsp.path, avail, p); std::tie(p, avail) = copy(" HTTP/", avail, p); - std::tie(p, avail) = copy(util::utos(lgsp->major).c_str(), avail, p); - if (lgsp->major < 2) { + std::tie(p, avail) = copy(util::utos(lgsp.major).c_str(), avail, p); + if (lgsp.major < 2) { std::tie(p, avail) = copy(".", avail, p); - std::tie(p, avail) = copy(util::utos(lgsp->minor).c_str(), avail, p); + std::tie(p, avail) = copy(util::utos(lgsp.minor).c_str(), avail, p); } break; case SHRPX_LOGF_STATUS: - std::tie(p, avail) = copy(util::utos(lgsp->status).c_str(), avail, p); + std::tie(p, avail) = copy(util::utos(lgsp.status).c_str(), avail, p); break; case SHRPX_LOGF_BODY_BYTES_SENT: std::tie(p, avail) = - copy(util::utos(lgsp->body_bytes_sent).c_str(), avail, p); + copy(util::utos(lgsp.body_bytes_sent).c_str(), avail, p); break; case SHRPX_LOGF_HTTP: if (downstream) { @@ -221,15 +222,14 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { break; case SHRPX_LOGF_REMOTE_PORT: - std::tie(p, avail) = copy(lgsp->remote_port, avail, p); + std::tie(p, avail) = copy(lgsp.remote_port, avail, p); break; case SHRPX_LOGF_SERVER_PORT: - std::tie(p, avail) = - copy(util::utos(lgsp->server_port).c_str(), avail, p); + std::tie(p, avail) = copy(util::utos(lgsp.server_port).c_str(), avail, p); break; case SHRPX_LOGF_REQUEST_TIME: { auto t = std::chrono::duration_cast( - lgsp->request_end_time - lgsp->request_start_time).count(); + lgsp.request_end_time - lgsp.request_start_time).count(); auto frac = util::utos(t % 1000); auto sec = util::utos(t / 1000); @@ -242,10 +242,10 @@ void upstream_accesslog(const std::vector &lfv, LogSpec *lgsp) { std::tie(p, avail) = copy(sec.c_str(), avail, p); } break; case SHRPX_LOGF_PID: - std::tie(p, avail) = copy(util::utos(lgsp->pid).c_str(), avail, p); + std::tie(p, avail) = copy(util::utos(lgsp.pid).c_str(), avail, p); break; case SHRPX_LOGF_ALPN: - std::tie(p, avail) = copy(lgsp->alpn, avail, p); + std::tie(p, avail) = copy(lgsp.alpn, avail, p); break; case SHRPX_LOGF_NONE: break; diff --git a/src/shrpx_log.h b/src/shrpx_log.h index 65e67960..41645d88 100644 --- a/src/shrpx_log.h +++ b/src/shrpx_log.h @@ -139,7 +139,8 @@ struct LogSpec { pid_t pid; }; -void upstream_accesslog(const std::vector &lf, LogSpec *lgsp); +void upstream_accesslog(const std::vector &lf, + const LogSpec &lgsp); int reopen_log_files(); From 4be4d875f30182e1e1fc291aec93e0e254c775e4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 4 May 2015 23:24:33 +0900 Subject: [PATCH 15/35] nghttpx: Log absolute URI for HTTP/2 or client proxy request --- src/shrpx_client_handler.cc | 54 ++++++++++++++++++++++++++++++++++--- src/shrpx_https_upstream.cc | 9 +++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 063c8388..1d66e2e3 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -690,15 +690,63 @@ void ClientHandler::start_immediate_shutdown() { ev_timer_start(conn_.loop, &reneg_shutdown_timer_); } +namespace { +// Construct absolute request URI from |downstream|, mainly to log +// request URI for proxy request (HTTP/2 proxy or client proxy). This +// is mostly same routine found in +// HttpDownstreamConnection::push_request_headers(), but vastly +// simplified since we only care about absolute URI. +std::string construct_absolute_request_uri(Downstream *downstream) { + const char *authority = nullptr, *host = nullptr; + if (!downstream->get_request_http2_authority().empty()) { + authority = downstream->get_request_http2_authority().c_str(); + } + auto h = downstream->get_request_header(http2::HD_HOST); + if (h) { + host = h->value.c_str(); + } + if (!authority && !host) { + return downstream->get_request_path(); + } + std::string uri; + if (downstream->get_request_http2_scheme().empty()) { + // this comes from HTTP/1 upstream without scheme. Just use http. + uri += "http://"; + } else { + uri += downstream->get_request_http2_scheme(); + uri += "://"; + } + if (authority) { + uri += authority; + } else { + uri += host; + } + + // Server-wide OPTIONS takes following form in proxy request: + // + // OPTIONS http://example.org HTTP/1.1 + // + // Notice that no slash after authority. See + // http://tools.ietf.org/html/rfc7230#section-5.3.4 + if (downstream->get_request_path() != "*") { + uri += downstream->get_request_path(); + } + return uri; +} +} // namespace + void ClientHandler::write_accesslog(Downstream *downstream) { upstream_accesslog( get_config()->accesslog_format, LogSpec{ downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(), - downstream->get_request_path().empty() - ? downstream->get_request_http2_authority().c_str() - : downstream->get_request_path().c_str(), + (downstream->get_request_method() != "CONNECT" && + (get_config()->http2_proxy || get_config()->client_proxy)) + ? construct_absolute_request_uri(downstream).c_str() + : downstream->get_request_path().empty() + ? downstream->get_request_http2_authority().c_str() + : downstream->get_request_path().c_str(), alpn_.c_str(), diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index e620a5da..f634bf4e 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -218,7 +218,12 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri, path += '?'; path.append(uri + fdata.off, fdata.len); } - downstream->set_request_path(path); + downstream->set_request_path(std::move(path)); + if (get_config()->http2_proxy || get_config()->client_proxy) { + std::string scheme; + http2::copy_url_component(scheme, &u, UF_SCHEMA, uri); + downstream->set_request_http2_scheme(std::move(scheme)); + } } } // namespace @@ -275,7 +280,7 @@ int htp_hdrs_completecb(http_parser *htp) { } // checking UF_HOST could be redundant, but just in case ... if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { - if (get_config()->client_proxy) { + if (get_config()->http2_proxy || get_config()->client_proxy) { // Request URI should be absolute-form for client proxy mode return -1; } From 1d65d82cb55a632391531cebfc57df0b36814a4f Mon Sep 17 00:00:00 2001 From: Zhuoyun Wei Date: Mon, 4 May 2015 23:17:27 +0800 Subject: [PATCH 16/35] Improve logrotate configuration file - Avoid changing file permissions - Make sure all log files are reopened - Remove non-sense prerotate script - Send SIGUSR1 to all nghttpx processes --- contrib/nghttpx-logrotate | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/contrib/nghttpx-logrotate b/contrib/nghttpx-logrotate index 56b37be6..f1002bd6 100644 --- a/contrib/nghttpx-logrotate +++ b/contrib/nghttpx-logrotate @@ -1,18 +1,11 @@ /var/log/nghttpx/*.log { - weekly - missingok - rotate 52 - compress - delaycompress - notifempty - create 0640 www-data adm - sharedscripts - prerotate - if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ - run-parts /etc/logrotate.d/httpd-prerotate; \ - fi \ - endscript - postrotate - [ -s /run/nghttpx.pid ] && kill -USR1 `cat /run/nghttpx.pid` - endscript + weekly + rotate 52 + missingok + compress + delaycompress + notifempty + postrotate + killall -USR1 nghttpx 2> /dev/null || true + endscript } From 989d381aab1c355ee639d3cf80e8ab17872ae925 Mon Sep 17 00:00:00 2001 From: Zhuoyun Wei Date: Mon, 4 May 2015 23:56:09 +0800 Subject: [PATCH 17/35] Add systemd service file --- contrib/nghttpx.service | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 contrib/nghttpx.service diff --git a/contrib/nghttpx.service b/contrib/nghttpx.service new file mode 100644 index 00000000..71367be4 --- /dev/null +++ b/contrib/nghttpx.service @@ -0,0 +1,10 @@ +[Unit] +Description=HTTP/2 experimental proxy +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/nghttpx --errorlog-syslog + +[Install] +WantedBy=multi-user.target From 7ecca39025c19d91870bc5485f4f6a1975f480f4 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 5 May 2015 23:43:56 +0900 Subject: [PATCH 18/35] nghttpx: Fix heap-use-after-free bug in http/1 frontend This is a regression introduced in 4be4d875f30182e1e1fc291aec93e0e254c775e4 --- src/shrpx_https_upstream.cc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index f634bf4e..674f17ba 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -271,9 +271,11 @@ int htp_hdrs_completecb(http_parser *htp) { if (downstream->get_request_method() != "CONNECT") { http_parser_url u{}; - auto uri = downstream->get_request_path().c_str(); - rv = http_parser_parse_url(uri, downstream->get_request_path().size(), 0, - &u); + // make a copy of request path, since we may set request path + // while we are refering to original request path. + auto uri = downstream->get_request_path(); + rv = http_parser_parse_url(uri.c_str(), + downstream->get_request_path().size(), 0, &u); if (rv != 0) { // Expect to respond with 400 bad request return -1; @@ -285,8 +287,7 @@ int htp_hdrs_completecb(http_parser *htp) { return -1; } } else { - rewrite_request_host_path_from_uri(downstream, uri, u); - // uri could be invalidated here + rewrite_request_host_path_from_uri(downstream, uri.c_str(), u); } } From 2d5d9d5d0439b51a8f1eca9783eb86bf4796d38a Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 10:42:43 +0900 Subject: [PATCH 19/35] nghttpd: Add -m, --max-concurrent-streams option --- src/HttpServer.cc | 16 +++++++++------- src/HttpServer.h | 1 + src/nghttpd.cc | 18 +++++++++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 19cdbed7..edbf1a81 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -89,9 +89,9 @@ template void append_nv(Stream *stream, const Array &nva) { Config::Config() : stream_read_timeout(60.), stream_write_timeout(60.), session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1), - header_table_size(-1), port(0), verbose(false), daemon(false), - verify_client(false), no_tls(false), error_gzip(false), - early_response(false), hexdump(false) { + max_concurrent_streams(100), header_table_size(-1), port(0), + verbose(false), daemon(false), verify_client(false), no_tls(false), + error_gzip(false), early_response(false), hexdump(false) { nghttp2_option_new(&session_option); nghttp2_option_set_recv_client_preface(session_option, 1); } @@ -660,8 +660,10 @@ int Http2Handler::on_write() { return write_(*this); } int Http2Handler::connection_made() { int r; + auto config = sessions_->get_config(); + r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this, - sessions_->get_config()->session_option); + config->session_option); if (r != 0) { return r; } @@ -669,11 +671,11 @@ int Http2Handler::connection_made() { size_t niv = 1; entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; - entry[0].value = 100; + entry[0].value = config->max_concurrent_streams; - if (sessions_->get_config()->header_table_size >= 0) { + if (config->header_table_size >= 0) { entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; - entry[niv].value = sessions_->get_config()->header_table_size; + entry[niv].value = config->header_table_size; ++niv; } r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv); diff --git a/src/HttpServer.h b/src/HttpServer.h index d6272f4a..98ca9c43 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -63,6 +63,7 @@ struct Config { void *data_ptr; size_t padding; size_t num_worker; + size_t max_concurrent_streams; ssize_t header_table_size; uint16_t port; bool verbose; diff --git a/src/nghttpd.cc b/src/nghttpd.cc index d7691598..5126cea1 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -88,6 +88,7 @@ void print_usage(std::ostream &out) { namespace { void print_help(std::ostream &out) { + Config config; print_usage(out); out << R"( Specify listening port number. @@ -128,6 +129,10 @@ Options: -b, --padding= Add at most bytes to a frame payload as padding. Specify 0 to disable padding. + -m, --max-concurrent-streams= + Set the maximum number of the concurrent streams in one + HTTP/2 session. + Default: )" << config.max_concurrent_streams << R"( -n, --workers= Set the number of worker threads. Default: 1 @@ -173,6 +178,7 @@ int main(int argc, char **argv) { {"header-table-size", required_argument, nullptr, 'c'}, {"push", required_argument, nullptr, 'p'}, {"padding", required_argument, nullptr, 'b'}, + {"max-concurrent-streams", required_argument, nullptr, 'm'}, {"workers", required_argument, nullptr, 'n'}, {"error-gzip", no_argument, nullptr, 'e'}, {"no-tls", no_argument, &flag, 1}, @@ -184,7 +190,7 @@ int main(int argc, char **argv) { {"hexdump", no_argument, &flag, 7}, {nullptr, 0, nullptr, 0}}; int option_index = 0; - int c = getopt_long(argc, argv, "DVb:c:d:ehn:p:va:", long_options, + int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options, &option_index); char *end; if (c == -1) { @@ -209,6 +215,16 @@ int main(int argc, char **argv) { case 'e': config.error_gzip = true; break; + case 'm': { + // max-concurrent-streams option + auto n = util::parse_uint(optarg); + if (n == -1) { + std::cerr << "-m: invalid argument: " << optarg << std::endl; + exit(EXIT_FAILURE); + } + config.max_concurrent_streams = n; + break; + } case 'n': #ifdef NOTHREADS std::cerr << "-n: WARNING: Threading disabled at build time, " From 232d359cbb7a6a9381c0583798e1e0355bd1fb34 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 11:02:51 +0900 Subject: [PATCH 20/35] Add nghttpx.service to distribution --- contrib/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 6727a846..8f12126f 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -21,7 +21,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -EXTRA_DIST = nghttpx-init.in nghttpx-logrotate +EXTRA_DIST = nghttpx-init.in nghttpx.service nghttpx-logrotate edit = sed -e 's|@bindir[@]|$(bindir)|g' From eb96aa261f40ed5dd4101f08ca7b0a48aabd0361 Mon Sep 17 00:00:00 2001 From: Zhuoyun Wei Date: Wed, 6 May 2015 10:38:24 +0800 Subject: [PATCH 21/35] Add Upstart configuration file --- contrib/nghttpx-upstart.conf | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 contrib/nghttpx-upstart.conf diff --git a/contrib/nghttpx-upstart.conf b/contrib/nghttpx-upstart.conf new file mode 100644 index 00000000..0d2a33a7 --- /dev/null +++ b/contrib/nghttpx-upstart.conf @@ -0,0 +1,8 @@ +# vim: ft=upstart: + +description "HTTP/2 reverse proxy" + +start on runlevel [2] +stop on runlevel [016] + +exec /usr/bin/nghttpx From 7bb154f768aaf546b9e858c671f5a0f42b3dba6d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 12:01:29 +0900 Subject: [PATCH 22/35] Substitute bindir for nghttpx.service Use static pattern rules to use same recipe for nghttpx-init and nghttpx.service. Also rewrite how to produce nghttpx --- configure.ac | 2 ++ contrib/.gitignore | 4 ++++ contrib/Makefile.am | 15 +++++++-------- .../{nghttpx-init.in => nghttpx-init.template.in} | 4 ++-- ...ghttpx.service => nghttpx.service.template.in} | 2 +- 5 files changed, 16 insertions(+), 11 deletions(-) rename contrib/{nghttpx-init.in => nghttpx-init.template.in} (97%) rename contrib/{nghttpx.service => nghttpx.service.template.in} (73%) diff --git a/configure.ac b/configure.ac index 0d2220b3..232c9c64 100644 --- a/configure.ac +++ b/configure.ac @@ -650,6 +650,8 @@ AC_CONFIG_FILES([ doc/asio_http2_client.h.rst doc/contribute.rst contrib/Makefile + contrib/nghttpx-init.template + contrib/nghttpx.service.template ]) AC_OUTPUT diff --git a/contrib/.gitignore b/contrib/.gitignore index a08baf90..a9fa9f81 100644 --- a/contrib/.gitignore +++ b/contrib/.gitignore @@ -1 +1,5 @@ nghttpx-init +nghttpx-init.template +nghttpx.service +nghttpx.service.template + diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 8f12126f..748f1cbb 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -21,19 +21,18 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -EXTRA_DIST = nghttpx-init.in nghttpx.service nghttpx-logrotate +EXTRA_DIST = nghttpx-init.template.in nghttpx.service.template.in \ + nghttpx-logrotate -edit = sed -e 's|@bindir[@]|$(bindir)|g' +edit = sed -e 's|$$(bindir)|$(bindir)|g' -nghttpx-init: Makefile +nghttpx-init nghttpx.service: %: $(srcdir)/%.template rm -f $@ $@.tmp - $(edit) $(srcdir)/$@.in > $@.tmp + $(edit) $< > $@.tmp chmod +x $@.tmp mv $@.tmp $@ -nghttpx-init: $(srcdir)/nghttpx-init.in - -all-local: nghttpx-init +all-local: nghttpx-init nghttpx.service clean-local: - -rm -f nghttpx-init nghttpx-init.tmp + -rm -f nghttpx-init nghttpx-init.tmp nghttpx.service nghttpx.service.tmp diff --git a/contrib/nghttpx-init.in b/contrib/nghttpx-init.template.in similarity index 97% rename from contrib/nghttpx-init.in rename to contrib/nghttpx-init.template.in index a1620a97..8367d810 100644 --- a/contrib/nghttpx-init.in +++ b/contrib/nghttpx-init.template.in @@ -17,8 +17,8 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin DESC="HTTP/2 reverse proxy" NAME=nghttpx -# Depending on the configuration, binary may be located under @sbindir@ -DAEMON=@bindir@/$NAME +# Depending on the configuration, binary may be located under $(bindir) +DAEMON=$(bindir)/$NAME PIDFILE=/var/run/$NAME.pid DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE" SCRIPTNAME=/etc/init.d/$NAME diff --git a/contrib/nghttpx.service b/contrib/nghttpx.service.template.in similarity index 73% rename from contrib/nghttpx.service rename to contrib/nghttpx.service.template.in index 71367be4..f6e0eed4 100644 --- a/contrib/nghttpx.service +++ b/contrib/nghttpx.service.template.in @@ -4,7 +4,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/nghttpx --errorlog-syslog +ExecStart=$(bindir)/nghttpx --errorlog-syslog [Install] WantedBy=multi-user.target From 2a37a28d72ecaf9165eb75a66bce886984641cc6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 15:37:56 +0900 Subject: [PATCH 23/35] Revert "Substitute bindir for nghttpx.service" This reverts commit 7bb154f768aaf546b9e858c671f5a0f42b3dba6d. --- configure.ac | 2 -- contrib/.gitignore | 4 ---- contrib/Makefile.am | 15 ++++++++------- .../{nghttpx-init.template.in => nghttpx-init.in} | 4 ++-- ...ghttpx.service.template.in => nghttpx.service} | 2 +- 5 files changed, 11 insertions(+), 16 deletions(-) rename contrib/{nghttpx-init.template.in => nghttpx-init.in} (97%) rename contrib/{nghttpx.service.template.in => nghttpx.service} (73%) diff --git a/configure.ac b/configure.ac index 232c9c64..0d2220b3 100644 --- a/configure.ac +++ b/configure.ac @@ -650,8 +650,6 @@ AC_CONFIG_FILES([ doc/asio_http2_client.h.rst doc/contribute.rst contrib/Makefile - contrib/nghttpx-init.template - contrib/nghttpx.service.template ]) AC_OUTPUT diff --git a/contrib/.gitignore b/contrib/.gitignore index a9fa9f81..a08baf90 100644 --- a/contrib/.gitignore +++ b/contrib/.gitignore @@ -1,5 +1 @@ nghttpx-init -nghttpx-init.template -nghttpx.service -nghttpx.service.template - diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 748f1cbb..8f12126f 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -21,18 +21,19 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -EXTRA_DIST = nghttpx-init.template.in nghttpx.service.template.in \ - nghttpx-logrotate +EXTRA_DIST = nghttpx-init.in nghttpx.service nghttpx-logrotate -edit = sed -e 's|$$(bindir)|$(bindir)|g' +edit = sed -e 's|@bindir[@]|$(bindir)|g' -nghttpx-init nghttpx.service: %: $(srcdir)/%.template +nghttpx-init: Makefile rm -f $@ $@.tmp - $(edit) $< > $@.tmp + $(edit) $(srcdir)/$@.in > $@.tmp chmod +x $@.tmp mv $@.tmp $@ -all-local: nghttpx-init nghttpx.service +nghttpx-init: $(srcdir)/nghttpx-init.in + +all-local: nghttpx-init clean-local: - -rm -f nghttpx-init nghttpx-init.tmp nghttpx.service nghttpx.service.tmp + -rm -f nghttpx-init nghttpx-init.tmp diff --git a/contrib/nghttpx-init.template.in b/contrib/nghttpx-init.in similarity index 97% rename from contrib/nghttpx-init.template.in rename to contrib/nghttpx-init.in index 8367d810..a1620a97 100644 --- a/contrib/nghttpx-init.template.in +++ b/contrib/nghttpx-init.in @@ -17,8 +17,8 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin DESC="HTTP/2 reverse proxy" NAME=nghttpx -# Depending on the configuration, binary may be located under $(bindir) -DAEMON=$(bindir)/$NAME +# Depending on the configuration, binary may be located under @sbindir@ +DAEMON=@bindir@/$NAME PIDFILE=/var/run/$NAME.pid DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE" SCRIPTNAME=/etc/init.d/$NAME diff --git a/contrib/nghttpx.service.template.in b/contrib/nghttpx.service similarity index 73% rename from contrib/nghttpx.service.template.in rename to contrib/nghttpx.service index f6e0eed4..71367be4 100644 --- a/contrib/nghttpx.service.template.in +++ b/contrib/nghttpx.service @@ -4,7 +4,7 @@ After=network.target [Service] Type=simple -ExecStart=$(bindir)/nghttpx --errorlog-syslog +ExecStart=/usr/bin/nghttpx --errorlog-syslog [Install] WantedBy=multi-user.target From 98034286ac83388dcd290fd3caba499188ac1331 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 15:53:12 +0900 Subject: [PATCH 24/35] Substitute bindir in nghttpx.service --- contrib/.gitignore | 1 + contrib/Makefile.am | 17 +++++++++++------ contrib/{nghttpx.service => nghttpx.service.in} | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) rename contrib/{nghttpx.service => nghttpx.service.in} (73%) diff --git a/contrib/.gitignore b/contrib/.gitignore index a08baf90..c2d09c33 100644 --- a/contrib/.gitignore +++ b/contrib/.gitignore @@ -1 +1,2 @@ nghttpx-init +nghttpx.service diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 8f12126f..596dc431 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -21,19 +21,24 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -EXTRA_DIST = nghttpx-init.in nghttpx.service nghttpx-logrotate +configfiles = nghttpx-init nghttpx.service + +EXTRA_DIST = $(configfiles:%=%.in) nghttpx-logrotate edit = sed -e 's|@bindir[@]|$(bindir)|g' -nghttpx-init: Makefile +nghttpx-init: %: $(srcdir)/%.in rm -f $@ $@.tmp - $(edit) $(srcdir)/$@.in > $@.tmp + $(edit) $< > $@.tmp chmod +x $@.tmp mv $@.tmp $@ -nghttpx-init: $(srcdir)/nghttpx-init.in +nghttpx.service: %: $(srcdir)/%.in + $(edit) $< > $@ -all-local: nghttpx-init +$(configfiles): Makefile + +all-local: $(configfiles) clean-local: - -rm -f nghttpx-init nghttpx-init.tmp + -rm -f nghttpx-init.tmp $(configfiles) diff --git a/contrib/nghttpx.service b/contrib/nghttpx.service.in similarity index 73% rename from contrib/nghttpx.service rename to contrib/nghttpx.service.in index 71367be4..9c7f851e 100644 --- a/contrib/nghttpx.service +++ b/contrib/nghttpx.service.in @@ -4,7 +4,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/bin/nghttpx --errorlog-syslog +ExecStart=@bindir@/nghttpx --errorlog-syslog [Install] WantedBy=multi-user.target From 14adcb2d817eb50a82f2813fc61f54d24fe68777 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 6 May 2015 15:56:18 +0900 Subject: [PATCH 25/35] Substitute bindir in nghttpx-upstart.conf --- contrib/.gitignore | 1 + contrib/Makefile.am | 4 ++-- contrib/{nghttpx-upstart.conf => nghttpx-upstart.conf.in} | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename contrib/{nghttpx-upstart.conf => nghttpx-upstart.conf.in} (82%) diff --git a/contrib/.gitignore b/contrib/.gitignore index c2d09c33..85acde38 100644 --- a/contrib/.gitignore +++ b/contrib/.gitignore @@ -1,2 +1,3 @@ nghttpx-init nghttpx.service +nghttpx-upstart.conf diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 596dc431..07a3bf8b 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -21,7 +21,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -configfiles = nghttpx-init nghttpx.service +configfiles = nghttpx-init nghttpx.service nghttpx-upstart.conf EXTRA_DIST = $(configfiles:%=%.in) nghttpx-logrotate @@ -33,7 +33,7 @@ nghttpx-init: %: $(srcdir)/%.in chmod +x $@.tmp mv $@.tmp $@ -nghttpx.service: %: $(srcdir)/%.in +nghttpx.service nghttpx-upstart.conf: %: $(srcdir)/%.in $(edit) $< > $@ $(configfiles): Makefile diff --git a/contrib/nghttpx-upstart.conf b/contrib/nghttpx-upstart.conf.in similarity index 82% rename from contrib/nghttpx-upstart.conf rename to contrib/nghttpx-upstart.conf.in index 0d2a33a7..0b799160 100644 --- a/contrib/nghttpx-upstart.conf +++ b/contrib/nghttpx-upstart.conf.in @@ -5,4 +5,4 @@ description "HTTP/2 reverse proxy" start on runlevel [2] stop on runlevel [016] -exec /usr/bin/nghttpx +exec @bindir@/nghttpx From 2620992003c08e6efb25ae03472e6067f8681379 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 May 2015 18:58:32 +0900 Subject: [PATCH 26/35] Document about time for connect and time for 1st byte in h2load man page --- doc/h2load.h2r | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/doc/h2load.h2r b/doc/h2load.h2r index 10cc5d56..df9fefed 100644 --- a/doc/h2load.h2r +++ b/doc/h2load.h2r @@ -44,11 +44,40 @@ time for request mean The mean time taken for request and response. sd - The standard deviation of the time for request and response. + The standard deviation of the time taken for request and response. +/- sd The fraction of the number of requests within standard deviation range (mean +/- sd) against total number of successful requests. +time for connect + min + The minimum time taken to connect to a server. + max + The maximum time taken to connect to a server. + mean + The mean time taken to connect to a server. + sd + The standard deviation of the time taken to connect to a server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +time for 1st byte (of (decrypted in case of TLS) application data) + min + The minimum time taken to get 1st byte from a server. + max + The maximum time taken to get 1st byte from a server. + mean + The mean time taken to get 1st byte from a server. + sd + The standard deviation of the time taken to get 1st byte from a + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + FLOW CONTROL ------------ From fc17c0a618ec581e16fe6b964b856778c350d071 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 May 2015 19:37:43 +0900 Subject: [PATCH 27/35] Fix global-buffer-overflow --- lib/nghttp2_hd.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c index 31b563dd..2c08fee1 100644 --- a/lib/nghttp2_hd.c +++ b/lib/nghttp2_hd.c @@ -1157,15 +1157,11 @@ static nghttp2_hd_entry *add_hd_table_incremental(nghttp2_hd_context *context, } static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) { - return a->namelen == b->namelen && - a->name[a->namelen - 1] == b->name[a->namelen - 1] && - memeq(a->name, b->name, a->namelen); + return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen); } static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) { - return a->valuelen == b->valuelen && - a->value[a->valuelen - 1] == b->value[a->valuelen - 1] && - memeq(a->value, b->value, a->valuelen); + return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen); } typedef struct { From eec65826cf9eb3d79aa0d689feb174af98154828 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 May 2015 20:55:10 +0900 Subject: [PATCH 28/35] Update man pages --- doc/h2load.1 | 47 +++++++++++++++++++++++++++++++++++++++++++++-- doc/h2load.1.rst | 31 ++++++++++++++++++++++++++++++- doc/nghttp.1 | 14 ++++++++++---- doc/nghttp.1.rst | 10 ++++++++-- doc/nghttpd.1 | 10 +++++++++- doc/nghttpd.1.rst | 7 +++++++ doc/nghttpx.1 | 19 ++++++++++++++++++- doc/nghttpx.1.rst | 15 +++++++++++++++ 8 files changed, 142 insertions(+), 11 deletions(-) diff --git a/doc/h2load.1 b/doc/h2load.1 index e2d9b3de..a5e6b960 100644 --- a/doc/h2load.1 +++ b/doc/h2load.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "H2LOAD" "1" "April 27, 2015" "0.7.13" "nghttp2" +.TH "H2LOAD" "1" "May 07, 2015" "0.7.14-DEV" "nghttp2" .SH NAME h2load \- HTTP/2 benchmarking tool . @@ -205,12 +205,55 @@ The maximum time taken for request and response. The mean time taken for request and response. .TP .B sd -The standard deviation of the time for request and response. +The standard deviation of the time taken for request and response. .TP .B +/\- sd The fraction of the number of requests within standard deviation range (mean +/\- sd) against total number of successful requests. .UNINDENT +.TP +.B time for connect +.INDENT 7.0 +.TP +.B min +The minimum time taken to connect to a server. +.TP +.B max +The maximum time taken to connect to a server. +.TP +.B mean +The mean time taken to connect to a server. +.TP +.B sd +The standard deviation of the time taken to connect to a server. +.TP +.B +/\- sd +The fraction of the number of connections within standard +deviation range (mean +/\- sd) against total number of successful +connections. +.UNINDENT +.TP +.B time for 1st byte (of (decrypted in case of TLS) application data) +.INDENT 7.0 +.TP +.B min +The minimum time taken to get 1st byte from a server. +.TP +.B max +The maximum time taken to get 1st byte from a server. +.TP +.B mean +The mean time taken to get 1st byte from a server. +.TP +.B sd +The standard deviation of the time taken to get 1st byte from a +server. +.TP +.B +/\- sd +The fraction of the number of connections within standard +deviation range (mean +/\- sd) against total number of successful +connections. +.UNINDENT .UNINDENT .SH FLOW CONTROL .sp diff --git a/doc/h2load.1.rst b/doc/h2load.1.rst index fc70f438..4dae19e7 100644 --- a/doc/h2load.1.rst +++ b/doc/h2load.1.rst @@ -153,11 +153,40 @@ time for request mean The mean time taken for request and response. sd - The standard deviation of the time for request and response. + The standard deviation of the time taken for request and response. +/- sd The fraction of the number of requests within standard deviation range (mean +/- sd) against total number of successful requests. +time for connect + min + The minimum time taken to connect to a server. + max + The maximum time taken to connect to a server. + mean + The mean time taken to connect to a server. + sd + The standard deviation of the time taken to connect to a server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + +time for 1st byte (of (decrypted in case of TLS) application data) + min + The minimum time taken to get 1st byte from a server. + max + The maximum time taken to get 1st byte from a server. + mean + The mean time taken to get 1st byte from a server. + sd + The standard deviation of the time taken to get 1st byte from a + server. + +/- sd + The fraction of the number of connections within standard + deviation range (mean +/- sd) against total number of successful + connections. + FLOW CONTROL ------------ diff --git a/doc/nghttp.1 b/doc/nghttp.1 index ae965c76..42e11d68 100644 --- a/doc/nghttp.1 +++ b/doc/nghttp.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTP" "1" "April 27, 2015" "0.7.13" "nghttp2" +.TH "NGHTTP" "1" "May 07, 2015" "0.7.14-DEV" "nghttp2" .SH NAME nghttp \- HTTP/2 experimental client . @@ -64,8 +64,9 @@ yet. .UNINDENT .INDENT 0.0 .TP -.B \-t, \-\-timeout= -Timeout each request after seconds. +.B \-t, \-\-timeout= +Timeout each request after . Set 0 to disable +timeout. .UNINDENT .INDENT 0.0 .TP @@ -215,6 +216,11 @@ Display this help and exit. .sp The argument is an integer and an optional unit (e.g., 10K is 10 * 1024). Units are K, M and G (powers of 1024). +.sp +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. .SH DEPENDENCY BASED PRIORITY .sp nghttp sends priority hints to server by default unless @@ -269,7 +275,7 @@ to its resource type. .sp For CSS, and Javascript files inside "head" element, they depend on stream 3 with the weight 2. The Javascript files outside "head" -element depend on stream 5 with the weight 2. The images depend on +element depend on stream 5 with the weight 2. The mages depend on stream 11 with the weight 12. The other resources (e.g., icon) depend on stream 11 with the weight 2. .SH SEE ALSO diff --git a/doc/nghttp.1.rst b/doc/nghttp.1.rst index ff9fc95f..88cb8afd 100644 --- a/doc/nghttp.1.rst +++ b/doc/nghttp.1.rst @@ -38,9 +38,10 @@ OPTIONS 'index.html' is used as a filename. Not implemented yet. -.. option:: -t, --timeout= +.. option:: -t, --timeout= - Timeout each request after seconds. + Timeout each request after . Set 0 to disable + timeout. .. option:: -w, --window-bits= @@ -168,6 +169,11 @@ OPTIONS The argument is an integer and an optional unit (e.g., 10K is 10 * 1024). Units are K, M and G (powers of 1024). +The argument is an integer and an optional unit (e.g., 1s +is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms +(hours, minutes, seconds and milliseconds, respectively). If a unit +is omitted, a second is used as unit. + DEPENDENCY BASED PRIORITY ------------------------- diff --git a/doc/nghttpd.1 b/doc/nghttpd.1 index 086eb9df..ac453e64 100644 --- a/doc/nghttpd.1 +++ b/doc/nghttpd.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTPD" "1" "April 27, 2015" "0.7.13" "nghttp2" +.TH "NGHTTPD" "1" "May 07, 2015" "0.7.14-DEV" "nghttp2" .SH NAME nghttpd \- HTTP/2 experimental server . @@ -119,6 +119,14 @@ Specify 0 to disable padding. .UNINDENT .INDENT 0.0 .TP +.B \-m, \-\-max\-concurrent\-streams= +Set the maximum number of the concurrent streams in one +HTTP/2 session. +.sp +Default: \fB100\fP +.UNINDENT +.INDENT 0.0 +.TP .B \-n, \-\-workers= Set the number of worker threads. .sp diff --git a/doc/nghttpd.1.rst b/doc/nghttpd.1.rst index 77cc01d0..abb9c45a 100644 --- a/doc/nghttpd.1.rst +++ b/doc/nghttpd.1.rst @@ -85,6 +85,13 @@ OPTIONS Add at most bytes to a frame payload as padding. Specify 0 to disable padding. +.. option:: -m, --max-concurrent-streams= + + Set the maximum number of the concurrent streams in one + HTTP/2 session. + + Default: ``100`` + .. option:: -n, --workers= Set the number of worker threads. diff --git a/doc/nghttpx.1 b/doc/nghttpx.1 index 8aa24018..e2ad5975 100644 --- a/doc/nghttpx.1 +++ b/doc/nghttpx.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTPX" "1" "April 27, 2015" "0.7.13" "nghttp2" +.TH "NGHTTPX" "1" "May 07, 2015" "0.7.14-DEV" "nghttp2" .SH NAME nghttpx \- HTTP/2 experimental proxy . @@ -704,6 +704,23 @@ won\(aqt replace anything already set. This option can be used several times to specify multiple header fields. Example: \fI\%\-\-add\-response\-header\fP="foo: bar" .UNINDENT +.INDENT 0.0 +.TP +.B \-\-header\-field\-buffer= +Set maximum buffer size for incoming HTTP header field +list. This is the sum of header name and value in +bytes. +.sp +Default: \fB64K\fP +.UNINDENT +.INDENT 0.0 +.TP +.B \-\-max\-header\-fields= +Set maximum number of incoming HTTP header fields, which +appear in one request or response header field list. +.sp +Default: \fB100\fP +.UNINDENT .SS Debug .INDENT 0.0 .TP diff --git a/doc/nghttpx.1.rst b/doc/nghttpx.1.rst index 99058a41..c4571d75 100644 --- a/doc/nghttpx.1.rst +++ b/doc/nghttpx.1.rst @@ -619,6 +619,21 @@ HTTP used several times to specify multiple header fields. Example: :option:`--add-response-header`\="foo: bar" +.. option:: --header-field-buffer= + + Set maximum buffer size for incoming HTTP header field + list. This is the sum of header name and value in + bytes. + + Default: ``64K`` + +.. option:: --max-header-fields= + + Set maximum number of incoming HTTP header fields, which + appear in one request or response header field list. + + Default: ``100`` + Debug ~~~~~ From 1a1902350b8cef10a960c1d5cc3271b0ff0049c5 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 7 May 2015 21:09:57 +0900 Subject: [PATCH 29/35] Update sphinx_rtd_theme --- doc/_themes/sphinx_rtd_theme/__init__.py | 2 +- doc/_themes/sphinx_rtd_theme/breadcrumbs.html | 18 ++-- doc/_themes/sphinx_rtd_theme/footer.html | 4 +- doc/_themes/sphinx_rtd_theme/layout.html | 2 +- .../sphinx_rtd_theme/static/css/theme.css | 4 +- .../sphinx_rtd_theme/static/css/theme.css.map | 2 +- .../sphinx_rtd_theme/static/js/theme.js | 101 ++++++++++++++---- 7 files changed, 99 insertions(+), 34 deletions(-) diff --git a/doc/_themes/sphinx_rtd_theme/__init__.py b/doc/_themes/sphinx_rtd_theme/__init__.py index fcce3bfc..95ddc52a 100644 --- a/doc/_themes/sphinx_rtd_theme/__init__.py +++ b/doc/_themes/sphinx_rtd_theme/__init__.py @@ -5,7 +5,7 @@ From https://github.com/ryan-roemer/sphinx-bootstrap-theme. """ import os -VERSION = (0, 1, 7) +VERSION = (0, 1, 8) __version__ = ".".join(str(v) for v in VERSION) __version_full__ = __version__ diff --git a/doc/_themes/sphinx_rtd_theme/breadcrumbs.html b/doc/_themes/sphinx_rtd_theme/breadcrumbs.html index af4f55a4..0028421e 100644 --- a/doc/_themes/sphinx_rtd_theme/breadcrumbs.html +++ b/doc/_themes/sphinx_rtd_theme/breadcrumbs.html @@ -6,14 +6,16 @@ {% endfor %}
  • {{ title }}
  • - {% if display_github %} - Edit on GitHub - {% elif display_bitbucket %} - Edit on Bitbucket - {% elif show_source and source_url_prefix %} - View page source - {% elif show_source and has_source and sourcename %} - View page source + {% if pagename != "search" %} + {% if display_github %} + Edit on GitHub + {% elif display_bitbucket %} + Edit on Bitbucket + {% elif show_source and source_url_prefix %} + View page source + {% elif show_source and has_source and sourcename %} + View page source + {% endif %} {% endif %}
  • diff --git a/doc/_themes/sphinx_rtd_theme/footer.html b/doc/_themes/sphinx_rtd_theme/footer.html index 0123a5de..6347a440 100644 --- a/doc/_themes/sphinx_rtd_theme/footer.html +++ b/doc/_themes/sphinx_rtd_theme/footer.html @@ -2,10 +2,10 @@ {% if next or prev %} {% endif %} diff --git a/doc/_themes/sphinx_rtd_theme/layout.html b/doc/_themes/sphinx_rtd_theme/layout.html index 0ddf6ee8..9481d8b4 100644 --- a/doc/_themes/sphinx_rtd_theme/layout.html +++ b/doc/_themes/sphinx_rtd_theme/layout.html @@ -107,7 +107,7 @@