diff --git a/configure.ac b/configure.ac index 5c885a87..31317479 100644 --- a/configure.ac +++ b/configure.ac @@ -25,14 +25,14 @@ dnl Do not change user variables! dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html AC_PREREQ(2.61) -AC_INIT([nghttp2], [0.7.10-DEV], [t-tujikawa@users.sourceforge.net]) +AC_INIT([nghttp2], [0.7.11-DEV], [t-tujikawa@users.sourceforge.net]) LT_PREREQ([2.2.6]) LT_INIT() dnl See versioning rule: dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html -AC_SUBST(LT_CURRENT, 12) -AC_SUBST(LT_REVISION, 2) -AC_SUBST(LT_AGE, 7) +AC_SUBST(LT_CURRENT, 13) +AC_SUBST(LT_REVISION, 0) +AC_SUBST(LT_AGE, 8) major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"` minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"` diff --git a/doc/Makefile.am b/doc/Makefile.am index 265e1683..1849badb 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -76,6 +76,8 @@ APIDOCS= \ nghttp2_session_client_new2.rst \ nghttp2_session_client_new3.rst \ nghttp2_session_consume.rst \ + nghttp2_session_consume_connection.rst \ + nghttp2_session_consume_stream.rst \ nghttp2_session_del.rst \ nghttp2_session_get_effective_local_window_size.rst \ nghttp2_session_get_effective_recv_data_length.rst \ @@ -202,11 +204,7 @@ help: apiref.rst: $(top_builddir)/lib/includes/nghttp2/nghttp2ver.h \ $(top_builddir)/lib/includes/nghttp2/nghttp2.h $(PYTHON) $(top_srcdir)/doc/mkapiref.py \ - $@ \ - $(top_builddir)/doc/macros.rst \ - $(top_builddir)/doc/enums.rst \ - $(top_builddir)/doc/types.rst \ - $(top_builddir)/doc/ $^ + $@ macros.rst enums.rst types.rst . $^ clean-local: -rm $(APIDOCS) diff --git a/doc/bash_completion/h2load b/doc/bash_completion/h2load index c8f42d46..2a4bf98d 100644 --- a/doc/bash_completion/h2load +++ b/doc/bash_completion/h2load @@ -8,7 +8,7 @@ _h2load() _get_comp_words_by_ref cur prev case $cur in -*) - COMPREPLY=( $( compgen -W '--threads --connection-window-bits --input-file --help --requests --verbose --version --window-bits --clients --no-tls-proto --header --max-concurrent-streams ' -- "$cur" ) ) + COMPREPLY=( $( compgen -W '--threads --connection-window-bits --input-file --help --requests --data --verbose --version --window-bits --clients --no-tls-proto --header --max-concurrent-streams ' -- "$cur" ) ) ;; *) _filedir diff --git a/doc/bash_completion/nghttp b/doc/bash_completion/nghttp index 009c99c2..53a0cc8c 100644 --- a/doc/bash_completion/nghttp +++ b/doc/bash_completion/nghttp @@ -8,7 +8,7 @@ _nghttp() _get_comp_words_by_ref cur prev case $cur in -*) - COMPREPLY=( $( compgen -W '--verbose --no-dep --get-assets --har --header-table-size --multiply --padding --dep-idle --continuation --connection-window-bits --peer-max-concurrent-streams --timeout --data --no-content-length --version --color --cert --upgrade --remote-name --weight --help --key --null-out --window-bits --stat --header ' -- "$cur" ) ) + COMPREPLY=( $( compgen -W '--verbose --no-dep --get-assets --har --header-table-size --multiply --padding --hexdump --dep-idle --continuation --connection-window-bits --peer-max-concurrent-streams --timeout --data --no-content-length --version --color --cert --upgrade --remote-name --trailer --weight --help --key --null-out --window-bits --stat --header ' -- "$cur" ) ) ;; *) _filedir diff --git a/doc/bash_completion/nghttpd b/doc/bash_completion/nghttpd index e42c7ecf..407bd064 100644 --- a/doc/bash_completion/nghttpd +++ b/doc/bash_completion/nghttpd @@ -8,7 +8,7 @@ _nghttpd() _get_comp_words_by_ref cur prev case $cur in -*) - COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --htdocs --padding --verbose --version --help --daemon --verify-client --workers --no-tls --color --early-response --dh-param-file ' -- "$cur" ) ) + COMPREPLY=( $( compgen -W '--error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --daemon --verify-client --workers --no-tls --color --early-response --dh-param-file ' -- "$cur" ) ) ;; *) _filedir diff --git a/doc/bash_completion/nghttpx b/doc/bash_completion/nghttpx index b2f50089..916220da 100644 --- a/doc/bash_completion/nghttpx +++ b/doc/bash_completion/nghttpx @@ -8,7 +8,7 @@ _nghttpx() _get_comp_words_by_ref cur prev case $cur in -*) - COMPREPLY=( $( compgen -W '--frontend-http2-connection-window-bits --worker-read-rate --frontend-no-tls --frontend-http2-dump-request-header --daemon --write-rate --altsvc --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --ciphers --verify-client-cacert --backend-keep-alive-timeout --strip-incoming-x-forwarded-for --errorlog-file --private-key-passwd-file --version --backlog --backend-http-proxy-uri --add-response-header --backend-write-timeout --backend-request-buffer --add-x-forwarded-for --write-burst --backend-http2-connection-window-bits --insecure --rlimit-nofile --backend-http2-window-bits --tls-proto-list --no-location-rewrite --padding --accesslog-syslog --conf --http2-max-concurrent-streams --client-proxy --worker-frontend-connections --cacert --frontend-read-timeout --worker-write-burst --npn-list --syslog-facility --backend-http1-connections-per-host --no-server-push --client --http2-bridge --no-via --user --stream-write-timeout --backend-response-buffer --http2-no-cookie-crumbling --backend-read-timeout --stream-read-timeout --workers --worker-read-burst --tls-ctx-per-worker --dh-param-file --errorlog-syslog --frontend --accesslog-file --http2-proxy --read-burst --accesslog-format --frontend-http2-window-bits --backend-no-tls --client-private-key-file --pid-file --client-cert-file --no-host-rewrite --log-level --worker-write-rate --help --backend-tls-sni-field --subcert --frontend-frame-debug --frontend-write-timeout --verify-client --read-rate --frontend-http2-read-timeout --backend-ipv4 --listener-disable-timeout --backend-ipv6 --backend ' -- "$cur" ) ) + COMPREPLY=( $( compgen -W '--frontend-http2-connection-window-bits --worker-read-rate --frontend-no-tls --frontend-http2-dump-request-header --daemon --write-rate --altsvc --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --ciphers --verify-client-cacert --backend-keep-alive-timeout --strip-incoming-x-forwarded-for --errorlog-file --private-key-passwd-file --version --backlog --backend-http-proxy-uri --add-response-header --backend-write-timeout --backend-request-buffer --add-x-forwarded-for --write-burst --backend-http2-connection-window-bits --insecure --rlimit-nofile --backend-http2-window-bits --tls-proto-list --no-location-rewrite --padding --conf --accesslog-syslog --backend-http2-connections-per-worker --http2-max-concurrent-streams --client-proxy --worker-frontend-connections --ocsp-update-interval --cacert --frontend-read-timeout --worker-write-burst --npn-list --syslog-facility --backend-http1-connections-per-host --no-server-push --client --http2-bridge --fetch-ocsp-response-file --no-via --user --stream-write-timeout --no-ocsp --backend-response-buffer --http2-no-cookie-crumbling --backend-read-timeout --stream-read-timeout --workers --worker-read-burst --dh-param-file --errorlog-syslog --frontend --accesslog-file --http2-proxy --frontend-http2-read-timeout --accesslog-format --frontend-http2-window-bits --backend-no-tls --client-private-key-file --pid-file --client-cert-file --no-host-rewrite --log-level --worker-write-rate --help --backend-tls-sni-field --subcert --frontend-frame-debug --frontend-write-timeout --verify-client --read-rate --read-burst --backend-ipv4 --listener-disable-timeout --backend-ipv6 --backend ' -- "$cur" ) ) ;; *) _filedir diff --git a/doc/h2load.1 b/doc/h2load.1 index 4f1f54c6..3fed07cd 100644 --- a/doc/h2load.1 +++ b/doc/h2load.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "H2LOAD" "1" "March 31, 2015" "0.7.10-DEV" "nghttp2" +.TH "H2LOAD" "1" "April 08, 2015" "0.7.10" "nghttp2" .SH NAME h2load \- HTTP/2 benchmarking tool . diff --git a/doc/nghttp.1 b/doc/nghttp.1 index d5a48c8c..ba533a30 100644 --- a/doc/nghttp.1 +++ b/doc/nghttp.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTP" "1" "March 31, 2015" "0.7.10-DEV" "nghttp2" +.TH "NGHTTP" "1" "April 08, 2015" "0.7.10" "nghttp2" .SH NAME nghttp \- HTTP/2 experimental client . diff --git a/doc/nghttpd.1 b/doc/nghttpd.1 index 35647819..67337d8d 100644 --- a/doc/nghttpd.1 +++ b/doc/nghttpd.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTPD" "1" "March 31, 2015" "0.7.10-DEV" "nghttp2" +.TH "NGHTTPD" "1" "April 08, 2015" "0.7.10" "nghttp2" .SH NAME nghttpd \- HTTP/2 experimental server . diff --git a/doc/nghttpx.1 b/doc/nghttpx.1 index 2832b267..5ceda445 100644 --- a/doc/nghttpx.1 +++ b/doc/nghttpx.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "NGHTTPX" "1" "March 31, 2015" "0.7.10-DEV" "nghttp2" +.TH "NGHTTPX" "1" "April 08, 2015" "0.7.10" "nghttp2" .SH NAME nghttpx \- HTTP/2 experimental proxy . @@ -435,17 +435,6 @@ are stored in memory. .UNINDENT .INDENT 0.0 .TP -.B \-\-tls\-ctx\-per\-worker -Create OpenSSL\(aqs SSL_CTX per worker, so that no internal -locking is required. This may improve scalability with -multi threaded configuration. If this option is -enabled, session ID is no longer shared accross SSL_CTX -objects, which means session ID generated by one worker -is not acceptable by another worker. On the other hand, -session ticket key is shared across all worker threads. -.UNINDENT -.INDENT 0.0 -.TP .B \-\-fetch\-ocsp\-response\-file= Path to fetch\-ocsp\-response script file. It should be absolute path. @@ -643,7 +632,8 @@ Default: \fB$remote_addr \- \- [$time_local] "$request" $status $body_bytes_sent .TP .B \-\-errorlog\-file= Set path to write error log. To reopen file, send USR1 -signal to nghttpx. +signal to nghttpx. stderr will be redirected to the +error log file unless \fI\%\-\-errorlog\-syslog\fP is used. .sp Default: \fB/dev/stderr\fP .UNINDENT @@ -872,6 +862,15 @@ specified socket already exists in the file system, nghttpx first deletes it. However, if SIGUSR2 is used to execute new binary and both old and new configurations use same filename, new binary does not delete the socket and continues to use it. +.SH OCSP STAPLING +.sp +OCSP query is done using external perl script \fBfetch\-ocsp\-response\fP, +which has been developed as part of h2o project +(\fI\%https://github.com/h2o/h2o\fP). +.sp +The script file is usually installed under +\fB$(prefix)/share/nghttp2/\fP directory. The actual path to script can +be customized using \fI\%\-\-fetch\-ocsp\-response\-file\fP option. .SH SEE ALSO .sp \fInghttp(1)\fP, \fInghttpd(1)\fP, \fIh2load(1)\fP diff --git a/doc/nghttpx.1.rst b/doc/nghttpx.1.rst index 340a7408..4aff82cd 100644 --- a/doc/nghttpx.1.rst +++ b/doc/nghttpx.1.rst @@ -377,16 +377,6 @@ SSL/TLS automatically and renewed every 12hrs. At most 2 keys are stored in memory. -.. option:: --tls-ctx-per-worker - - Create OpenSSL's SSL_CTX per worker, so that no internal - locking is required. This may improve scalability with - multi threaded configuration. If this option is - enabled, session ID is no longer shared accross SSL_CTX - objects, which means session ID generated by one worker - is not acceptable by another worker. On the other hand, - session ticket key is shared across all worker threads. - .. option:: --fetch-ocsp-response-file= Path to fetch-ocsp-response script file. It should be @@ -561,7 +551,8 @@ Logging .. option:: --errorlog-file= Set path to write error log. To reopen file, send USR1 - signal to nghttpx. + signal to nghttpx. stderr will be redirected to the + error log file unless :option:`--errorlog-syslog` is used. Default: ``/dev/stderr`` @@ -784,6 +775,17 @@ deletes it. However, if SIGUSR2 is used to execute new binary and both old and new configurations use same filename, new binary does not delete the socket and continues to use it. +OCSP STAPLING +------------- + +OCSP query is done using external perl script ``fetch-ocsp-response``, +which has been developed as part of h2o project +(https://github.com/h2o/h2o). + +The script file is usually installed under +``$(prefix)/share/nghttp2/`` directory. The actual path to script can +be customized using :option:`--fetch-ocsp-response-file` option. + SEE ALSO -------- diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go index fc1daf54..909816c3 100644 --- a/integration-tests/nghttpx_http2_test.go +++ b/integration-tests/nghttpx_http2_test.go @@ -8,6 +8,7 @@ import ( "io" "io/ioutil" "net/http" + "strings" "syscall" "testing" ) @@ -494,7 +495,9 @@ func TestH2H1SNI(t *testing.T) { func TestH2H1ServerPush(t *testing.T) { st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) { // only resources marked as rel=preload are pushed - w.Header().Add("Link", "; rel=preload, , ; rel=preload") + if !strings.HasPrefix(r.URL.Path, "/css/") { + w.Header().Add("Link", "; rel=preload, , ; rel=preload") + } }) defer st.Close() diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index c1a725cb..ad7ea1a6 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2673,7 +2673,11 @@ NGHTTP2_EXTERN uint32_t * * Tells the |session| that |size| bytes for a stream denoted by * |stream_id| were consumed by application and are ready to - * WINDOW_UPDATE. This function is intended to be used without + * WINDOW_UPDATE. The consumed bytes are counted towards both + * connection and stream level WINDOW_UPDATE (see + * `nghttp2_session_consume_connection()` and + * `nghttp2_session_consume_stream()` to update consumption + * independently). This function is intended to be used without * automatic window update (see * `nghttp2_option_set_no_auto_window_update()`). * @@ -2690,6 +2694,47 @@ NGHTTP2_EXTERN uint32_t NGHTTP2_EXTERN int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, size_t size); +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for connection level. Note that + * HTTP/2 maintains connection and stream level flow control windows + * independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_connection(nghttp2_session *session, + size_t size); + +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for stream denoted by |stream_id|. + * Note that HTTP/2 maintains connection and stream level flow control + * windows independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session, + int32_t stream_id, + size_t size); + /** * @function * @@ -3336,6 +3381,9 @@ NGHTTP2_EXTERN int32_t * The |flags| is currently ignored and should be * :enum:`NGHTTP2_FLAG_NONE`. * + * The |stream_id| is the stream ID to send this WINDOW_UPDATE. To + * send connection level WINDOW_UPDATE, specify 0 to |stream_id|. + * * If the |window_size_increment| is positive, the WINDOW_UPDATE with * that value as window_size_increment is queued. If the * |window_size_increment| is larger than the received bytes from the diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index cae5c614..1469a427 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -6612,12 +6612,58 @@ int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id, stream = nghttp2_session_get_stream(session, stream_id); - if (stream) { - rv = session_update_stream_consumed_size(session, stream, size); + if (!stream) { + return 0; + } - if (nghttp2_is_fatal(rv)) { - return rv; - } + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_connection(nghttp2_session *session, size_t size) { + int rv; + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + rv = session_update_connection_consumed_size(session, size); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + return 0; +} + +int nghttp2_session_consume_stream(nghttp2_session *session, int32_t stream_id, + size_t size) { + int rv; + nghttp2_stream *stream; + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) { + return NGHTTP2_ERR_INVALID_STATE; + } + + stream = nghttp2_session_get_stream(session, stream_id); + + if (!stream) { + return 0; + } + + rv = session_update_stream_consumed_size(session, stream, size); + + if (nghttp2_is_fatal(rv)) { + return rv; } return 0; diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index cdb67f2f..09b70d79 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -87,7 +87,8 @@ void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) { } static int stream_push_item(nghttp2_stream *stream, nghttp2_session *session) { - int rv; + /* This is required for Android NDK r10d */ + int rv = 0; nghttp2_outbound_item *item; assert(stream->item); diff --git a/src/memchunk.h b/src/memchunk.h index d2e7a6c7..0f12c15e 100644 --- a/src/memchunk.h +++ b/src/memchunk.h @@ -102,6 +102,11 @@ template struct Pool { } freelist = m; } + void clear() { + freelist = nullptr; + pool = nullptr; + poolsize = 0; + } using value_type = T; std::unique_ptr pool; T *freelist; diff --git a/src/shrpx.cc b/src/shrpx.cc index 7e1ff06e..b53709f8 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -422,6 +422,7 @@ void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { } (void)reopen_log_files(); + redirect_stderr_to_errorlog(); if (get_config()->num_worker > 1) { conn_handler->worker_reopen_log_files(); @@ -636,6 +637,10 @@ int event_loop() { // We get new PID after successful daemon(). mod_config()->pid = getpid(); + + // daemon redirects stderr file descriptor to /dev/null, so we + // need this. + redirect_stderr_to_errorlog(); } if (get_config()->pid_file) { @@ -1311,7 +1316,8 @@ Logging: Default: )" << DEFAULT_ACCESSLOG_FORMAT << R"( --errorlog-file= Set path to write error log. To reopen file, send USR1 - signal to nghttpx. + signal to nghttpx. stderr will be redirected to the + error log file unless --errorlog-syslog is used. Default: )" << get_config()->errorlog_file.get() << R"( --errorlog-syslog Send error log to syslog. If this option is used, @@ -1917,6 +1923,8 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } + redirect_stderr_to_errorlog(); + if (get_config()->uid != 0) { if (log_config()->accesslog_fd != -1 && fchown(log_config()->accesslog_fd, get_config()->uid, diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 4554f428..53f8ce82 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -402,6 +402,10 @@ ClientHandler::~ClientHandler() { auto worker_stat = worker_->get_worker_stat(); --worker_stat->num_connections; + if (worker_stat->num_connections == 0) { + worker_->schedule_clear_mcpool(); + } + ev_timer_stop(conn_.loop, &reneg_shutdown_timer_); // TODO If backend is http/2, and it is in CONNECTED state, signal @@ -556,7 +560,14 @@ int ClientHandler::validate_next_proto() { int ClientHandler::do_read() { return read_(*this); } int ClientHandler::do_write() { return write_(*this); } -int ClientHandler::on_read() { return on_read_(*this); } +int ClientHandler::on_read() { + auto rv = on_read_(*this); + if (rv != 0) { + return rv; + } + conn_.handle_tls_pending_read(); + return 0; +} int ClientHandler::on_write() { return on_write_(*this); } const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; } @@ -623,6 +634,8 @@ ClientHandler::get_downstream_connection() { return dconn; } +MemchunkPool *ClientHandler::get_mcpool() { return worker_->get_mcpool(); } + SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; } ConnectBlocker *ClientHandler::get_connect_blocker() const { diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h index 88d11675..4367c2b5 100644 --- a/src/shrpx_client_handler.h +++ b/src/shrpx_client_handler.h @@ -36,6 +36,7 @@ #include "shrpx_rate_limit.h" #include "shrpx_connection.h" #include "buffer.h" +#include "memchunk.h" using namespace nghttp2; @@ -92,6 +93,7 @@ public: void pool_downstream_connection(std::unique_ptr dconn); void remove_downstream_connection(DownstreamConnection *dconn); std::unique_ptr get_downstream_connection(); + MemchunkPool *get_mcpool(); SSL *get_ssl() const; ConnectBlocker *get_connect_blocker() const; // Call this function when HTTP/2 connection header is received at diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index 5edc480b..51a5d39c 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -41,7 +41,7 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, size_t read_burst, IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data) : tls{ssl}, wlimit(loop, &wev, write_rate, write_burst), - rlimit(loop, &rev, read_rate, read_burst), writecb(writecb), + rlimit(loop, &rev, read_rate, read_burst, ssl), writecb(writecb), readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd) { ev_io_init(&wev, writecb, fd, EV_WRITE); @@ -303,4 +303,11 @@ ssize_t Connection::read_clear(void *data, size_t len) { return nread; } +void Connection::handle_tls_pending_read() { + if (!ev_is_active(&rev)) { + return; + } + rlimit.handle_tls_pending_read(); +} + } // namespace shrpx diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h index fc6a14a3..4e348412 100644 --- a/src/shrpx_connection.h +++ b/src/shrpx_connection.h @@ -83,6 +83,8 @@ struct Connection { ssize_t writev_clear(struct iovec *iov, int iovcnt); ssize_t read_clear(void *data, size_t len); + void handle_tls_pending_read(); + TLSConnection tls; ev_io wev; ev_io rev; diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index a176de3e..87e8ff58 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -106,12 +106,12 @@ void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { } // namespace // upstream could be nullptr for unittests -Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority) +Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, + int32_t stream_id, int32_t priority) : dlnext(nullptr), dlprev(nullptr), request_start_time_(std::chrono::high_resolution_clock::now()), - request_buf_(upstream ? upstream->get_mcpool() : nullptr), - response_buf_(upstream ? upstream->get_mcpool() : nullptr), - request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0), + request_buf_(mcpool), response_buf_(mcpool), request_bodylen_(0), + response_bodylen_(0), response_sent_bodylen_(0), request_content_length_(-1), response_content_length_(-1), upstream_(upstream), blocked_link_(nullptr), request_headers_sum_(0), response_headers_sum_(0), request_datalen_(0), response_datalen_(0), diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 6febe806..1f55cd40 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -52,7 +52,8 @@ struct BlockedLink; class Downstream { public: - Downstream(Upstream *upstream, int32_t stream_id, int32_t priority); + Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id, + int32_t priority); ~Downstream(); void reset_upstream(Upstream *upstream); Upstream *get_upstream() const; diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc index ad0abaa4..2e7a6960 100644 --- a/src/shrpx_downstream_test.cc +++ b/src/shrpx_downstream_test.cc @@ -33,7 +33,7 @@ namespace shrpx { void test_downstream_index_request_headers(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_request_header("1", "0"); d.add_request_header("2", "1"); d.add_request_header("Charlie", "2"); @@ -56,7 +56,7 @@ void test_downstream_index_request_headers(void) { } void test_downstream_index_response_headers(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_response_header("Charlie", "0"); d.add_response_header("Alpha", "1"); d.add_response_header("Delta", "2"); @@ -69,7 +69,7 @@ void test_downstream_index_response_headers(void) { } void test_downstream_get_request_header(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_request_header("alpha", "0"); d.add_request_header(":authority", "1"); d.add_request_header("content-length", "2"); @@ -86,7 +86,7 @@ void test_downstream_get_request_header(void) { } void test_downstream_get_response_header(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_response_header("alpha", "0"); d.add_response_header(":status", "1"); d.add_response_header("content-length", "2"); @@ -99,7 +99,7 @@ void test_downstream_get_response_header(void) { } void test_downstream_crumble_request_cookie(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_request_header(":method", "get"); d.add_request_header(":path", "/"); auto val = "alpha; bravo; ; ;; charlie;;"; @@ -122,7 +122,7 @@ void test_downstream_crumble_request_cookie(void) { } void test_downstream_assemble_request_cookie(void) { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.add_request_header(":method", "get"); d.add_request_header(":path", "/"); d.add_request_header("cookie", "alpha"); @@ -135,7 +135,7 @@ void test_downstream_assemble_request_cookie(void) { void test_downstream_rewrite_location_response_header(void) { { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.set_request_downstream_host("localhost:3000"); d.add_request_header("host", "localhost"); d.add_response_header("location", "http://localhost:3000/"); @@ -146,7 +146,7 @@ void test_downstream_rewrite_location_response_header(void) { CU_ASSERT("https://localhost/" == (*location).value); } { - Downstream d(nullptr, 0, 0); + Downstream d(nullptr, nullptr, 0, 0); d.set_request_downstream_host("localhost"); d.set_request_http2_authority("localhost"); d.add_response_header("location", "http://localhost:3000/"); diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 121db248..79f41043 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -256,8 +256,11 @@ int on_begin_headers_callback(nghttp2_session *session, << frame->hd.stream_id; } + auto handler = upstream->get_client_handler(); + // TODO Use priority 0 for now - auto downstream = make_unique(upstream, frame->hd.stream_id, 0); + auto downstream = make_unique(upstream, handler->get_mcpool(), + frame->hd.stream_id, 0); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, downstream.get()); @@ -484,16 +487,47 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, verbose_on_frame_send_callback(session, frame, user_data); } auto upstream = static_cast(user_data); + auto handler = upstream->get_client_handler(); switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: { + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { + return 0; + } + // RST_STREAM if request is still incomplete. + auto stream_id = frame->hd.stream_id; + auto downstream = static_cast( + nghttp2_session_get_stream_user_data(session, stream_id)); + + // For tunneling, issue RST_STREAM to finish the stream. + if (downstream->get_upgraded() || + nghttp2_session_get_stream_remote_close(session, stream_id) == 0) { + if (LOG_ENABLED(INFO)) { + ULOG(INFO, upstream) + << "Send RST_STREAM to " + << (downstream->get_upgraded() ? "tunneled " : "") + << "stream stream_id=" << downstream->get_stream_id() + << " to finish off incomplete request"; + } + + upstream->rst_stream(downstream, NGHTTP2_NO_ERROR); + } + + return 0; + } case NGHTTP2_SETTINGS: if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) { upstream->start_settings_timer(); } return 0; case NGHTTP2_PUSH_PROMISE: { - auto downstream = make_unique( - upstream, frame->push_promise.promised_stream_id, 0); + auto promised_stream_id = frame->push_promise.promised_stream_id; + auto downstream = make_unique(upstream, handler->get_mcpool(), + promised_stream_id, 0); + + nghttp2_session_set_stream_user_data(session, promised_stream_id, + downstream.get()); downstream->disable_upstream_rtimer(); @@ -1096,16 +1130,6 @@ ssize_t downstream_data_read_callback(nghttp2_session *session, } } } - if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) { - upstream->rst_stream(downstream, NGHTTP2_NO_ERROR); - } - } else { - // For tunneling, issue RST_STREAM to finish the stream. - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) - << "RST_STREAM to tunneled stream stream_id=" << stream_id; - } - upstream->rst_stream(downstream, NGHTTP2_NO_ERROR); } } @@ -1480,8 +1504,6 @@ int Http2Upstream::on_downstream_reset(bool no_retry) { return 0; } -MemchunkPool *Http2Upstream::get_mcpool() { return &mcpool_; } - int Http2Upstream::prepare_push_promise(Downstream *downstream) { int rv; http_parser_url u; diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h index 81b42563..ef8c0bee 100644 --- a/src/shrpx_http2_upstream.h +++ b/src/shrpx_http2_upstream.h @@ -79,8 +79,6 @@ public: virtual void on_handler_delete(); virtual int on_downstream_reset(bool no_retry); - virtual MemchunkPool *get_mcpool(); - bool get_flow_control() const; // Perform HTTP/2 upgrade from |upstream|. On success, this object // takes ownership of the |upstream|. This function returns 0 if it @@ -103,9 +101,7 @@ public: int on_request_headers(Downstream *downstream, const nghttp2_frame *frame); private: - // must be put before downstream_queue_ std::unique_ptr pre_upstream_; - MemchunkPool mcpool_; DownstreamQueue downstream_queue_; ev_timer settings_timer_; ev_timer shutdown_timer_; diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index fc2f9d86..e1b90383 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -720,13 +720,6 @@ int HttpDownstreamConnection::on_read() { http_parser_execute(&response_htp_, &htp_hooks, reinterpret_cast(buf.data()), nread); - if (nproc != static_cast(nread)) { - if (LOG_ENABLED(INFO)) { - DCLOG(INFO, this) << "nproc != nread"; - } - return -1; - } - auto htperr = HTTP_PARSER_ERRNO(&response_htp_); if (htperr != HPE_OK) { @@ -739,6 +732,13 @@ int HttpDownstreamConnection::on_read() { return -1; } + if (nproc != static_cast(nread)) { + if (LOG_ENABLED(INFO)) { + DCLOG(INFO, this) << "nproc != nread"; + } + return -1; + } + if (downstream_->response_buf_full()) { downstream_->pause_read(SHRPX_NO_BUFFER); return 0; diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index c760c5db..6a6ed2f2 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -64,8 +64,12 @@ int htp_msg_begin(http_parser *htp) { ULOG(INFO, upstream) << "HTTP request started"; } upstream->reset_current_header_length(); + + auto handler = upstream->get_client_handler(); + // TODO specify 0 as priority for now - upstream->attach_downstream(make_unique(upstream, 0, 0)); + upstream->attach_downstream( + make_unique(upstream, handler->get_mcpool(), 0, 0)); return 0; } } // namespace @@ -636,7 +640,8 @@ void HttpsUpstream::error_reply(unsigned int status_code) { auto downstream = get_downstream(); if (!downstream) { - attach_downstream(make_unique(this, 1, 1)); + attach_downstream( + make_unique(this, handler_->get_mcpool(), 1, 1)); downstream = get_downstream(); } @@ -926,6 +931,4 @@ fail: return 0; } -MemchunkPool *HttpsUpstream::get_mcpool() { return &mcpool_; } - } // namespace shrpx diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h index 820d462c..1b0c648a 100644 --- a/src/shrpx_https_upstream.h +++ b/src/shrpx_https_upstream.h @@ -76,8 +76,6 @@ public: virtual void on_handler_delete(); virtual int on_downstream_reset(bool no_retry); - virtual MemchunkPool *get_mcpool(); - void reset_current_header_length(); void log_response_headers(const std::string &hdrs) const; @@ -85,8 +83,6 @@ private: ClientHandler *handler_; http_parser htp_; size_t current_header_length_; - // must be put before downstream_ - MemchunkPool mcpool_; std::unique_ptr downstream_; IOControl ioctrl_; }; diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc index 955c5dc7..421dcb06 100644 --- a/src/shrpx_log.cc +++ b/src/shrpx_log.cc @@ -324,4 +324,14 @@ int reopen_log_files() { return res; } +void redirect_stderr_to_errorlog() { + auto lgconf = log_config(); + + if (get_config()->errorlog_syslog || lgconf->errorlog_fd == -1) { + return; + } + + dup2(lgconf->errorlog_fd, STDERR_FILENO); +} + } // namespace shrpx diff --git a/src/shrpx_log.h b/src/shrpx_log.h index 14604633..65e67960 100644 --- a/src/shrpx_log.h +++ b/src/shrpx_log.h @@ -143,6 +143,8 @@ void upstream_accesslog(const std::vector &lf, LogSpec *lgsp); int reopen_log_files(); +void redirect_stderr_to_errorlog(); + } // namespace shrpx #endif // SHRPX_LOG_H diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc index 3f2ec95b..85836c14 100644 --- a/src/shrpx_rate_limit.cc +++ b/src/shrpx_rate_limit.cc @@ -35,8 +35,9 @@ void regencb(struct ev_loop *loop, ev_timer *w, int revents) { } } // namespace -RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst) - : w_(w), loop_(loop), rate_(rate), burst_(burst), avail_(burst), +RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, + SSL *ssl) + : w_(w), loop_(loop), ssl_(ssl), rate_(rate), burst_(burst), avail_(burst), startw_req_(false) { ev_timer_init(&t_, regencb, 0., 1.); t_.data = this; @@ -45,9 +46,7 @@ RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst) } } -RateLimit::~RateLimit() { - ev_timer_stop(loop_, &t_); -} +RateLimit::~RateLimit() { ev_timer_stop(loop_, &t_); } size_t RateLimit::avail() const { if (rate_ == 0) { @@ -79,6 +78,7 @@ void RateLimit::regen() { if (avail_ > 0 && startw_req_) { ev_io_start(loop_, w_); + handle_tls_pending_read(); } } @@ -86,6 +86,7 @@ void RateLimit::startw() { startw_req_ = true; if (rate_ == 0 || avail_ > 0) { ev_io_start(loop_, w_); + handle_tls_pending_read(); return; } } @@ -95,4 +96,14 @@ void RateLimit::stopw() { ev_io_stop(loop_, w_); } +void RateLimit::handle_tls_pending_read() { + if (!ssl_ || SSL_pending(ssl_) == 0) { + return; + } + + // Note that ev_feed_event works without starting watcher, but we + // only call this function if watcher is active. + ev_feed_event(loop_, w_, EV_READ); +} + } // namespace shrpx diff --git a/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h index db51df40..4550d012 100644 --- a/src/shrpx_rate_limit.h +++ b/src/shrpx_rate_limit.h @@ -29,21 +29,31 @@ #include +#include + namespace shrpx { class RateLimit { public: - RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst); + // We need |ssl| object to check that it has unread decrypted bytes. + RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst, + SSL *ssl = nullptr); ~RateLimit(); size_t avail() const; void drain(size_t n); void regen(); void startw(); void stopw(); + // Feeds event if ssl_ object has unread decrypted bytes. This is + // required since it is buffered in ssl_ object, io event is not + // generated unless new incoming data is received. + void handle_tls_pending_read(); + private: - ev_io *w_; ev_timer t_; + ev_io *w_; struct ev_loop *loop_; + SSL *ssl_; size_t rate_; size_t burst_; size_t avail_; diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 4968a413..bf49c28f 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -797,7 +797,8 @@ int SpdyUpstream::error_reply(Downstream *downstream, Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id, int32_t priority) { - auto downstream = make_unique(this, stream_id, priority); + auto downstream = make_unique(this, handler_->get_mcpool(), + stream_id, priority); spdylay_session_set_stream_user_data(session_, stream_id, downstream.get()); auto res = downstream.get(); @@ -1080,6 +1081,4 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) { return 0; } -MemchunkPool *SpdyUpstream::get_mcpool() { return &mcpool_; } - } // namespace shrpx diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h index 2a6e37e1..2e4aec47 100644 --- a/src/shrpx_spdy_upstream.h +++ b/src/shrpx_spdy_upstream.h @@ -74,8 +74,6 @@ public: virtual void on_handler_delete(); virtual int on_downstream_reset(bool no_retry); - virtual MemchunkPool *get_mcpool(); - bool get_flow_control() const; int consume(int32_t stream_id, size_t len); @@ -84,8 +82,6 @@ public: void initiate_downstream(Downstream *downstream); private: - // must be put before downstream_queue_ - MemchunkPool mcpool_; DownstreamQueue downstream_queue_; ClientHandler *handler_; spdylay_session *session_; diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h index ad8e7420..fe9a673d 100644 --- a/src/shrpx_upstream.h +++ b/src/shrpx_upstream.h @@ -27,9 +27,6 @@ #include "shrpx.h" #include "shrpx_io_control.h" -#include "memchunk.h" - -using namespace nghttp2; namespace shrpx { @@ -65,8 +62,6 @@ public: virtual void pause_read(IOCtrlReason reason) = 0; virtual int resume_read(IOCtrlReason reason, Downstream *downstream, size_t consumed) = 0; - - virtual MemchunkPool *get_mcpool() = 0; }; } // namespace shrpx diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index f7d4d489..bbae73b1 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -37,8 +37,6 @@ #include "util.h" #include "template.h" -using namespace nghttp2; - namespace shrpx { namespace { @@ -48,6 +46,16 @@ void eventcb(struct ev_loop *loop, ev_async *w, int revents) { } } // namespace +namespace { +void mcpool_clear_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto worker = static_cast(w->data); + if (worker->get_worker_stat()->num_connections != 0) { + return; + } + worker->get_mcpool()->clear(); +} +} // namespace + Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, ssl::CertLookupTree *cert_tree, const std::shared_ptr &ticket_keys) @@ -59,6 +67,9 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, w_.data = this; ev_async_start(loop_, &w_); + ev_timer_init(&mcpool_clear_timer_, mcpool_clear_cb, 0., 0.); + mcpool_clear_timer_.data = this; + if (get_config()->downstream_proto == PROTO_HTTP2) { auto n = get_config()->http2_downstream_connections_per_worker; for (; n > 0; --n) { @@ -68,7 +79,17 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, } } -Worker::~Worker() { ev_async_stop(loop_, &w_); } +Worker::~Worker() { + ev_async_stop(loop_, &w_); + ev_timer_stop(loop_, &mcpool_clear_timer_); +} + +void Worker::schedule_clear_mcpool() { + // libev manual says: "If the watcher is already active nothing will + // happen." Since we don't change any timeout here, we don't have + // to worry about querying ev_is_active. + ev_timer_start(loop_, &mcpool_clear_timer_); +} void Worker::wait() { #ifndef NOTHREADS @@ -218,4 +239,6 @@ void Worker::set_graceful_shutdown(bool f) { graceful_shutdown_ = f; } bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; } +MemchunkPool *Worker::get_mcpool() { return &mcpool_; } + } // namespace shrpx diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index 78ed18ac..47213710 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -42,6 +42,9 @@ #include "shrpx_config.h" #include "shrpx_downstream_connection_pool.h" +#include "memchunk.h" + +using namespace nghttp2; namespace shrpx { @@ -104,6 +107,9 @@ public: void set_graceful_shutdown(bool f); bool get_graceful_shutdown() const; + MemchunkPool *get_mcpool(); + void schedule_clear_mcpool(); + private: std::vector> http2sessions_; size_t next_http2session_; @@ -113,6 +119,8 @@ private: std::mutex m_; std::deque q_; ev_async w_; + ev_timer mcpool_clear_timer_; + MemchunkPool mcpool_; DownstreamConnectionPool dconn_pool_; WorkerStat worker_stat_; struct ev_loop *loop_;