Merge branches 'master' and 'cmake-updated' into cmake

Update to latest master with appropriate cmake changes at the same time.
This commit is contained in:
Peter Wu 2016-03-14 17:26:15 +01:00
commit 7c55c335cc
137 changed files with 6091 additions and 3910 deletions

View File

@ -48,5 +48,5 @@ script:
- make check
- cd integration-tests
- export GOPATH="$PWD/integration-tests/golang"
- make itprep-local
- make it-local
- make itprep
- make it

81
AUTHORS
View File

@ -1 +1,80 @@
Tatsuhiro Tsujikawa <t-tujikawa at users dot sourceforge dot net>
nghttp2 project was started as a fork of spdylay project [1]. Both
projects were started by Tatsuhiro Tsujikawa, who is still the main
author of these projects. Meanwhile, we have many contributions, and
we are not here without them. We sincerely thank you to all who made
a contribution. Here is the all individuals/organizations who
contributed to nghttp2 and spdylay project at which we forked. These
names are retrieved from git commit log. If you have made a
contribution, but you are missing in the list, please let us know via
github issues [2].
[1] https://github.com/tatsuhiro-t/spdylay
[2] https://github.com/tatsuhiro-t/nghttp2/issues
--------
187j3x1
Alek Storm
Alex Nalivko
Alexis La Goutte
Anders Bakken
Andreas Pohl
Andy Davies
Ant Bryan
Bernard Spil
Brian Card
Daniel Stenberg
Dave Reisner
David Beitey
David Weekly
Etienne Cimon
Fabian Möller
Fabian Wiesel
Gabi Davar
Janusz Dziemidowicz
Jay Satiro
Jim Morrison
José F. Calcerrada
Kamil Dudka
Kazuho Oku
Kenny (kang-yen) Peng
Kenny Peng
Kit Chan
Kyle Schomp
Lucas Pardue
MATSUMOTO Ryosuke
Mike Frysinger
Nicholas Hurley
Nora Shoemaker
Peeyush Aggarwal
Peter Wu
Piotr Sikora
Raul Gutierrez Segales
Remo E
Reza Tavakoli
Ross Smith II
Scott Mitchell
Stefan Eissing
Stephen Ludin
Sunpoet Po-Chuan Hsieh
Svante Signell
Syohei YOSHIDA
Tatsuhiko Kubo
Tatsuhiro Tsujikawa
Tom Harwood
Tomasz Buchert
Vernon Tang
Viacheslav Biriukov
Viktor Szépe
Xiaoguang Sun
Zhuoyun Wei
acesso
ayanamist
bxshi
es
fangdingjun
kumagi
mod-h2-dev
moparisthebest
snnn
yuuki-kodama

View File

@ -23,14 +23,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cmake_minimum_required(VERSION 3.0)
# XXX using 1.7.90 instead of 1.8.0-DEV
project(nghttp2 VERSION 1.7.90)
# XXX using 1.8.90 instead of 1.9.0-DEV
project(nghttp2 VERSION 1.8.90)
# See versioning rule:
# http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
set(LT_CURRENT 18)
set(LT_REVISION 1)
set(LT_AGE 4)
set(LT_CURRENT 19)
set(LT_REVISION 0)
set(LT_AGE 5)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(Version)
@ -280,6 +280,13 @@ check_function_exists(accept4 HAVE_ACCEPT4)
include(CheckSymbolExists)
# XXX does this correctly detect initgroups (un)availability on cygwin?
check_symbol_exists(initgroups grp.h HAVE_DECL_INITGROUPS)
if(NOT HAVE_DECL_INITGROUPS AND HAVE_UNISTD_H)
# FreeBSD declares initgroups() in unistd.h
check_symbol_exists(initgroups unistd.h HAVE_DECL_INITGROUPS2)
if(HAVE_DECL_INITGROUPS2)
set(HAVE_DECL_INITGROUPS 1)
endif()
endif()
check_function_exists(timerfd_create HAVE_TIMERFD_CREATE)
# Checks for epoll availability, primarily for examples/tiny-nghttpd
@ -341,6 +348,8 @@ if(NOT CMAKE_C_COMPILER_ID STREQUAL "MSVC")
-Wredundant-decls
# Only work with Clang for the moment
-Wheader-guard
# This is required because we pass format string as "const char*.
-Wno-format-nonliteral
)
extract_valid_cxx_flags(WARNCXXFLAGS

View File

@ -1,6 +1,7 @@
The MIT License
Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa
Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -655,46 +655,38 @@ HTTP/2. ``nghttpx`` also offers the functionality to share session
cache and ticket keys among multiple ``nghttpx`` instances via
memcached.
``nghttpx`` has several operational modes:
``nghttpx`` has 2 operation modes:
================== ============================ ============== =============
Mode option Frontend Backend Note
================== ============================ ============== =============
default mode HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1 Reverse proxy
``--http2-proxy`` HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1 SPDY proxy
``--http2-bridge`` HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/2 (TLS)
``--client`` HTTP/2, HTTP/1.1 HTTP/2 (TLS)
``--client-proxy`` HTTP/2, HTTP/1.1 HTTP/2 (TLS) Forward proxy
================== ============================ ============== =============
================== ====================== =================== =============
Mode option Frontend Backend Note
================== ====================== =================== =============
default mode HTTP/2, SPDY, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy
``--http2-proxy`` HTTP/2, SPDY, HTTP/1.1 HTTP/1.1, or HTTP/2 Forward proxy
================== ====================== =================== =============
The interesting mode at the moment is the default mode. It works like
a reverse proxy and listens for HTTP/2, SPDY and HTTP/1.1 and can be
deployed as a SSL/TLS terminator for existing web server.
The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use
SSL/TLS in the frontend connection by default. To disable SSL/TLS,
use the ``--frontend-no-tls`` option. If that option is used, SPDY is
disabled in the frontend and incoming HTTP/1.1 connections can be
upgraded to HTTP/2 through HTTP Upgrade. In these modes, HTTP/1
backend connections are cleartext by default. To enable TLS, use
``--backend-http1-tls`` opiton.
The ``--http2-bridge``, ``--client`` and ``--client-proxy`` modes use
SSL/TLS in the backend connection by default. To disable SSL/TLS, use
the ``--backend-no-tls`` option.
In all modes, the frontend connections are encrypted by SSL/TLS by
default. To disable encryption, use the ``--frontend-no-tls`` option.
If encryption is disabled, SPDY is disabled in the frontend and
incoming HTTP/1.1 connections can be upgraded to HTTP/2 through HTTP
Upgrade. On the other hard, backend connections are not encrypted by
default. To encrypt backend connections, use ``--backend-tls``
option.
``nghttpx`` supports a configuration file. See the ``--conf`` option and
sample configuration file ``nghttpx.conf.sample``.
In the default mode, (without any of ``--http2-proxy``,
``--http2-bridge``, ``--client-proxy`` and ``--client`` options),
``nghttpx`` works as reverse proxy to the backend server::
In the default mode, ``nghttpx`` works as reverse proxy to the backend
server::
Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Web Server
Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server
[reverse proxy]
With the ``--http2-proxy`` option, it works as a so called secure proxy (aka
SPDY proxy)::
With the ``--http2-proxy`` option, it works as forward proxy, and it
is so called secure HTTP/2 proxy (aka SPDY proxy)::
Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy
[secure proxy] (e.g., Squid, ATS)
@ -702,9 +694,9 @@ SPDY proxy)::
The ``Client`` in the above example needs to be configured to use
``nghttpx`` as secure proxy.
At the time of this writing, Chrome is the only browser which supports
secure proxy. One way to configure Chrome to use a secure proxy is
to create a proxy.pac script like this:
At the time of this writing, both Chrome and Firefox support secure
HTTP/2 proxy. One way to configure Chrome to use a secure proxy is to
create a proxy.pac script like this:
.. code-block:: javascript
@ -720,37 +712,9 @@ Then run Chrome with the following arguments::
$ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
With ``--http2-bridge``, it accepts HTTP/2, SPDY and HTTP/1.1
connections and communicates with the backend in HTTP/2::
Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> Web or HTTP/2 Proxy etc
(e.g., nghttpx -s)
With ``--client-proxy``, it works as a forward proxy and expects
that the backend is an HTTP/2 proxy::
Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> HTTP/2 Proxy
[forward proxy] (e.g., nghttpx -s)
The ``Client`` needs to be configured to use nghttpx as a forward
proxy. The frontend HTTP/1.1 connection can be upgraded to HTTP/2
through HTTP Upgrade. With the above configuration, one can use
HTTP/1.1 client to access and test their HTTP/2 servers.
With ``--client``, it works as a reverse proxy and expects that
the backend is an HTTP/2 Web server::
Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> Web Server
[reverse proxy]
The frontend HTTP/1.1 connection can be upgraded to HTTP/2
through HTTP Upgrade.
For the operation modes which talk to the backend in HTTP/2 over
SSL/TLS, the backend connections can be tunneled through an HTTP proxy.
The backend HTTP/2 connections can be tunneled through an HTTP proxy.
The proxy is specified using ``--backend-http-proxy-uri``. The
following figure illustrates the example of the ``--http2-bridge`` and
``--backend-http-proxy-uri`` options to talk to the outside HTTP/2
following figure illustrates how nghttpx talks to the outside HTTP/2
proxy through an HTTP proxy::
Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/2) --

View File

@ -25,7 +25,7 @@ dnl Do not change user variables!
dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
AC_PREREQ(2.61)
AC_INIT([nghttp2], [1.8.0-DEV], [t-tujikawa@users.sourceforge.net])
AC_INIT([nghttp2], [1.9.0-DEV], [t-tujikawa@users.sourceforge.net])
AC_CONFIG_AUX_DIR([.])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS([config.h])
@ -40,15 +40,13 @@ AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([subdir-objects])
# AM_EXTRA_RECURSIVE_TARGETS requires automake 1.13 or higher
m4_ifdef([AM_EXTRA_RECURSIVE_TARGETS], [AM_EXTRA_RECURSIVE_TARGETS([it itprep])])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl See versioning rule:
dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
AC_SUBST(LT_CURRENT, 18)
AC_SUBST(LT_REVISION, 1)
AC_SUBST(LT_AGE, 4)
AC_SUBST(LT_CURRENT, 19)
AC_SUBST(LT_REVISION, 0)
AC_SUBST(LT_AGE, 5)
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"`
@ -654,8 +652,13 @@ AC_CHECK_FUNC([timerfd_create],
# For cygwin: we can link initgroups, so AC_CHECK_FUNCS succeeds, but
# cygwin disables initgroups due to feature test macro magic with our
# configuration.
AC_CHECK_DECLS([initgroups], [], [], [[#include <grp.h>]])
# configuration. FreeBSD declares initgroups() in unistd.h.
AC_CHECK_DECLS([initgroups], [], [], [[
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <grp.h>
]])
# Checks for epoll availability, primarily for examples/tiny-nghttpd
AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])
@ -718,6 +721,9 @@ if test "x$werror" != "xno"; then
# Only work with Clang for the moment
AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"])
# This is required because we pass format string as "const char*.
AX_CHECK_COMPILE_FLAG([-Wno-format-nonliteral], [CFLAGS="$CFLAGS -Wno-format-nonliteral"])
# For C++ compiler
AC_LANG_PUSH(C++)
AX_CHECK_COMPILE_FLAG([-Wall], [CXXFLAGS="$CXXFLAGS -Wall"])

View File

@ -31,13 +31,16 @@ EXTRA_DIST = \
edit = sed -e 's|@bindir[@]|$(bindir)|g'
nghttpx-init: %: $(srcdir)/%.in
nghttpx-init: $(srcdir)/nghttpx-init.in
rm -f $@ $@.tmp
$(edit) $< > $@.tmp
chmod +x $@.tmp
mv $@.tmp $@
nghttpx.service nghttpx-upstart.conf: %: $(srcdir)/%.in
nghttpx.service: $(srcdir)/nghttpx.service.in
$(edit) $< > $@
nghttpx-upstart.conf: $(srcdir)/nghttpx-upstart.conf.in
$(edit) $< > $@
$(configfiles): Makefile

View File

@ -25,37 +25,48 @@ set(APIDOCS
nghttp2_hd_inflate_hd.rst
nghttp2_hd_inflate_new.rst
nghttp2_hd_inflate_new2.rst
nghttp2_http2_strerror.rst
nghttp2_is_fatal.rst
nghttp2_nv_compare_name.rst
nghttp2_option_del.rst
nghttp2_option_new.rst
nghttp2_option_set_max_reserved_remote_streams.rst
nghttp2_option_set_no_auto_ping_ack.rst
nghttp2_option_set_no_auto_window_update.rst
nghttp2_option_set_no_http_messaging.rst
nghttp2_option_set_no_recv_client_magic.rst
nghttp2_option_set_peer_max_concurrent_streams.rst
nghttp2_option_set_user_recv_extension_type.rst
nghttp2_pack_settings_payload.rst
nghttp2_priority_spec_check_default.rst
nghttp2_priority_spec_default_init.rst
nghttp2_priority_spec_init.rst
nghttp2_rcbuf_decref.rst
nghttp2_rcbuf_get_buf.rst
nghttp2_rcbuf_incref.rst
nghttp2_select_next_protocol.rst
nghttp2_session_callbacks_del.rst
nghttp2_session_callbacks_new.rst
nghttp2_session_callbacks_set_before_frame_send_callback.rst
nghttp2_session_callbacks_set_data_source_read_length_callback.rst
nghttp2_session_callbacks_set_error_callback.rst
nghttp2_session_callbacks_set_on_begin_frame_callback.rst
nghttp2_session_callbacks_set_on_begin_headers_callback.rst
nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst
nghttp2_session_callbacks_set_on_frame_not_send_callback.rst
nghttp2_session_callbacks_set_on_frame_recv_callback.rst
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst
nghttp2_session_callbacks_set_on_frame_send_callback.rst
nghttp2_session_callbacks_set_on_header_callback.rst
nghttp2_session_callbacks_set_on_header_callback2.rst
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst
nghttp2_session_callbacks_set_on_stream_close_callback.rst
nghttp2_session_callbacks_set_pack_extension_callback.rst
nghttp2_session_callbacks_set_recv_callback.rst
nghttp2_session_callbacks_set_select_padding_callback.rst
nghttp2_session_callbacks_set_send_callback.rst
nghttp2_session_callbacks_set_send_data_callback.rst
nghttp2_session_callbacks_set_unpack_extension_callback.rst
nghttp2_session_client_new.rst
nghttp2_session_client_new2.rst
nghttp2_session_client_new3.rst
@ -107,6 +118,7 @@ set(APIDOCS
nghttp2_stream_get_weight.rst
nghttp2_strerror.rst
nghttp2_submit_data.rst
nghttp2_submit_extension.rst
nghttp2_submit_goaway.rst
nghttp2_submit_headers.rst
nghttp2_submit_ping.rst

View File

@ -49,37 +49,48 @@ APIDOCS= \
nghttp2_hd_inflate_hd.rst \
nghttp2_hd_inflate_new.rst \
nghttp2_hd_inflate_new2.rst \
nghttp2_http2_strerror.rst \
nghttp2_is_fatal.rst \
nghttp2_nv_compare_name.rst \
nghttp2_option_del.rst \
nghttp2_option_new.rst \
nghttp2_option_set_max_reserved_remote_streams.rst \
nghttp2_option_set_no_auto_ping_ack.rst \
nghttp2_option_set_no_auto_window_update.rst \
nghttp2_option_set_no_http_messaging.rst \
nghttp2_option_set_no_recv_client_magic.rst \
nghttp2_option_set_peer_max_concurrent_streams.rst \
nghttp2_option_set_user_recv_extension_type.rst \
nghttp2_pack_settings_payload.rst \
nghttp2_priority_spec_check_default.rst \
nghttp2_priority_spec_default_init.rst \
nghttp2_priority_spec_init.rst \
nghttp2_rcbuf_decref.rst \
nghttp2_rcbuf_get_buf.rst \
nghttp2_rcbuf_incref.rst \
nghttp2_select_next_protocol.rst \
nghttp2_session_callbacks_del.rst \
nghttp2_session_callbacks_new.rst \
nghttp2_session_callbacks_set_before_frame_send_callback.rst \
nghttp2_session_callbacks_set_data_source_read_length_callback.rst \
nghttp2_session_callbacks_set_error_callback.rst \
nghttp2_session_callbacks_set_on_begin_frame_callback.rst \
nghttp2_session_callbacks_set_on_begin_headers_callback.rst \
nghttp2_session_callbacks_set_on_data_chunk_recv_callback.rst \
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback.rst \
nghttp2_session_callbacks_set_on_frame_not_send_callback.rst \
nghttp2_session_callbacks_set_on_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_frame_send_callback.rst \
nghttp2_session_callbacks_set_on_header_callback.rst \
nghttp2_session_callbacks_set_on_header_callback2.rst \
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback.rst \
nghttp2_session_callbacks_set_on_stream_close_callback.rst \
nghttp2_session_callbacks_set_pack_extension_callback.rst \
nghttp2_session_callbacks_set_recv_callback.rst \
nghttp2_session_callbacks_set_select_padding_callback.rst \
nghttp2_session_callbacks_set_send_callback.rst \
nghttp2_session_callbacks_set_send_data_callback.rst \
nghttp2_session_callbacks_set_unpack_extension_callback.rst \
nghttp2_session_client_new.rst \
nghttp2_session_client_new2.rst \
nghttp2_session_client_new3.rst \
@ -131,6 +142,7 @@ APIDOCS= \
nghttp2_stream_get_weight.rst \
nghttp2_strerror.rst \
nghttp2_submit_data.rst \
nghttp2_submit_extension.rst \
nghttp2_submit_goaway.rst \
nghttp2_submit_headers.rst \
nghttp2_submit_ping.rst \
@ -239,6 +251,7 @@ apiref.rst: \
$(APIDOCS): apiref.rst
clean-local:
[ $(srcdir) = $(builddir) ] || for i in $(RST_FILES); do [ -e $(builddir)/$$i ] && rm $(builddir)/$$i; done
-rm -f apiref.rst
-rm -f $(APIDOCS)
-rm -rf $(BUILDDIR)/*

View File

@ -8,7 +8,7 @@ _nghttpx()
_get_comp_words_by_ref cur prev
case $cur in
-*)
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --max-response-header-fields --backend-request-buffer --max-request-header-fields --backend-http2-connection-window-bits --backend-tls-session-cache-per-worker --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --no-http2-cipher-black-list --mruby-file --stream-read-timeout --tls-ticket-key-memcached --forwarded-for --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --tls-dyn-rec-warmup-threshold --no-via --ocsp-update-interval --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --request-header-field-buffer --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --read-burst --backend-ipv4 --backend-ipv6 --backend --insecure --log-level --host-rewrite --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --backend-http1-tls --syslog-facility --fastopen --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --add-forwarded --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --response-header-field-buffer --padding --stream-write-timeout --cacert --forwarded-by --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --tls-dyn-rec-idle-timeout --verify-client --read-rate --strip-incoming-forwarded ' -- "$cur" ) )
COMPREPLY=( $( compgen -W '--worker-read-rate --frontend-no-tls --frontend-http2-dump-response-header --backend-http1-connections-per-frontend --tls-ticket-key-file --verify-client-cacert --include --max-response-header-fields --backend-request-buffer --max-request-header-fields --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --no-via --tls-session-cache-memcached-cert-file --no-http2-cipher-black-list --mruby-file --stream-read-timeout --tls-ticket-key-memcached --forwarded-for --accesslog-syslog --frontend-http2-read-timeout --listener-disable-timeout --frontend-http2-connection-window-bits --ciphers --strip-incoming-x-forwarded-for --private-key-passwd-file --backend-keep-alive-timeout --backend-http-proxy-uri --backend-http1-connections-per-host --rlimit-nofile --tls-dyn-rec-warmup-threshold --tls-ticket-key-memcached-cert-file --ocsp-update-interval --forwarded-by --tls-session-cache-memcached-private-key-file --backend-write-timeout --client --tls-ticket-key-memcached-max-retry --http2-no-cookie-crumbling --worker-read-burst --client-proxy --http2-bridge --accesslog-format --errorlog-syslog --request-header-field-buffer --errorlog-file --http2-max-concurrent-streams --frontend-write-timeout --tls-ticket-key-cipher --read-burst --backend --insecure --log-level --host-rewrite --tls-session-cache-memcached-tls --tls-proto-list --backend-http2-connections-per-worker --tls-ticket-key-memcached-interval --dh-param-file --worker-frontend-connections --backend-http1-tls --syslog-facility --fastopen --no-location-rewrite --tls-ticket-key-memcached-tls --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --add-forwarded --frontend-http2-window-bits --worker-write-rate --add-request-header --backend-tls-sni-field --subcert --help --frontend-frame-debug --pid-file --frontend-http2-dump-request-header --daemon --write-rate --altsvc --user --add-x-forwarded-for --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --response-header-field-buffer --tls-ticket-key-memcached-address-family --padding --tls-session-cache-memcached-address-family --stream-write-timeout --cacert --tls-ticket-key-memcached-private-key-file --backend-address-family --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --tls-dyn-rec-idle-timeout --verify-client --read-rate --strip-incoming-forwarded ' -- "$cur" ) )
;;
*)
_filedir

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "H2LOAD" "1" "February 14, 2016" "1.8.0-DEV" "nghttp2"
.TH "H2LOAD" "1" "February 29, 2016" "1.9.0-DEV" "nghttp2"
.SH NAME
h2load \- HTTP/2 benchmarking tool
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTP" "1" "February 14, 2016" "1.8.0-DEV" "nghttp2"
.TH "NGHTTP" "1" "February 29, 2016" "1.9.0-DEV" "nghttp2"
.SH NAME
nghttp \- HTTP/2 client
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPD" "1" "February 14, 2016" "1.8.0-DEV" "nghttp2"
.TH "NGHTTPD" "1" "February 29, 2016" "1.9.0-DEV" "nghttp2"
.SH NAME
nghttpd \- HTTP/2 server
.

View File

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "NGHTTPX" "1" "February 14, 2016" "1.8.0-DEV" "nghttp2"
.TH "NGHTTPX" "1" "February 29, 2016" "1.9.0-DEV" "nghttp2"
.SH NAME
nghttpx \- HTTP/2 proxy
.
@ -39,15 +39,15 @@ A reverse proxy for HTTP/2, HTTP/1 and SPDY.
.INDENT 0.0
.TP
.B <PRIVATE_KEY>
Set path to server\(aqs private key. Required unless \fI\%\-p\fP,
\fI\%\-\-client\fP or \fI\%\-\-frontend\-no\-tls\fP are given.
Set path to server\(aqs private key. Required unless
\fI\%\-\-frontend\-no\-tls\fP are given.
.UNINDENT
.INDENT 0.0
.TP
.B <CERT>
Set path to server\(aqs certificate. Required unless \fI\%\-p\fP,
\fI\%\-\-client\fP or \fI\%\-\-frontend\-no\-tls\fP are given. To make OCSP
stapling work, this must be absolute path.
Set path to server\(aqs certificate. Required unless
\fI\%\-\-frontend\-no\-tls\fP are given. To make OCSP stapling
work, this must be an absolute path.
.UNINDENT
.SH OPTIONS
.sp
@ -55,38 +55,39 @@ The options are categorized into several groups.
.SS Connections
.INDENT 0.0
.TP
.B \-b, \-\-backend=(<HOST>,<PORT>|unix:<PATH>)[;<PATTERN>[:...]]
.B \-b, \-\-backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][;proto=<PROTO>]]
Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX
domain socket can be specified by prefixing path name
with "unix:" (e.g., unix:/var/run/backend.sock).
.sp
Optionally, if <PATTERN>s are given, the backend address
is only used if request matches the pattern. If \fI\%\-s\fP or
\fI\%\-p\fP is used, <PATTERN>s are ignored. The pattern
matching is closely designed to ServeMux in net/http
package of Go programming language. <PATTERN> consists
of path, host + path or just host. The path must start
with "\fI/\fP". If it ends with "\fI/\fP", it matches all request
path in its subtree. To deal with the request to the
directory without trailing slash, the path which ends
with "\fI/\fP" also matches the request path which only lacks
trailing \(aq\fI/\fP\(aq (e.g., path "\fI/foo/\fP" matches request path
"\fI/foo\fP"). If it does not end with "\fI/\fP", it performs exact
match against the request path. If host is given, it
performs exact match against the request host. If host
alone is given, "\fI/\fP" is appended to it, so that it
matches all request paths under the host (e.g.,
specifying "nghttp2.org" equals to "nghttp2.org/").
is only used if request matches the pattern. If
\fI\%\-\-http2\-proxy\fP is used, <PATTERN>s are ignored. The
pattern matching is closely designed to ServeMux in
net/http package of Go programming language. <PATTERN>
consists of path, host + path or just host. The path
must start with "\fI/\fP". If it ends with "\fI/\fP", it matches
all request path in its subtree. To deal with the
request to the directory without trailing slash, the
path which ends with "\fI/\fP" also matches the request path
which only lacks trailing \(aq\fI/\fP\(aq (e.g., path "\fI/foo/\fP"
matches request path "\fI/foo\fP"). If it does not end with
"\fI/\fP", it performs exact match against the request path.
If host is given, it performs exact match against the
request host. If host alone is given, "\fI/\fP" is appended
to it, so that it matches all request paths under the
host (e.g., specifying "nghttp2.org" equals to
"nghttp2.org/").
.sp
Patterns with host take precedence over patterns with
just path. Then, longer patterns take precedence over
shorter ones, breaking a tie by the order of the
appearance in the configuration.
.sp
If <PATTERN> is omitted, "\fI/\fP" is used as pattern, which
matches all request paths (catch\-all pattern). The
catch\-all backend must be given.
If <PATTERN> is omitted or empty string, "\fI/\fP" is used as
pattern, which matches all request paths (catch\-all
pattern). The catch\-all backend must be given.
.sp
When doing a match, nghttpx made some normalization to
pattern, request host and path. For host part, they are
@ -109,6 +110,15 @@ and \fI\%\-b\fP\(aq127.0.0.1,8080;www.nghttp2.org\(aq.
The backend addresses sharing same <PATTERN> are grouped
together forming load balancing group.
.sp
Optionally, backend application protocol can be
specified in <PROTO>. All that share the same <PATTERN>
must have the same <PROTO> value if it is given.
<PROTO> should be one of the following list without
quotes: "h2", "http/1.1". The default value of <PROTO>
is "http/1.1". Note that usually "h2" refers to HTTP/2
over TLS. But in this option, it may mean HTTP/2 over
cleartext TCP unless \fI\%\-\-backend\-tls\fP is used.
.sp
Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted.
@ -167,19 +177,8 @@ Accept PROXY protocol version 1 on frontend connection.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-no\-tls
Disable SSL/TLS on backend connections. For HTTP/2
backend connections, TLS is enabled by default. For
HTTP/1 backend connections, TLS is disabled by default,
and can be enabled by \fI\%\-\-backend\-http1\-tls\fP option. If
both \fI\%\-\-backend\-no\-tls\fP and \fI\%\-\-backend\-http1\-tls\fP options
are used, \fI\%\-\-backend\-no\-tls\fP has the precedence.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-http1\-tls
Enable SSL/TLS on backend HTTP/1 connections. See also
\fI\%\-\-backend\-no\-tls\fP option.
.B \-\-backend\-tls
Enable SSL/TLS on backend connections.
.UNINDENT
.SS Performance
.INDENT 0.0
@ -269,37 +268,27 @@ Default: \fB0\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-http2\-connections\-per\-worker=<N>
Set maximum number of backend HTTP/2 physical
connections per worker. If pattern is used in \fI\%\-b\fP
option, this limit is applied to each pattern group (in
other words, each pattern group can have maximum <N>
HTTP/2 connections). The default value is 0, which
means that the value is adjusted to the number of
backend addresses. If pattern is used, this adjustment
is done for each pattern group.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-http1\-connections\-per\-host=<N>
Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful
when \fI\%\-s\fP option is used. The origin host is determined
by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of
connections per frontend for default mode, use
\fI\%\-\-backend\-http1\-connections\-per\-frontend\fP\&.
.B \-\-backend\-connections\-per\-host=<N>
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per origin host.
This option is meaningful when \fI\%\-\-http2\-proxy\fP option is
used. The origin host is determined by authority
portion of request URI (or :authority header field for
HTTP/2). To limit the number of connections per
frontend for default mode, use
\fI\%\-\-backend\-connections\-per\-frontend\fP\&.
.sp
Default: \fB8\fP
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-http1\-connections\-per\-frontend=<N>
Set maximum number of backend concurrent HTTP/1
connections per frontend. This option is only used for
default mode. 0 means unlimited. To limit the number
of connections per host for HTTP/2 or SPDY proxy mode
(\-s option), use \fI\%\-\-backend\-http1\-connections\-per\-host\fP\&.
.B \-\-backend\-connections\-per\-frontend=<N>
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per frontend. This
option is only used for default mode. 0 means
unlimited. To limit the number of connections per host
with \fI\%\-\-http2\-proxy\fP option, use
\fI\%\-\-backend\-connections\-per\-host\fP\&.
.sp
Default: \fB0\fP
.UNINDENT
@ -697,20 +686,22 @@ Allow black listed cipher suite on HTTP/2 connection.
See \fI\%https://tools.ietf.org/html/rfc7540#appendix\-A\fP for
the complete HTTP/2 cipher suites black list.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-tls\-session\-cache\-per\-worker=<N>
Set the maximum number of backend TLS session cache
stored per worker.
.sp
Default: \fB10000\fP
.UNINDENT
.SS HTTP/2 and SPDY
.INDENT 0.0
.TP
.B \-c, \-\-http2\-max\-concurrent\-streams=<N>
.B \-c, \-\-frontend\-http2\-max\-concurrent\-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 and SPDY session.
frontend HTTP/2 and SPDY session.
.sp
Default: \(ga\(ga 100\(ga\(ga
.UNINDENT
.INDENT 0.0
.TP
.B \-\-backend\-http2\-max\-concurrent\-streams=<N>
Set the maximum number of the concurrent streams in one
backend HTTP/2 session. This sets maximum number of
concurrent opened pushed streams. The maximum number of
concurrent requests are set by a remote server.
.sp
Default: \fB100\fP
.UNINDENT
@ -751,7 +742,7 @@ Default: \fB16\fP
Sets the per\-connection window size of HTTP/2 backend
connection to 2**<N>\-1.
.sp
Default: \fB16\fP
Default: \fB30\fP
.UNINDENT
.INDENT 0.0
.TP
@ -772,11 +763,10 @@ protocol security.
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
HTTP/2 in default mode. In this case, server push from
backend session is relayed to frontend, and server push
via Link header field is also supported. SPDY frontend
does not support server push.
.UNINDENT
.SS Mode
.INDENT 0.0
@ -785,39 +775,13 @@ support server push.
Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. If
\fI\%\-\-frontend\-no\-tls\fP is used, accept HTTP/2 and HTTP/1.1.
The incoming HTTP/1.1 connection can be upgraded to
HTTP/2 through HTTP Upgrade. The protocol to the
backend is HTTP/1.1.
HTTP/2 through HTTP Upgrade.
.UNINDENT
.INDENT 0.0
.TP
.B \-s, \-\-http2\-proxy
Like default mode, but enable secure proxy mode.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-http2\-bridge
Like default mode, but communicate with the backend in
HTTP/2 over SSL/TLS. Thus the incoming all connections
are converted to HTTP/2 connection and relayed to the
backend. See \fI\%\-\-backend\-http\-proxy\-uri\fP option if you are
behind the proxy and want to connect to the outside
HTTP/2 proxy.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-client
Accept HTTP/2 and HTTP/1.1 without SSL/TLS. The
incoming HTTP/1.1 connection can be upgraded to HTTP/2
connection through HTTP Upgrade. The protocol to the
backend is HTTP/2. To use nghttpx as a forward proxy,
use \fI\%\-p\fP option instead.
.UNINDENT
.INDENT 0.0
.TP
.B \-p, \-\-client\-proxy
Like \fI\%\-\-client\fP option, but it also requires the request
path from frontend must be an absolute URI, suitable for
use as a forward proxy.
Like default mode, but enable forward proxy. This is so
called HTTP/2 proxy mode.
.UNINDENT
.SS Logging
.INDENT 0.0
@ -983,18 +947,16 @@ is received, it is left unaltered.
.INDENT 0.0
.TP
.B \-\-no\-location\-rewrite
Don\(aqt rewrite location header field on \fI\%\-\-http2\-bridge\fP,
\fI\%\-\-client\fP and default mode. For \fI\%\-\-http2\-proxy\fP and
\fI\%\-\-client\-proxy\fP mode, location header field will not be
altered regardless of this option.
Don\(aqt rewrite location header field in default mode.
When \fI\%\-\-http2\-proxy\fP is used, location header field will
not be altered regardless of this option.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-host\-rewrite
Rewrite host and :authority header fields on
\fI\%\-\-http2\-bridge\fP, \fI\%\-\-client\fP and default mode. For
\fI\%\-\-http2\-proxy\fP and \fI\%\-\-client\-proxy\fP mode, these headers
will not be altered regardless of this option.
Rewrite host and :authority header fields in default
mode. When \fI\%\-\-http2\-proxy\fP is used, these headers will
not be altered regardless of this option.
.UNINDENT
.INDENT 0.0
.TP
@ -1266,12 +1228,12 @@ associated stream\(aqs status code must be 200.
This limitation may be loosened in the future release.
.sp
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies \fI\%\-\-http2\-bridge\fP or \fI\%\-\-client\fP).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
HTTP/2 in default mode. In this case, in addition to server push via
Link header field, server push from backend is forwarded to frontend
HTTP/2 session.
.sp
HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP or
\fI\%\-\-client\-proxy\fP is used.
HTTP/2 server push will be disabled if \fI\%\-\-http2\-proxy\fP is
used.
.SH UNIX DOMAIN SOCKET
.sp
nghttpx supports UNIX domain socket with a filename for both frontend

View File

@ -19,14 +19,14 @@ A reverse proxy for HTTP/2, HTTP/1 and SPDY.
.. describe:: <PRIVATE_KEY>
Set path to server's private key. Required unless :option:`-p`\,
:option:`--client` or :option:`\--frontend-no-tls` are given.
Set path to server's private key. Required unless
:option:`--frontend-no-tls` are given.
.. describe:: <CERT>
Set path to server's certificate. Required unless :option:`-p`\,
:option:`--client` or :option:`\--frontend-no-tls` are given. To make OCSP
stapling work, this must be absolute path.
Set path to server's certificate. Required unless
:option:`--frontend-no-tls` are given. To make OCSP stapling
work, this must be an absolute path.
OPTIONS
@ -37,7 +37,7 @@ The options are categorized into several groups.
Connections
~~~~~~~~~~~
.. option:: -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;<PATTERN>[:...]]
.. option:: -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][;proto=<PROTO>]]
Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX
@ -45,31 +45,32 @@ Connections
with "unix:" (e.g., unix:/var/run/backend.sock).
Optionally, if <PATTERN>s are given, the backend address
is only used if request matches the pattern. If :option:`-s` or
:option:`-p` is used, <PATTERN>s are ignored. The pattern
matching is closely designed to ServeMux in net/http
package of Go programming language. <PATTERN> consists
of path, host + path or just host. The path must start
with "*/*". If it ends with "*/*", it matches all request
path in its subtree. To deal with the request to the
directory without trailing slash, the path which ends
with "*/*" also matches the request path which only lacks
trailing '*/*' (e.g., path "*/foo/*" matches request path
"*/foo*"). If it does not end with "*/*", it performs exact
match against the request path. If host is given, it
performs exact match against the request host. If host
alone is given, "*/*" is appended to it, so that it
matches all request paths under the host (e.g.,
specifying "nghttp2.org" equals to "nghttp2.org/").
is only used if request matches the pattern. If
:option:`--http2-proxy` is used, <PATTERN>s are ignored. The
pattern matching is closely designed to ServeMux in
net/http package of Go programming language. <PATTERN>
consists of path, host + path or just host. The path
must start with "*/*". If it ends with "*/*", it matches
all request path in its subtree. To deal with the
request to the directory without trailing slash, the
path which ends with "*/*" also matches the request path
which only lacks trailing '*/*' (e.g., path "*/foo/*"
matches request path "*/foo*"). If it does not end with
"*/*", it performs exact match against the request path.
If host is given, it performs exact match against the
request host. If host alone is given, "*/*" is appended
to it, so that it matches all request paths under the
host (e.g., specifying "nghttp2.org" equals to
"nghttp2.org/").
Patterns with host take precedence over patterns with
just path. Then, longer patterns take precedence over
shorter ones, breaking a tie by the order of the
appearance in the configuration.
If <PATTERN> is omitted, "*/*" is used as pattern, which
matches all request paths (catch-all pattern). The
catch-all backend must be given.
If <PATTERN> is omitted or empty string, "*/*" is used as
pattern, which matches all request paths (catch-all
pattern). The catch-all backend must be given.
When doing a match, nghttpx made some normalization to
pattern, request host and path. For host part, they are
@ -92,6 +93,15 @@ Connections
The backend addresses sharing same <PATTERN> are grouped
together forming load balancing group.
Optionally, backend application protocol can be
specified in <PROTO>. All that share the same <PATTERN>
must have the same <PROTO> value if it is given.
<PROTO> should be one of the following list without
quotes: "h2", "http/1.1". The default value of <PROTO>
is "http/1.1". Note that usually "h2" refers to HTTP/2
over TLS. But in this option, it may mean HTTP/2 over
cleartext TCP unless :option:`--backend-tls` is used.
Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted.
@ -144,19 +154,9 @@ Connections
Accept PROXY protocol version 1 on frontend connection.
.. option:: --backend-no-tls
.. option:: --backend-tls
Disable SSL/TLS on backend connections. For HTTP/2
backend connections, TLS is enabled by default. For
HTTP/1 backend connections, TLS is disabled by default,
and can be enabled by :option:`--backend-http1-tls` option. If
both :option:`--backend-no-tls` and :option:`\--backend-http1-tls` options
are used, :option:`--backend-no-tls` has the precedence.
.. option:: --backend-http1-tls
Enable SSL/TLS on backend HTTP/1 connections. See also
:option:`--backend-no-tls` option.
Enable SSL/TLS on backend connections.
Performance
@ -237,36 +237,27 @@ Performance
Default: ``0``
.. option:: --backend-http2-connections-per-worker=<N>
.. option:: --backend-connections-per-host=<N>
Set maximum number of backend HTTP/2 physical
connections per worker. If pattern is used in :option:`-b`
option, this limit is applied to each pattern group (in
other words, each pattern group can have maximum <N>
HTTP/2 connections). The default value is 0, which
means that the value is adjusted to the number of
backend addresses. If pattern is used, this adjustment
is done for each pattern group.
.. option:: --backend-http1-connections-per-host=<N>
Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful
when :option:`-s` option is used. The origin host is determined
by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of
connections per frontend for default mode, use
:option:`--backend-http1-connections-per-frontend`\.
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per origin host.
This option is meaningful when :option:`--http2-proxy` option is
used. The origin host is determined by authority
portion of request URI (or :authority header field for
HTTP/2). To limit the number of connections per
frontend for default mode, use
:option:`--backend-connections-per-frontend`\.
Default: ``8``
.. option:: --backend-http1-connections-per-frontend=<N>
.. option:: --backend-connections-per-frontend=<N>
Set maximum number of backend concurrent HTTP/1
connections per frontend. This option is only used for
default mode. 0 means unlimited. To limit the number
of connections per host for HTTP/2 or SPDY proxy mode
(-s option), use :option:`--backend-http1-connections-per-host`\.
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per frontend. This
option is only used for default mode. 0 means
unlimited. To limit the number of connections per host
with :option:`--http2-proxy` option, use
:option:`--backend-connections-per-host`\.
Default: ``0``
@ -622,21 +613,23 @@ SSL/TLS
See https://tools.ietf.org/html/rfc7540#appendix-A for
the complete HTTP/2 cipher suites black list.
.. option:: --backend-tls-session-cache-per-worker=<N>
Set the maximum number of backend TLS session cache
stored per worker.
Default: ``10000``
HTTP/2 and SPDY
~~~~~~~~~~~~~~~
.. option:: -c, --http2-max-concurrent-streams=<N>
.. option:: -c, --frontend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 and SPDY session.
frontend HTTP/2 and SPDY session.
Default: `` 100``
.. option:: --backend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
backend HTTP/2 session. This sets maximum number of
concurrent opened pushed streams. The maximum number of
concurrent requests are set by a remote server.
Default: ``100``
@ -672,7 +665,7 @@ HTTP/2 and SPDY
Sets the per-connection window size of HTTP/2 backend
connection to 2\*\*<N>-1.
Default: ``16``
Default: ``30``
.. option:: --http2-no-cookie-crumbling
@ -690,11 +683,10 @@ HTTP/2 and SPDY
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`\--client` mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
HTTP/2 in default mode. In this case, server push from
backend session is relayed to frontend, and server push
via Link header field is also supported. SPDY frontend
does not support server push.
Mode
@ -706,35 +698,12 @@ Mode
Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. If
:option:`--frontend-no-tls` is used, accept HTTP/2 and HTTP/1.1.
The incoming HTTP/1.1 connection can be upgraded to
HTTP/2 through HTTP Upgrade. The protocol to the
backend is HTTP/1.1.
HTTP/2 through HTTP Upgrade.
.. option:: -s, --http2-proxy
Like default mode, but enable secure proxy mode.
.. option:: --http2-bridge
Like default mode, but communicate with the backend in
HTTP/2 over SSL/TLS. Thus the incoming all connections
are converted to HTTP/2 connection and relayed to the
backend. See :option:`--backend-http-proxy-uri` option if you are
behind the proxy and want to connect to the outside
HTTP/2 proxy.
.. option:: --client
Accept HTTP/2 and HTTP/1.1 without SSL/TLS. The
incoming HTTP/1.1 connection can be upgraded to HTTP/2
connection through HTTP Upgrade. The protocol to the
backend is HTTP/2. To use nghttpx as a forward proxy,
use :option:`-p` option instead.
.. option:: -p, --client-proxy
Like :option:`--client` option, but it also requires the request
path from frontend must be an absolute URI, suitable for
use as a forward proxy.
Like default mode, but enable forward proxy. This is so
called HTTP/2 proxy mode.
Logging
@ -875,17 +844,15 @@ HTTP
.. option:: --no-location-rewrite
Don't rewrite location header field on :option:`--http2-bridge`\,
:option:`--client` and default mode. For :option:`\--http2-proxy` and
:option:`--client-proxy` mode, location header field will not be
altered regardless of this option.
Don't rewrite location header field in default mode.
When :option:`--http2-proxy` is used, location header field will
not be altered regardless of this option.
.. option:: --host-rewrite
Rewrite host and :authority header fields on
:option:`--http2-bridge`\, :option:`--client` and default mode. For
:option:`--http2-proxy` and :option:`\--client-proxy` mode, these headers
will not be altered regardless of this option.
Rewrite host and :authority header fields in default
mode. When :option:`--http2-proxy` is used, these headers will
not be altered regardless of this option.
.. option:: --altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
@ -1133,12 +1100,12 @@ Currently, the following restriction is applied for server push:
This limitation may be loosened in the future release.
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
HTTP/2 in default mode. In this case, in addition to server push via
Link header field, server push from backend is forwarded to frontend
HTTP/2 session.
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
:option:`--client-proxy` is used.
HTTP/2 server push will be disabled if :option:`--http2-proxy` is
used.
UNIX DOMAIN SOCKET
------------------

View File

@ -98,12 +98,12 @@ Currently, the following restriction is applied for server push:
This limitation may be loosened in the future release.
nghttpx also supports server push if both frontend and backend are
HTTP/2 (which implies :option:`--http2-bridge` or :option:`--client`).
In this case, in addition to server push via Link header field, server
push from backend is relayed to frontend HTTP/2 session.
HTTP/2 in default mode. In this case, in addition to server push via
Link header field, server push from backend is forwarded to frontend
HTTP/2 session.
HTTP/2 server push will be disabled if :option:`--http2-proxy` or
:option:`--client-proxy` is used.
HTTP/2 server push will be disabled if :option:`--http2-proxy` is
used.
UNIX DOMAIN SOCKET
------------------

View File

@ -1,6 +1,62 @@
Programmers' Guide
==================
Architecture
------------
The most notable point in nghttp2 library architecture is it does not
perform any I/O. nghttp2 only performs HTTP/2 protocol stuff based on
input byte strings. It will calls callback functions set by
applications while processing input. The output of nghttp2 is just
byte string. An application is responsible to send these output to
the remote peer. The callback functions may be called while producing
output.
Not doing I/O makes embedding nghttp2 library in the existing code
base very easy. Usually, the existing applications have its own I/O
event loops. It is very hard to use nghttp2 in that situation if
nghttp2 does its own I/O. It also makes light weight language wrapper
for nghttp2 easy with the same reason. The down side is that an
application author has to write more code to write complete
application using nghttp2. This is especially true for simple "toy"
application. For the real applications, however, this is not the
case. This is because you probably want to support HTTP/1 which
nghttp2 does not provide, and to do that, you will need to write your
own HTTP/1 stack or use existing third-party library, and bind them
together with nghttp2 and I/O event loop. In this point, not
performing I/O in nghttp2 has more point than doing it.
The primary object that an application uses is :type:`nghttp2_session`
object, which is opaque struct and its details are hidden in order to
ensure the upgrading its internal architecture without breaking the
backward compatibility. An application can set callbacks to
:type:`nghttp2_session` object through the dedicated object and
functions, and it also interacts with it via many API function calls.
An application can create as many :type:`nghttp2_session` object as it
wants. But single :type:`nghttp2_session` object must be used by a
single thread at the same time. This is not so hard to enforce since
most event-based architecture applicatons use is single thread per
core, and handling one connection I/O is done by single thread.
To feed input to :type:`nghttp2_session` object, one can use
`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` functions.
They behave similarly, and the difference is that
`nghttp2_session_recv()` will use :type:`nghttp2_read_callback` to get
input. On the other hand, `nghttp2_session_mem_recv()` will take
input as its parameter. If in doubt, use `nghttp2_session_mem_recv()`
since it is simpler, and could be faster since it avoids calling
callback function.
To get output from :type:`nghttp2_session` object, one can use
`nghttp2_session_send()` or `nghttp2_session_mem_send()`. The
difference between them is that the former uses
:type:`nghttp2_send_callback` to pass output to an application. On
the other hand, the latter returns the output to the caller. If in
doubt, use `nghttp2_session_mem_send()` since it is simpler. But
`nghttp2_session_send()` might be easier to use if the output buffer
an application has is fixed sized.
Includes
--------

View File

@ -1,18 +1,21 @@
.. program:: h2load
h2load - HTTP/2 benchmarking tool - HOW-TO
==========================================
h2load is benchmarking tool for HTTP/2 and HTTP/1.1. If built with
spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also
supports SPDY protocol. It supports SSL/TLS and clear text for all
supported protocols.
:doc:`h2load.1` is benchmarking tool for HTTP/2 and HTTP/1.1. If
built with spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it
also supports SPDY protocol. It supports SSL/TLS and clear text for
all supported protocols.
Compiling from source
---------------------
``h2load`` is compiled alongside ``nghttp2`` and requires that the
``--enable-apps`` flag is passed to ``./configure`` and `required dependencies
<https://github.com/tatsuhiro-t/nghttp2#requirements>`_ are available during
compilation. For details on compiling, see `nghttp2: Building from Git
h2load is compiled alongside nghttp2 and requires that the
``--enable-apps`` flag is passed to ``./configure`` and `required
dependencies <https://github.com/tatsuhiro-t/nghttp2#requirements>`_
are available during compilation. For details on compiling, see
`nghttp2: Building from Git
<https://github.com/tatsuhiro-t/nghttp2#building-from-git>`_.
Basic Usage
@ -20,23 +23,21 @@ Basic Usage
In order to set benchmark settings, specify following 3 options.
``-n``
:option:`-n`
The number of total requests. Default: 1
``-c``
:option:`-c`
The number of concurrent clients. Default: 1
``-m``
The max concurrent streams to issue per client.
If ``auto`` is given, the number of given URIs is used.
Default: ``auto``
:option:`-m`
The max concurrent streams to issue per client. Default: 1
For SSL/TLS connection, the protocol will be negotiated via ALPN/NPN.
You can set specific protocols in ``--npn-list`` option. For
You can set specific protocols in :option:`--npn-list` option. For
cleartext connection, the default protocol is HTTP/2. To change the
protocol in cleartext connection, use ``--no-tls-proto`` option. For
convenience, ``--h1`` option forces HTTP/1.1 for both cleartext and
SSL/TLS connections.
protocol in cleartext connection, use :option:`--no-tls-proto` option.
For convenience, :option:`--h1` option forces HTTP/1.1 for both
cleartext and SSL/TLS connections.
Here is a command-line to perform benchmark to URI \https://localhost
using total 100000 requests, 100 concurrent clients and 10 max
@ -71,11 +72,11 @@ benchmarking results. By default, h2load uses large enough flow
control window, which effectively disables flow control. To adjust
receiver flow control window size, there are following options:
``-w``
:option:`-w`
Sets the stream level initial window size to
(2**<N>)-1. For SPDY, 2**<N> is used instead.
``-W``
:option:`-W`
Sets the connection level initial window size to
(2**<N>)-1. For SPDY, if <N> is strictly less
than 16, this option is ignored. Otherwise
@ -85,17 +86,17 @@ Multi-Threading
---------------
Sometimes benchmarking client itself becomes a bottleneck. To remedy
this situation, use ``-t`` option to specify the number of native
this situation, use :option:`-t` option to specify the number of native
thread to use.
``-t``
:option:`-t`
The number of native threads. Default: 1
Selecting protocol for clear text
---------------------------------
By default, if \http:// URI is given, HTTP/2 protocol is used. To
change the protocol to use for clear text, use ``-p`` option.
change the protocol to use for clear text, use :option:`-p` option.
Multiple URIs
-------------
@ -106,3 +107,12 @@ If multiple URIs are specified, they are used in round robin manner.
Please note that h2load uses scheme, host and port in the first URI
and ignores those parts in the rest of the URIs.
UNIX domain socket
------------------
To request against UNIX domain socket, use :option:`--base-uri`, and
specify ``unix:`` followed by the path to UNIX domain socket. For
example, if UNIX domain socket is ``/tmp/nghttpx.sock``, use
``--base-uri=unix:/tmp/nghttpx.sock``. h2load uses scheme, host and
port in the first URI in command-line or input file.

View File

@ -1,44 +1,49 @@
.. program:: nghttpx
nghttpx - HTTP/2 proxy - HOW-TO
===============================
nghttpx is a proxy translating protocols between HTTP/2 and other
protocols (e.g., HTTP/1, SPDY). It operates in several modes and each
mode may require additional programs to work with. This article
describes each operation mode and explains the intended use-cases. It
also covers some useful options later.
:doc:`nghttpx.1` is a proxy translating protocols between HTTP/2 and
other protocols (e.g., HTTP/1, SPDY). It operates in several modes
and each mode may require additional programs to work with. This
article describes each operation mode and explains the intended
use-cases. It also covers some useful options later.
Default mode
------------
If nghttpx is invoked without any ``-s``, ``-p`` and ``--client``, it
operates in default mode. In this mode, nghttpx frontend listens for
HTTP/2 requests and translates them to HTTP/1 requests. Thus it works
as reverse proxy (gateway) for HTTP/2 clients to HTTP/1 web server.
This is also known as "HTTP/2 router". HTTP/1 requests are also
supported in frontend as a fallback. If nghttpx is linked with
spdylay library and frontend connection is SSL/TLS, the frontend also
supports SPDY protocol.
If nghttpx is invoked without :option:`--http2-proxy`, it operates in
default mode. In this mode, it works as reverse proxy (gateway) for
both HTTP/2 and HTTP/1 clients to backend servers. This is also known
as "HTTP/2 router". If nghttpx is linked with spdylay library and
frontend connection is SSL/TLS, the frontend also supports SPDY
protocol.
By default, this mode's frontend connection is encrypted using
SSL/TLS. So server's private key and certificate must be supplied to
the command line (or through configuration file). In this case, the
frontend protocol selection will be done via ALPN or NPN.
By default, frontend connection is encrypted using SSL/TLS. So
server's private key and certificate must be supplied to the command
line (or through configuration file). In this case, the frontend
protocol selection will be done via ALPN or NPN.
With ``--frontend-no-tls`` option, user can turn off SSL/TLS in
With :option:`--frontend-no-tls` option, user can turn off SSL/TLS in
frontend connection. In this case, SPDY protocol is not available
even if spdylay library is liked to nghttpx. HTTP/2 and HTTP/1 are
available on the frontend and a HTTP/1 connection can be upgraded to
available on the frontend, and an HTTP/1 connection can be upgraded to
HTTP/2 using HTTP Upgrade. Starting HTTP/2 connection by sending
HTTP/2 connection preface is also supported.
By default, backend HTTP/1 connections are not encrypted. To enable
TLS on HTTP/1 backend connections, use ``--backend-http1-tls`` option.
This applies to all mode whose backend connections are HTTP/1.
By default, backend connections are not encrypted. To enable TLS
encryption on backend connections, use :option:`--backend-tls` option.
Using patterns and ``proto`` keyword in :option:`--backend` option,
backend application protocol can be specified per host/request path
pattern. It means that you can use both HTTP/2 and HTTP/1 in backend
connections at the same time. Note that default backend protocol is
HTTP/1.1. To use HTTP/2 in backend, you have to specify ``h2`` in
``proto`` keyword in :option:`--backend` explicitly.
The backend is supposed to be HTTP/1 Web server. For example, to make
The backend is supposed to be Web server. For example, to make
nghttpx listen to encrypted HTTP/2 requests at port 8443, and a
backend HTTP/1 web server is configured to listen to HTTP/1 request at
port 8080 in the same host, run nghttpx command-line like this::
backend Web server is configured to listen to HTTP request at port
8080 in the same host, run nghttpx command-line like this::
$ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
@ -50,30 +55,36 @@ example, you can send GET request to the server using nghttp::
HTTP/2 proxy mode
-----------------
If nghttpx is invoked with ``-s`` option, it operates in HTTP/2 proxy
mode. The supported protocols in frontend and backend connections are
the same in `default mode`_. The difference is that this mode acts
like forward proxy and assumes the backend is HTTP/1 proxy server
(e.g., squid, traffic server). So HTTP/1 request must include
absolute URI in request line.
If nghttpx is invoked with :option:`--http2-proxy` (or its shorthand
:option:`-s`) option, it operates in HTTP/2 proxy mode. The supported
protocols in frontend and backend connections are the same in `default
mode`_. The difference is that this mode acts like forward proxy and
assumes the backend is HTTP proxy server (e.g., Squid, Apache Traffic
Server). HTTP/1 request must include absolute URI in request line.
By default, frontend connection is encrypted. So this mode is also
called secure proxy. If nghttpx is linked with spdylay, it supports
SPDY protocols and it works as so called SPDY proxy.
With ``--frontend-no-tls`` option, SSL/TLS is turned off in frontend
connection, so the connection gets insecure.
With :option:`--frontend-no-tls` option, SSL/TLS is turned off in
frontend connection, so the connection gets insecure.
The backend must be HTTP/1 proxy server. nghttpx supports multiple
backend server addresses. It translates incoming requests to HTTP/1
The backend must be HTTP proxy server. nghttpx supports multiple
backend server addresses. It translates incoming requests to HTTP
request to backend server. The backend server performs real proxy
work for each request, for example, dispatching requests to the origin
server and caching contents.
The backend connection is not encrypted by default. To enable
encryption, use :option:`--backend-tls` option. The default backend
protocol is HTTP/1.1. To use HTTP/2 in backend connection, use
:option:`--backend` option, and specify ``h2`` in ``proto`` keyword
explicitly.
For example, to make nghttpx listen to encrypted HTTP/2 requests at
port 8443, and a backend HTTP/1 proxy server is configured to listen
to HTTP/1 request at port 8080 in the same host, run nghttpx
command-line like this::
port 8443, and a backend HTTP proxy server is configured to listen to
HTTP/1 request at port 8080 in the same host, run nghttpx command-line
like this::
$ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
@ -96,7 +107,9 @@ Chromium require valid certificate for secure proxy.
For Firefox, open Preference window and select Advanced then click
Network tab. Clicking Connection Settings button will show the
dialog. Select "Automatic proxy configuration URL" and enter the path
to proxy.pac file, something like this::
to proxy.pac file, something like this:
.. code-block:: text
file:///path/to/proxy.pac
@ -112,136 +125,51 @@ configuration items to edit::
CONFIG proxy.config.url_remap.remap_required INT 0
Consult Traffic server `documentation
<https://docs.trafficserver.apache.org/en/latest/admin/forward-proxy.en.html>`_
<http://trafficserver.readthedocs.org/en/latest/admin-guide/configuration/transparent-forward-proxying.en.html>`_
to know how to configure traffic server as forward proxy and its
security implications.
Client mode
-----------
Disable frontend SSL/TLS
------------------------
If nghttpx is invoked with ``--client`` option, it operates in client
mode. In this mode, nghttpx listens for plain, unencrypted HTTP/2 and
HTTP/1 requests and translates them to encrypted HTTP/2 requests to
the backend. User cannot enable SSL/TLS in frontend connection.
The frontend connections are encrypted with SSL/TLS by default. To
turn off SSL/TLS, use :option:`--frontend-no-tls` option. If this
option is used, the private key and certificate are not required to
run nghttpx.
HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP
Upgrade. To disable SSL/TLS in backend connection, use
``--backend-no-tls`` option.
Enable backend SSL/TLS
----------------------
By default, the number of backend HTTP/2 connections per worker
(thread) is determined by number of ``-b`` option. To adjust this
value, use ``--backend-http2-connections-per-worker`` option.
The backend connections are not encrypted by default. To enable
SSL/TLS encryption, :option:`--backend-tls` option.
The backend server is supporsed to be a HTTP/2 web server (e.g.,
nghttpd). The one use-case of this mode is utilize existing HTTP/1
clients to test HTTP/2 deployment. Suppose that HTTP/2 web server
listens to port 80 without encryption. Then run nghttpx as client
mode to access to that web server::
Enable SSL/TLS on memcached connection
--------------------------------------
$ nghttpx --client -f127.0.0.1,8080 -b127.0.0.1,80 --backend-no-tls
By default, memcached connection is not encrypted. To enable
encryption, use :option:`--tls-ticket-key-memcached-tls` for TLS
ticket key, and use :option:`--tls-session-cache-memcached-tls` for
TLS session cache.
.. note::
Specifying additional server certificates
-----------------------------------------
You may need ``-k`` option if HTTP/2 server enables SSL/TLS and
its certificate is self-signed. But please note that it is
insecure.
Then you can use curl to access HTTP/2 server via nghttpx::
$ curl http://localhost:8080/
Client proxy mode
-----------------
If nghttpx is invoked with ``-p`` option, it operates in client proxy
mode. This mode behaves like `client mode`_, but it works like
forward proxy. So HTTP/1 request must include absolute URI in request
line.
HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP
Upgrade. To disable SSL/TLS in backend connection, use
``--backend-no-tls`` option.
By default, the number of backend HTTP/2 connections per worker
(thread) is determined by number of ``-b`` option. To adjust this
value, use ``--backend-http2-connections-per-worker`` option.
The backend server must be a HTTP/2 proxy. You can use nghttpx in
`HTTP/2 proxy mode`_ as backend server. The one use-case of this mode
is utilize existing HTTP/1 clients to test HTTP/2 connections between
2 proxies. The another use-case is use this mode to aggregate local
HTTP/1 connections to one HTTP/2 backend encrypted connection. This
makes HTTP/1 clients which does not support secure proxy can use
secure HTTP/2 proxy via nghttpx client mode.
Suppose that HTTP/2 proxy listens to port 8443, just like we saw in
`HTTP/2 proxy mode`_. To run nghttpx in client proxy mode to access
that server, invoke nghttpx like this::
$ nghttpx -p -f127.0.0.1,8080 -b127.0.0.1,8443
.. note::
You may need ``-k`` option if HTTP/2 server's certificate is
self-signed. But please note that it is insecure.
Then you can use curl to issue HTTP request via HTTP/2 proxy::
$ curl --http-proxy=http://localhost:8080 http://www.google.com/
You can configure web browser to use localhost:8080 as forward
proxy.
HTTP/2 bridge mode
------------------
If nghttpx is invoked with ``--http2-bridge`` option, it operates in
HTTP/2 bridge mode. The supported protocols in frontend connections
are the same in `default mode`_. The protocol in backend is HTTP/2
only.
With ``--frontend-no-tls`` option, SSL/TLS is turned off in frontend
connection, so the connection gets insecure. To disable SSL/TLS in
backend connection, use ``--backend-no-tls`` option.
By default, the number of backend HTTP/2 connections per worker
(thread) is determined by number of ``-b`` option. To adjust this
value, use ``--backend-http2-connections-per-worker`` option.
The backend server is supporsed to be a HTTP/2 web server or HTTP/2
proxy. If backend server is HTTP/2 proxy, use
``--no-location-rewrite`` and ``--no-host-rewrite`` options to disable
rewriting location, host and :authority header field.
The use-case of this mode is aggregate the incoming connections to one
HTTP/2 connection. One backend HTTP/2 connection is created per
worker (thread).
Disable SSL/TLS
---------------
In `default mode`_, `HTTP/2 proxy mode`_ and `HTTP/2 bridge mode`_,
frontend connections are encrypted with SSL/TLS by default. To turn
off SSL/TLS, use ``--frontend-no-tls`` option. If this option is
used, the private key and certificate are not required to run nghttpx.
In `client mode`_, `client proxy mode`_ and `HTTP/2 bridge mode`_,
backend connections are encrypted with SSL/TLS by default. To turn
off SSL/TLS, use ``--backend-no-tls`` option.
nghttpx accepts additional server private key and certificate pairs
using :option:`--subcert` option. It can be used multiple times.
Specifying additional CA certificate
------------------------------------
By default, nghttpx tries to read CA certificate from system. But
depending on the system you use, this may fail or is not supported.
To specify CA certificate manually, use ``--cacert`` option. The
specified file must be PEM format and can contain multiple
To specify CA certificate manually, use :option:`--cacert` option.
The specified file must be PEM format and can contain multiple
certificates.
By default, nghttpx validates server's certificate. If you want to
turn off this validation, knowing this is really insecure and what you
are doing, you can use ``-k`` option to disable certificate
validation.
are doing, you can use :option:`--insecure` option to disable
certificate validation.
Read/write rate limit
---------------------
@ -250,9 +178,9 @@ nghttpx supports transfer rate limiting on frontend connections. You
can do rate limit per frontend connection for reading and writing
individually.
To perform rate limit for reading, use ``--read-rate`` and
``--read-burst`` options. For writing, use ``--write-rate`` and
``--write-burst``.
To perform rate limit for reading, use :option:`--read-rate` and
:option:`--read-burst` options. For writing, use
:option:`--write-rate` and :option:`--write-burst`.
Please note that rate limit is performed on top of TCP and nothing to
do with HTTP/2 flow control.
@ -294,14 +222,92 @@ Re-opening log files
When rotating log files, it is desirable to re-open log files after
log rotation daemon renamed existing log files. To tell nghttpx to
re-open log files, send USR1 signal to nghttpx process. It will
re-open files specified by ``--accesslog-file`` and
``--errorlog-file`` options.
re-open files specified by :option:`--accesslog-file` and
:option:`--errorlog-file` options.
Multiple backend addresses
--------------------------
nghttpx supports multiple backend addresses. To specify them, just
use ``-b`` option repeatedly. For example, to use backend1:8080 and
backend2:8080, use command-line like this: ``-bbackend1,8080
-bbackend2,8080``. For HTTP/2 backend, see also
``--backend-http2-connections-per-worker`` option.
use :option:`--backend` (or its shorthand :option:`-b`) option
repeatedly. For example, to use ``192.168.0.10:8080`` and
``192.168.0.11:8080``, use command-line like this:
``-b192.168.0.10,8080 -b192.168.0.11,8080``. In configuration file,
this looks like:
.. code-block:: text
backend=192.168.0.10,8080
backend=192.168.0.11,8008
nghttpx can route request to different backend according to request
host and path. For example, to route request destined to host
``doc.example.com`` to backend server ``docserv:3000``, you can write
like so:
.. code-block:: text
backend=docserv,3000;doc.example.com/
When you write this option in command-line, you should enclose
argument with single or double quotes, since the character ``;`` has a
special meaning in shell.
To route, request to request path whose prefix is ``/foo`` to backend
server ``[::1]:8080``, you can write like so:
.. code-block:: text
backend=::1,8080;/foo
Of course, you can specify both host and request path at the same
time.
One important thing you have to remember is that we have to specify
default routing pattern for so called "catch all" pattern. To write
"catch all" pattern, just specify backend server address, without
pattern.
Usually, host is the value of ``Host`` header field. In HTTP/2, the
value of ``:authority`` pseudo header field is used.
When you write multiple backend addresses sharing the same routing
pattern, they are used as load balancing. For example, to use 2
servers ``serv1:3000`` and ``serv2:3000`` for request host
``example.com`` and path ``/myservice``, you can write like so:
.. code-block:: text
backend=serv1,3000;example.com/myservice
backend=serv2,3000;example.com/myservice
You can also specify backend application protocol in
:option:`--backend` option using ``proto`` keyword after pattern.
Utilizing this allows ngttpx to route certain request to HTTP/2, other
requests to HTTP/1. For example, to route requests to ``/ws/`` in
backend HTTP/1.1 connection, and use backend HTTP/2 for other
requests, do this:
.. code-block:: text
backend=serv1,3000;/;proto=h2
backend=serv1,3000;/ws/;proto=http/1.1
Note that the backends share the same pattern must have the same
backend protocol. The default backend protocol is HTTP/1.1.
Deprecated modes
----------------
As of nghttpx 1.9.0, ``--http2-bridge``, ``--client`` and
``--client-proxy`` options were removed. These functionality can be
used using combinations of options.
* ``--http2-bridge``: Use ``--backend='<ADDR>,<PORT>;;proto=h2'``, and
``--backend-tls``.
* ``--client``: Use ``--frontend-no-tls``,
``--backend='<ADDR>,<PORT>;;proto=h2'``, and ``--backend-tls``.
* ``--client-proxy``: Use ``--http2-proxy``, ``--frontend-no-tls``,
``--backend='<ADDR>,<PORT>;;proto=h2'``, and ``--backend-tls``.

View File

@ -106,7 +106,7 @@ def gen_enum():
def gen_index_header():
print '''\
static inline int lookup_token(const uint8_t *name, size_t namelen) {
static inline int32_t lookup_token(const uint8_t *name, size_t namelen) {
switch (namelen) {'''
b = build_header(HEADERS)
for size in sorted(b.keys()):

View File

@ -115,7 +115,6 @@ OPTIONS = [
"max-header-fields",
"no-http2-cipher-black-list",
"backend-http1-tls",
"backend-tls-session-cache-per-worker",
"tls-session-cache-memcached-cert-file",
"tls-session-cache-memcached-private-key-file",
"tls-session-cache-memcached-address-family",
@ -123,7 +122,12 @@ OPTIONS = [
"tls-ticket-key-memcached-cert-file",
"tls-ticket-key-memcached-private-key-file",
"tls-ticket-key-memcached-address-family",
"backend-address-family"
"backend-address-family",
"frontend-http2-max-concurrent-streams",
"backend-http2-max-concurrent-streams",
"backend-connections-per-frontend",
"backend-tls",
"backend-connections-per-host"
]
LOGVARS = [

View File

@ -40,12 +40,12 @@ EXTRA_DIST = \
req-return.rb \
resp-return.rb
itprep-local:
itprep:
go get -d -v golang.org/x/net/http2
go get -d -v github.com/tatsuhiro-t/go-nghttp2
go get -d -v github.com/tatsuhiro-t/spdy
go get -d -v golang.org/x/net/websocket
it-local:
it:
for i in $(GO_FILES); do [ -e $(builddir)/$$i ] || cp $(srcdir)/$$i $(builddir); done
sh setenv go test -v

View File

@ -6,10 +6,10 @@ import (
"crypto/tls"
"errors"
"fmt"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"github.com/tatsuhiro-t/go-nghttp2"
"github.com/tatsuhiro-t/spdy"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"golang.org/x/net/websocket"
"io"
"io/ioutil"
@ -30,7 +30,7 @@ const (
serverBin = buildDir + "/src/nghttpx"
serverPort = 3009
testDir = sourceDir + "/integration-tests"
logDir = buildDir + "/integration-tests"
logDir = buildDir + "/integration-tests"
)
func pair(name, value string) hpack.HeaderField {
@ -86,14 +86,18 @@ func newServerTesterTLSConfig(args []string, t *testing.T, handler http.HandlerF
// newServerTesterInternal creates test context. If frontendTLS is
// true, set up TLS frontend connection.
func newServerTesterInternal(args []string, t *testing.T, handler http.Handler, frontendTLS bool, clientConfig *tls.Config) *serverTester {
func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handler, frontendTLS bool, clientConfig *tls.Config) *serverTester {
ts := httptest.NewUnstartedServer(handler)
args := []string{}
backendTLS := false
for _, k := range args {
for _, k := range src_args {
switch k {
case "--http2-bridge":
backendTLS = true
default:
args = append(args, k)
}
}
if backendTLS {
@ -102,9 +106,9 @@ func newServerTesterInternal(args []string, t *testing.T, handler http.Handler,
// NextProtos separately for ts.TLS. NextProtos set
// in nghttp2.ConfigureServer is effectively ignored.
ts.TLS = new(tls.Config)
ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2-14")
ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2")
ts.StartTLS()
args = append(args, "-k")
args = append(args, "-k", "--backend-tls")
} else {
ts.Start()
}
@ -124,6 +128,9 @@ func newServerTesterInternal(args []string, t *testing.T, handler http.Handler,
// URL.Host looks like "127.0.0.1:8080", but we want
// "127.0.0.1,8080"
b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1)
if backendTLS {
b += ";;proto=h2"
}
args = append(args, fmt.Sprintf("-f127.0.0.1,%v", serverPort), b,
"--errorlog-file="+logDir+"/log.txt", "-LINFO")

View File

@ -22,6 +22,7 @@ set(NGHTTP2_SOURCES
nghttp2_callbacks.c
nghttp2_mem.c
nghttp2_http.c
nghttp2_rcbuf.c
)
# Public shared library

View File

@ -47,7 +47,8 @@ OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \
nghttp2_option.c \
nghttp2_callbacks.c \
nghttp2_mem.c \
nghttp2_http.c
nghttp2_http.c \
nghttp2_rcbuf.c
HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
nghttp2_frame.h \
@ -61,7 +62,8 @@ HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
nghttp2_option.h \
nghttp2_callbacks.h \
nghttp2_mem.h \
nghttp2_http.h
nghttp2_http.h \
nghttp2_rcbuf.h
libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
libnghttp2_la_LDFLAGS = -no-undefined \

View File

@ -12,16 +12,16 @@
#
THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))
USE_CYTHON := 1
#USE_CYTHON := 0
USE_CYTHON := 0
#USE_CYTHON := 1
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -r -e 's/(-DEV)?], //g')
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV//g' -e 's/], //g')
_VERSION := $(subst ., ,$(_VERSION))
VER_MAJOR := $(word 1,$(_VERSION))
VER_MINOR := $(word 2,$(_VERSION))
VER_MICRO := $(word 3,$(_VERSION))
VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO)
VERSION_NUM := ($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO)
VERSION_NUM := (($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO))
GENERATED := 'Generated by $(realpath Makefile.MSVC)'
@ -90,7 +90,8 @@ NGHTTP2_SRC := nghttp2_pq.c \
nghttp2_option.c \
nghttp2_callbacks.c \
nghttp2_mem.c \
nghttp2_http.c
nghttp2_http.c \
nghttp2_rcbuf.c
NGHTTP2_OBJ_R := $(addprefix $(OBJ_DIR)/r_, $(notdir $(NGHTTP2_SRC:.c=.obj)))
NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj)))
@ -101,7 +102,7 @@ NGHTTP2_OBJ_D := $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj)))
clean_nghttp2_pyd_0 clean_nghttp2_pyd_1
all: intro $(OBJ_DIR) $(TARGETS) build_nghttp2_pyd_$(USE_CYTHON)
all: intro includes/nghttp2/nghttp2ver.h $(OBJ_DIR) $(TARGETS) build_nghttp2_pyd_$(USE_CYTHON)
@echo 'Welcome to NgHTTP2 (release + debug).'
@echo 'Do a "make -f Makefile.MSVC install" at own risk!'
@ -193,17 +194,17 @@ $(OBJ_DIR)/d_%.obj: %.c $(THIS_MAKEFILE)
@echo
$(OBJ_DIR)/r_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE)
$(RC) -nologo -D_RELEASE -Fo $@ $<
$(RC) -D_RELEASE -Fo $@ $<
@echo
$(OBJ_DIR)/d_nghttp2.res: $(OBJ_DIR)/nghttp2.rc $(THIS_MAKEFILE)
$(RC) -nologo -D_DEBUG -Fo $@ $<
$(RC) -D_DEBUG -Fo $@ $<
@echo
includes/nghttp2/nghttp2ver.h: includes/nghttp2/nghttp2ver.h.in $(THIS_MAKEFILE)
sed < includes/nghttp2/nghttp2ver.h.in \
-e 's/@PACKAGE_VERSION@/$(VERSION)/g' \
-e 's/@PACKAGE_VERSION_NUM@/($(VERSION_NUM))/g' > $@
-e 's/@PACKAGE_VERSION_NUM@/$(VERSION_NUM)/g' > $@
touch --reference=includes/nghttp2/nghttp2ver.h.in $@

View File

@ -382,6 +382,10 @@ typedef enum {
* Unexpected internal error, but recovered.
*/
NGHTTP2_ERR_INTERNAL = -534,
/**
* Indicates that a processing was canceled.
*/
NGHTTP2_ERR_CANCEL = -535,
/**
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and processing was terminated (e.g.,
@ -415,6 +419,55 @@ typedef enum {
NGHTTP2_ERR_FLOODED = -904
} nghttp2_error;
/**
* @struct
*
* The object representing single contagious buffer.
*/
typedef struct {
/**
* The pointer to the buffer.
*/
uint8_t *base;
/**
* The length of the buffer.
*/
size_t len;
} nghttp2_vec;
struct nghttp2_rcbuf;
/**
* @struct
*
* The object representing reference counted buffer. The details of
* this structure are intentionally hidden from the public API.
*/
typedef struct nghttp2_rcbuf nghttp2_rcbuf;
/**
* @function
*
* Increments the reference count of |rcbuf| by 1.
*/
NGHTTP2_EXTERN void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf);
/**
* @function
*
* Decrements the reference count of |rcbuf| by 1. If the reference
* count becomes zero, the object pointed by |rcbuf| will be freed.
* In this case, application must not use |rcbuf| again.
*/
NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf);
/**
* @function
*
* Returns the underlying buffer managed by |rcbuf|.
*/
NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf);
/**
* @enum
*
@ -1623,6 +1676,32 @@ typedef int (*nghttp2_on_header_callback)(nghttp2_session *session,
const uint8_t *value, size_t valuelen,
uint8_t flags, void *user_data);
/**
* @functypedef
*
* Callback function invoked when a header name/value pair is received
* for the |frame|. The |name| is header name. The |value| is header
* value. The |flags| is bitwise OR of one or more of
* :type:`nghttp2_nv_flag`.
*
* This callback behaves like :type:`nghttp2_on_header_callback`,
* except that |name| and |value| are stored in reference counted
* buffer. If application wishes to keep these references without
* copying them, use `nghttp2_rcbuf_incref()` to increment their
* reference count. It is the application's responsibility to call
* `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so
* as not to leak memory. If the |session| is created by
* `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`,
* the function to free memory is the one belongs to the mem
* parameter. As long as this free function alives, |name| and
* |value| can live after |session| was destroyed.
*/
typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session,
const nghttp2_frame *frame,
nghttp2_rcbuf *name,
nghttp2_rcbuf *value, uint8_t flags,
void *user_data);
/**
* @functypedef
*
@ -1700,6 +1779,124 @@ typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data);
/**
* @functypedef
*
* Callback function invoked when chunk of extension frame payload is
* received. The |hd| points to frame header. The received
* chunk is |data| of length |len|.
*
* The implementation of this function must return 0 if it succeeds.
*
* To abort processing this extension frame, return
* :enum:`NGHTTP2_ERR_CANCEL`.
*
* If fatal error occurred, application should return
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
* `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the
* other values are returned, currently they are treated as
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
*/
typedef int (*nghttp2_on_extension_chunk_recv_callback)(
nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data,
size_t len, void *user_data);
/**
* @functypedef
*
* Callback function invoked when library asks the application to
* unpack extension payload from its wire format. The extension
* payload has been passed to the application using
* :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header
* is already unpacked by the library and provided as |hd|.
*
* To receive extension frames, the application must tell desired
* extension frame type to the library using
* `nghttp2_option_set_user_recv_extension_type()`.
*
* The implementation of this function may store the pointer to the
* created object as a result of unpacking in |*payload|, and returns
* 0. The pointer stored in |*payload| is opaque to the library, and
* the library does not own its pointer. |*payload| is initialized as
* ``NULL``. The |*payload| is available as ``frame->ext.payload`` in
* :type:`nghttp2_on_frame_recv_callback`. Therefore if application
* can free that memory inside :type:`nghttp2_on_frame_recv_callback`
* callback. Of course, application has a liberty not ot use
* |*payload|, and do its own mechanism to process extension frames.
*
* To abort processing this extension frame, return
* :enum:`NGHTTP2_ERR_CANCEL`.
*
* If fatal error occurred, application should return
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
* `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the
* other values are returned, currently they are treated as
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
*/
typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session,
void **payload,
const nghttp2_frame_hd *hd,
void *user_data);
/**
* @functypedef
*
* Callback function invoked when library asks the application to pack
* extension payload in its wire format. The frame header will be
* packed by library. Application must pack payload only.
* ``frame->ext.payload`` is the object passed to
* `nghttp2_submit_extension()` as payload parameter. Application
* must pack extension payload to the |buf| of its capacity |len|
* bytes. The |len| is at least 16KiB.
*
* The implementation of this function should return the number of
* bytes written into |buf| when it succeeds.
*
* To abort processing this extension frame, return
* :enum:`NGHTTP2_ERR_CANCEL`, and
* :type:`nghttp2_on_frame_not_send_callback` will be invoked.
*
* If fatal error occurred, application should return
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
* `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the
* other values are returned, currently they are treated as
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. If the return value is
* strictly larger than |len|, it is treated as
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
*/
typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session,
uint8_t *buf, size_t len,
const nghttp2_frame *frame,
void *user_data);
/**
* @functypedef
*
* Callback function invoked when library provides the error message
* intended for human consumption. This callback is solely for
* debugging purpose. The |msg| is typically NULL-terminated string
* of length |len|. |len| does not include the sentinel NULL
* character.
*
* The format of error message may change between nghttp2 library
* versions. The application should not depend on the particular
* format.
*
* Normally, application should return 0 from this callback. If fatal
* error occurred while doing something in this callback, application
* should return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. In this case,
* library will return immediately with return value
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if nonzero value
* is returned from this callback, they are treated as
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`, but application should not
* rely on this details.
*/
typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg,
size_t len, void *user_data);
struct nghttp2_session_callbacks;
/**
@ -1844,12 +2041,25 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback(
* @function
*
* Sets callback function invoked when a header name/value pair is
* received.
* received. If both
* `nghttp2_session_callbacks_set_on_header_callback()` and
* `nghttp2_session_callbacks_set_on_header_callback2()` are used to
* set callbacks, the latter has the precedence.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback(
nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback on_header_callback);
/**
* @function
*
* Sets callback function invoked when a header name/value pair is
* received.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback2 on_header_callback2);
/**
* @function
*
@ -1892,6 +2102,46 @@ NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback(
nghttp2_session_callbacks *cbs,
nghttp2_send_data_callback send_data_callback);
/**
* @function
*
* Sets callback function invoked when the library asks the
* application to pack extension frame payload in wire format.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback(
nghttp2_session_callbacks *cbs,
nghttp2_pack_extension_callback pack_extension_callback);
/**
* @function
*
* Sets callback function invoked when the library asks the
* application to unpack extension frame payload from wire format.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback(
nghttp2_session_callbacks *cbs,
nghttp2_unpack_extension_callback unpack_extension_callback);
/**
* @function
*
* Sets callback function invoked when chunk of extension frame
* payload is received.
*/
NGHTTP2_EXTERN void
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
nghttp2_session_callbacks *cbs,
nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback);
/**
* @function
*
* Sets callback function invoked when library tells error message to
* the application.
*/
NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback(
nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback);
/**
* @functypedef
*
@ -2106,6 +2356,37 @@ NGHTTP2_EXTERN void
nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
uint32_t val);
/**
* @function
*
* Sets extension frame type the application is willing to handle with
* user defined callbacks (see
* :type:`nghttp2_on_extension_chunk_recv_callback` and
* :type:`nghttp2_unpack_extension_callback`). The |type| is
* extension frame type, and must be strictly greater than 0x9.
* Otherwise, this function does nothing. The application can call
* this function multiple times to set more than one frame type to
* receive. The application does not have to call this function if it
* just sends extension frames.
*/
NGHTTP2_EXTERN void
nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type);
/**
* @function
*
* This option prevents the library from sending PING frame with ACK
* flag set automatically when PING frame without ACK flag set is
* received. If this option is set to nonzero, the library won't send
* PING frame with ACK flag set in the response for incoming PING
* frame. The application can send PING frame with ACK flag set using
* `nghttp2_submit_ping()` with :enum:`NGHTTP2_FLAG_ACK` as flags
* parameter.
*/
NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option,
int val);
/**
* @function
*
@ -3079,6 +3360,16 @@ nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
*/
NGHTTP2_EXTERN const char *nghttp2_strerror(int lib_error_code);
/**
* @function
*
* Returns string representation of HTTP/2 error code |error_code|
* (e.g., ``PROTOCOL_ERROR`` is returned if ``error_code ==
* NGHTTP2_PROTOCOL_ERROR``). If string representation is unknown for
* given |error_code|, this function returns string ``unknown``.
*/
NGHTTP2_EXTERN const char *nghttp2_http2_strerror(uint32_t error_code);
/**
* @function
*
@ -3628,8 +3919,12 @@ nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
* received PING frame. The library automatically submits PING frame
* in this case.
*
* The |flags| is currently ignored and should be
* :enum:`NGHTTP2_FLAG_NONE`.
* The |flags| is bitwise OR of 0 or more of the following value.
*
* * :enum:`NGHTTP2_FLAG_ACK`
*
* Unless `nghttp2_option_set_no_auto_ping_ack()` is used, the |flags|
* should be :enum:`NGHTTP2_FLAG_NONE`.
*
* If the |opaque_data| is non ``NULL``, then it should point to the 8
* bytes array of memory to specify opaque data to send with PING
@ -3776,6 +4071,48 @@ NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session,
int32_t stream_id,
int32_t window_size_increment);
/**
* @function
*
* Submits extension frame.
*
* Application can pass arbitrary frame flags and stream ID in |flags|
* and |stream_id| respectively. The |payload| is opaque pointer, and
* it can be accessible though ``frame->ext.payload`` in
* :type:`nghttp2_pack_extension_callback`. The library will not own
* passed |payload| pointer.
*
* The application must set :type:`nghttp2_pack_extension_callback`
* using `nghttp2_session_callbacks_set_pack_extension_callback()`.
*
* The application should retain the memory pointed by |payload| until
* the transmission of extension frame is done (which is indicated by
* :type:`nghttp2_on_frame_send_callback`), or transmission fails
* (which is indicated by :type:`nghttp2_on_frame_not_send_callback`).
* If application does not touch this memory region after packing it
* into a wire format, application can free it inside
* :type:`nghttp2_pack_extension_callback`.
*
* The standard HTTP/2 frame cannot be sent with this function, so
* |type| must be strictly grater than 0x9. Otherwise, this function
* will fail with error code :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* :enum:`NGHTTP2_ERR_INVALID_STATE`
* If :type:`nghttp2_pack_extension_callback` is not set.
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* If |type| specifies standard HTTP/2 frame type. The frame
* types in the rage [0x0, 0x9], both inclusive, are standard
* HTTP/2 frame type, and cannot be sent using this function.
* :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory
*/
NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session,
uint8_t type, uint8_t flags,
int32_t stream_id, void *payload);
/**
* @function
*

View File

@ -73,7 +73,7 @@ typedef struct {
/*
* Initializes the |buf|. No memory is allocated in this function. Use
* nghttp2_buf_reserve() or nghttp2_buf_reserve2() to allocate memory.
* nghttp2_buf_reserve() to allocate memory.
*/
void nghttp2_buf_init(nghttp2_buf *buf);

View File

@ -104,6 +104,12 @@ void nghttp2_session_callbacks_set_on_header_callback(
cbs->on_header_callback = on_header_callback;
}
void nghttp2_session_callbacks_set_on_header_callback2(
nghttp2_session_callbacks *cbs,
nghttp2_on_header_callback2 on_header_callback2) {
cbs->on_header_callback2 = on_header_callback2;
}
void nghttp2_session_callbacks_set_select_padding_callback(
nghttp2_session_callbacks *cbs,
nghttp2_select_padding_callback select_padding_callback) {
@ -127,3 +133,26 @@ void nghttp2_session_callbacks_set_send_data_callback(
nghttp2_send_data_callback send_data_callback) {
cbs->send_data_callback = send_data_callback;
}
void nghttp2_session_callbacks_set_pack_extension_callback(
nghttp2_session_callbacks *cbs,
nghttp2_pack_extension_callback pack_extension_callback) {
cbs->pack_extension_callback = pack_extension_callback;
}
void nghttp2_session_callbacks_set_unpack_extension_callback(
nghttp2_session_callbacks *cbs,
nghttp2_unpack_extension_callback unpack_extension_callback) {
cbs->unpack_extension_callback = unpack_extension_callback;
}
void nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
nghttp2_session_callbacks *cbs,
nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback) {
cbs->on_extension_chunk_recv_callback = on_extension_chunk_recv_callback;
}
void nghttp2_session_callbacks_set_error_callback(
nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback) {
cbs->error_callback = error_callback;
}

View File

@ -91,6 +91,7 @@ struct nghttp2_session_callbacks {
* received.
*/
nghttp2_on_header_callback on_header_callback;
nghttp2_on_header_callback2 on_header_callback2;
/**
* Callback function invoked when the library asks application how
* many padding bytes are required for the transmission of the given
@ -107,6 +108,10 @@ struct nghttp2_session_callbacks {
*/
nghttp2_on_begin_frame_callback on_begin_frame_callback;
nghttp2_send_data_callback send_data_callback;
nghttp2_pack_extension_callback pack_extension_callback;
nghttp2_unpack_extension_callback unpack_extension_callback;
nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback;
nghttp2_error_callback error_callback;
};
#endif /* NGHTTP2_CALLBACKS_H */

View File

@ -184,6 +184,15 @@ void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags,
void nghttp2_frame_data_free(nghttp2_data *frame _U_) {}
void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
uint8_t flags, int32_t stream_id,
void *payload) {
nghttp2_frame_hd_init(&frame->hd, 0, type, flags, stream_id);
frame->payload = payload;
}
void nghttp2_frame_extension_free(nghttp2_extension *frame _U_) {}
size_t nghttp2_frame_priority_len(uint8_t flags) {
if (flags & NGHTTP2_FLAG_PRIORITY) {
return NGHTTP2_PRIORITY_SPECLEN;

View File

@ -439,6 +439,12 @@ void nghttp2_frame_window_update_init(nghttp2_window_update *frame,
void nghttp2_frame_window_update_free(nghttp2_window_update *frame);
void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type,
uint8_t flags, int32_t stream_id,
void *payload);
void nghttp2_frame_extension_free(nghttp2_extension *frame);
/*
* Returns the number of padding bytes after payload. The total
* padding length is given in the |padlen|. The returned value does

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
#include "nghttp2_hd_huffman.h"
#include "nghttp2_buf.h"
#include "nghttp2_mem.h"
#include "nghttp2_rcbuf.h"
#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
#define NGHTTP2_HD_ENTRY_OVERHEAD 32
@ -109,28 +110,32 @@ typedef enum {
NGHTTP2_TOKEN_CONNECTION,
NGHTTP2_TOKEN_KEEP_ALIVE,
NGHTTP2_TOKEN_PROXY_CONNECTION,
NGHTTP2_TOKEN_UPGRADE
NGHTTP2_TOKEN_UPGRADE,
} nghttp2_token;
typedef enum {
NGHTTP2_HD_FLAG_NONE = 0,
/* Indicates name was dynamically allocated and must be freed */
NGHTTP2_HD_FLAG_NAME_ALLOC = 1,
/* Indicates value was dynamically allocated and must be freed */
NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1,
/* Indicates that the name was gifted to the entry and no copying
necessary. */
NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 2,
/* Indicates that the value was gifted to the entry and no copying
necessary. */
NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3
} nghttp2_hd_flags;
struct nghttp2_hd_entry;
typedef struct nghttp2_hd_entry nghttp2_hd_entry;
typedef struct {
/* The buffer containing header field name. NULL-termination is
guaranteed. */
nghttp2_rcbuf *name;
/* The buffer containing header field value. NULL-termination is
guaranteed. */
nghttp2_rcbuf *value;
/* nghttp2_token value for name. It could be -1 if we have no token
for that header field name. */
int32_t token;
/* Bitwise OR of one or more of nghttp2_nv_flag. */
uint8_t flags;
} nghttp2_hd_nv;
struct nghttp2_hd_entry {
nghttp2_nv nv;
/* The header field name/value pair */
nghttp2_hd_nv nv;
/* This is solely for nghttp2_hd_{deflate,inflate}_get_table_entry
APIs to keep backward compatibility. */
nghttp2_nv cnv;
/* The next entry which shares same bucket in hash table. */
nghttp2_hd_entry *next;
/* The sequence number. We will increment it by one whenever we
@ -138,14 +143,17 @@ struct nghttp2_hd_entry {
uint32_t seq;
/* The hash value for header name (nv.name). */
uint32_t hash;
/* nghttp2_token value for nv.name. It could be -1 if we have no
token for that header field name. */
int token;
/* Reference count */
uint8_t ref;
uint8_t flags;
};
/* The entry used for static header table. */
typedef struct {
nghttp2_rcbuf name;
nghttp2_rcbuf value;
nghttp2_nv cnv;
int32_t token;
uint32_t hash;
} nghttp2_hd_static_entry;
typedef struct {
nghttp2_hd_entry **buffer;
size_t mask;
@ -219,24 +227,18 @@ struct nghttp2_hd_deflater {
struct nghttp2_hd_inflater {
nghttp2_hd_context ctx;
/* header buffer */
nghttp2_bufs nvbufs;
/* Stores current state of huffman decoding */
nghttp2_hd_huff_decode_context huff_decode_ctx;
/* Pointer to the nghttp2_hd_entry which is used current header
emission. This is required because in some cases the
ent_keep->ref == 0 and we have to keep track of it. */
nghttp2_hd_entry *ent_keep;
/* Pointer to the name/value pair buffer which is used in the
current header emission. */
uint8_t *nv_keep;
/* header buffer */
nghttp2_buf namebuf, valuebuf;
nghttp2_rcbuf *namercbuf, *valuercbuf;
/* Pointer to the name/value pair which are used in the current
header emission. */
nghttp2_rcbuf *nv_name_keep, *nv_value_keep;
/* The number of bytes to read */
size_t left;
/* The index in indexed repr or indexed name */
size_t index;
/* The length of new name encoded in literal. For huffman encoded
string, this is the length after it is decoded. */
size_t newnamelen;
/* The maximum header table size the inflater supports. This is the
same value transmitted in SETTINGS_HEADER_TABLE_SIZE */
size_t settings_hd_table_bufsize_max;
@ -256,24 +258,16 @@ struct nghttp2_hd_inflater {
};
/*
* Initializes the |ent| members. If NGHTTP2_HD_FLAG_NAME_ALLOC bit
* set in the |flags|, the content pointed by the |name| with length
* |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit
* set in the |flags|, the content pointed by the |value| with length
* |valuelen| is copied. The |token| is enum number looked up by
* |name|. It could be -1 if we don't have that enum value.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM
* Out of memory.
* Initializes the |ent| members. The reference counts of nv->name
* and nv->value are increased by one for each.
*/
int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name,
size_t namelen, uint8_t *value, size_t valuelen,
int token, nghttp2_mem *mem);
void nghttp2_hd_entry_init(nghttp2_hd_entry *ent, nghttp2_hd_nv *nv);
void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem);
/*
* This function decreases the reference counts of nv->name and
* nv->value.
*/
void nghttp2_hd_entry_free(nghttp2_hd_entry *ent);
/*
* Initializes |deflater| for deflating name/values pairs.
@ -354,16 +348,14 @@ int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem);
void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater);
/*
* Similar to nghttp2_hd_inflate_hd(), but this takes additional
* output parameter |token|. On successful header emission, it
* contains nghttp2_token value for nv_out->name. It could be -1 if
* we don't have enum value for the name. Other than that return
* values and semantics are the same as nghttp2_hd_inflate_hd().
* Similar to nghttp2_hd_inflate_hd(), but this takes nghttp2_hd_nv
* instead of nghttp2_nv as output parameter |nv_out|. Other than
* that return values and semantics are the same as
* nghttp2_hd_inflate_hd().
*/
ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater,
nghttp2_nv *nv_out, int *inflate_flags,
int *token, uint8_t *in, size_t inlen,
int in_final);
nghttp2_hd_nv *nv_out, int *inflate_flags,
uint8_t *in, size_t inlen, int in_final);
/* For unittesting purpose */
int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index,
@ -377,8 +369,7 @@ int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size);
/* For unittesting purpose */
nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context,
size_t index);
nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index);
/* For unittesting purpose */
ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final,
@ -414,11 +405,10 @@ int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src,
void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx);
/*
* Decodes the given data |src| with length |srclen|. The |ctx| must
* Decodes the given data |src| with length |srclen|. The |ctx| must
* be initialized by nghttp2_hd_huff_decode_context_init(). The result
* will be added to |dest|. This function may expand |dest| as
* needed. The caller is responsible to release the memory of |dest|
* by calling nghttp2_bufs_free().
* will be written to |buf|. This function assumes that |buf| has the
* enough room to store the decoded byte string.
*
* The caller must set the |final| to nonzero if the given input is
* the final block.
@ -430,13 +420,11 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx);
*
* NGHTTP2_ERR_NOMEM
* Out of memory.
* NGHTTP2_ERR_BUFFER_ERROR
* Maximum buffer capacity size exceeded.
* NGHTTP2_ERR_HEADER_COMP
* Decoding process has failed.
*/
ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
nghttp2_bufs *bufs, const uint8_t *src,
nghttp2_buf *buf, const uint8_t *src,
size_t srclen, int final);
#endif /* NGHTTP2_HD_H */

View File

@ -166,31 +166,10 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) {
ctx->accept = 1;
}
/* Use macro to make the code simpler..., but error case is tricky.
We spent most of the CPU in decoding, so we are doing this
thing. */
#define hd_huff_decode_sym_emit(bufs, sym, avail) \
do { \
if ((avail)) { \
nghttp2_bufs_fast_addb((bufs), (sym)); \
--(avail); \
} else { \
rv = nghttp2_bufs_addb((bufs), (sym)); \
if (rv != 0) { \
return rv; \
} \
(avail) = nghttp2_bufs_cur_avail((bufs)); \
} \
} while (0)
ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
nghttp2_bufs *bufs, const uint8_t *src,
nghttp2_buf *buf, const uint8_t *src,
size_t srclen, int final) {
size_t i;
int rv;
size_t avail;
avail = nghttp2_bufs_cur_avail(bufs);
/* We use the decoding algorithm described in
http://graphics.ics.uci.edu/pub/Prefix.pdf */
@ -202,8 +181,7 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
return NGHTTP2_ERR_HEADER_COMP;
}
if (t->flags & NGHTTP2_HUFF_SYM) {
/* this is macro, and may return from this function on error */
hd_huff_decode_sym_emit(bufs, t->sym, avail);
*buf->last++ = t->sym;
}
t = &huff_decode_table[t->state][src[i] & 0xf];
@ -211,8 +189,7 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
return NGHTTP2_ERR_HEADER_COMP;
}
if (t->flags & NGHTTP2_HUFF_SYM) {
/* this is macro, and may return from this function on error */
hd_huff_decode_sym_emit(bufs, t->sym, avail);
*buf->last++ = t->sym;
}
ctx->state = t->state;

View File

@ -288,6 +288,8 @@ const char *nghttp2_strerror(int error_code) {
return "Stream was refused";
case NGHTTP2_ERR_INTERNAL:
return "Internal error";
case NGHTTP2_ERR_CANCEL:
return "Cancel";
case NGHTTP2_ERR_NOMEM:
return "Out of memory";
case NGHTTP2_ERR_CALLBACK_FAILURE:
@ -449,3 +451,38 @@ uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) {
return dest + len;
}
const char *nghttp2_http2_strerror(uint32_t error_code) {
switch (error_code) {
case NGHTTP2_NO_ERROR:
return "NO_ERROR";
case NGHTTP2_PROTOCOL_ERROR:
return "PROTOCOL_ERROR";
case NGHTTP2_INTERNAL_ERROR:
return "INTERNAL_ERROR";
case NGHTTP2_FLOW_CONTROL_ERROR:
return "FLOW_CONTROL_ERROR";
case NGHTTP2_SETTINGS_TIMEOUT:
return "SETTINGS_TIMEOUT";
case NGHTTP2_STREAM_CLOSED:
return "STREAM_CLOSED";
case NGHTTP2_FRAME_SIZE_ERROR:
return "FRAME_SIZE_ERROR";
case NGHTTP2_REFUSED_STREAM:
return "REFUSED_STREAM";
case NGHTTP2_CANCEL:
return "CANCEL";
case NGHTTP2_COMPRESSION_ERROR:
return "COMPRESSION_ERROR";
case NGHTTP2_CONNECT_ERROR:
return "CONNECT_ERROR";
case NGHTTP2_ENHANCE_YOUR_CALM:
return "ENHANCE_YOUR_CALM";
case NGHTTP2_INADEQUATE_SECURITY:
return "INADEQUATE_SECURITY";
case NGHTTP2_HTTP_1_1_REQUIRED:
return "HTTP_1_1_REQUIRED";
default:
return "unknown";
}
}

View File

@ -82,12 +82,12 @@ static int lws(const uint8_t *s, size_t n) {
return 1;
}
static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_nv *nv,
static int check_pseudo_header(nghttp2_stream *stream, const nghttp2_hd_nv *nv,
int flag) {
if (stream->http_flags & flag) {
return 0;
}
if (lws(nv->value, nv->valuelen)) {
if (lws(nv->value->base, nv->value->len)) {
return 0;
}
stream->http_flags = (uint16_t)(stream->http_flags | flag);
@ -112,16 +112,16 @@ static int check_path(nghttp2_stream *stream) {
(stream->http_flags & NGHTTP2_HTTP_FLAG_PATH_ASTERISK)));
}
static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
int token, int trailer) {
if (nv->name[0] == ':') {
static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
int trailer) {
if (nv->name->base[0] == ':') {
if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
}
switch (token) {
switch (nv->token) {
case NGHTTP2_TOKEN__AUTHORITY:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__AUTHORITY)) {
return NGHTTP2_ERR_HTTP_HEADER;
@ -131,16 +131,16 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__METHOD)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
switch (nv->valuelen) {
switch (nv->value->len) {
case 4:
if (lstreq("HEAD", nv->value, nv->valuelen)) {
if (lstreq("HEAD", nv->value->base, nv->value->len)) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_HEAD;
}
break;
case 7:
switch (nv->value[6]) {
switch (nv->value->base[6]) {
case 'T':
if (lstreq("CONNECT", nv->value, nv->valuelen)) {
if (lstreq("CONNECT", nv->value->base, nv->value->len)) {
if (stream->stream_id % 2 == 0) {
/* we won't allow CONNECT for push */
return NGHTTP2_ERR_HTTP_HEADER;
@ -153,7 +153,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
}
break;
case 'S':
if (lstreq("OPTIONS", nv->value, nv->valuelen)) {
if (lstreq("OPTIONS", nv->value->base, nv->value->len)) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_OPTIONS;
}
break;
@ -168,9 +168,9 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
if (nv->value[0] == '/') {
if (nv->value->base[0] == '/') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_REGULAR;
} else if (nv->valuelen == 1 && nv->value[0] == '*') {
} else if (nv->value->len == 1 && nv->value->base[0] == '*') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PATH_ASTERISK;
}
break;
@ -181,8 +181,8 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
if ((nv->valuelen == 4 && memieq("http", nv->value, 4)) ||
(nv->valuelen == 5 && memieq("https", nv->value, 5))) {
if ((nv->value->len == 4 && memieq("http", nv->value->base, 4)) ||
(nv->value->len == 5 && memieq("https", nv->value->base, 5))) {
stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
}
break;
@ -195,7 +195,7 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (stream->content_length != -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
stream->content_length = parse_uint(nv->value, nv->valuelen);
stream->content_length = parse_uint(nv->value->base, nv->value->len);
if (stream->content_length == -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
@ -209,41 +209,41 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_UPGRADE:
return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE:
if (!lstrieq("trailers", nv->value, nv->valuelen)) {
if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
break;
default:
if (nv->name[0] == ':') {
if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;
}
}
if (nv->name[0] != ':') {
if (nv->name->base[0] != ':') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
}
return 0;
}
static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
int token, int trailer) {
if (nv->name[0] == ':') {
static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
int trailer) {
if (nv->name->base[0] == ':') {
if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
}
switch (token) {
switch (nv->token) {
case NGHTTP2_TOKEN__STATUS: {
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__STATUS)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
if (nv->valuelen != 3) {
if (nv->value->len != 3) {
return NGHTTP2_ERR_HTTP_HEADER;
}
stream->status_code = (int16_t)parse_uint(nv->value, nv->valuelen);
stream->status_code = (int16_t)parse_uint(nv->value->base, nv->value->len);
if (stream->status_code == -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
@ -253,7 +253,7 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
if (stream->content_length != -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
stream->content_length = parse_uint(nv->value, nv->valuelen);
stream->content_length = parse_uint(nv->value->base, nv->value->len);
if (stream->content_length == -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
@ -267,17 +267,17 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
case NGHTTP2_TOKEN_UPGRADE:
return NGHTTP2_ERR_HTTP_HEADER;
case NGHTTP2_TOKEN_TE:
if (!lstrieq("trailers", nv->value, nv->valuelen)) {
if (!lstrieq("trailers", nv->value->base, nv->value->len)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
break;
default:
if (nv->name[0] == ':') {
if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;
}
}
if (nv->name[0] != ':') {
if (nv->name->base[0] != ':') {
stream->http_flags |= NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED;
}
@ -375,7 +375,7 @@ static int check_scheme(const uint8_t *value, size_t len) {
}
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_nv *nv, int token,
nghttp2_frame *frame, nghttp2_hd_nv *nv,
int trailer) {
int rv;
@ -386,14 +386,14 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
this, we may disrupt many web sites and/or libraries. So we
become conservative here, and just ignore those illegal regular
headers. */
if (!nghttp2_check_header_name(nv->name, nv->namelen)) {
if (!nghttp2_check_header_name(nv->name->base, nv->name->len)) {
size_t i;
if (nv->namelen > 0 && nv->name[0] == ':') {
if (nv->name->len > 0 && nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;
}
/* header field name must be lower-cased without exception */
for (i = 0; i < nv->namelen; ++i) {
uint8_t c = nv->name[i];
for (i = 0; i < nv->name->len; ++i) {
uint8_t c = nv->name->base[i];
if ('A' <= c && c <= 'Z') {
return NGHTTP2_ERR_HTTP_HEADER;
}
@ -405,17 +405,18 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
return NGHTTP2_ERR_IGN_HTTP_HEADER;
}
if (token == NGHTTP2_TOKEN__AUTHORITY || token == NGHTTP2_TOKEN_HOST) {
rv = check_authority(nv->value, nv->valuelen);
} else if (token == NGHTTP2_TOKEN__SCHEME) {
rv = check_scheme(nv->value, nv->valuelen);
if (nv->token == NGHTTP2_TOKEN__AUTHORITY ||
nv->token == NGHTTP2_TOKEN_HOST) {
rv = check_authority(nv->value->base, nv->value->len);
} else if (nv->token == NGHTTP2_TOKEN__SCHEME) {
rv = check_scheme(nv->value->base, nv->value->len);
} else {
rv = nghttp2_check_header_value(nv->value, nv->valuelen);
rv = nghttp2_check_header_value(nv->value->base, nv->value->len);
}
if (rv == 0) {
assert(nv->namelen > 0);
if (nv->name[0] == ':') {
assert(nv->name->len > 0);
if (nv->name->base[0] == ':') {
return NGHTTP2_ERR_HTTP_HEADER;
}
/* When ignoring regular headers, we set this flag so that we
@ -426,10 +427,10 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
}
if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
return http_request_on_header(stream, nv, token, trailer);
return http_request_on_header(stream, nv, trailer);
}
return http_response_on_header(stream, nv, token, trailer);
return http_response_on_header(stream, nv, trailer);
}
int nghttp2_http_on_request_headers(nghttp2_stream *stream,

View File

@ -36,8 +36,7 @@
/*
* This function is called when HTTP header field |nv| in |frame| is
* received for |stream|. This function will validate |nv| against
* the current state of stream. The |token| is nghttp2_token value
* for nv->name, or -1 if we don't have enum value for the name.
* the current state of stream.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
@ -49,7 +48,7 @@
* if it was not received because of compatibility reasons.
*/
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
nghttp2_frame *frame, nghttp2_nv *nv, int token,
nghttp2_frame *frame, nghttp2_hd_nv *nv,
int trailer);
/*

View File

@ -52,6 +52,10 @@ void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) {
mem->free(ptr, mem->mem_user_data);
}
void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data) {
free(ptr, mem_user_data);
}
void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) {
return mem->calloc(nmemb, size, mem->mem_user_data);
}

View File

@ -38,6 +38,7 @@ nghttp2_mem *nghttp2_mem_default(void);
|mem|. */
void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size);
void nghttp2_mem_free(nghttp2_mem *mem, void *ptr);
void nghttp2_mem_free2(nghttp2_free free, void *ptr, void *mem_user_data);
void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size);
void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size);

View File

@ -62,3 +62,19 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS;
option->max_reserved_remote_streams = val;
}
void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option,
uint8_t type) {
if (type < 10) {
return;
}
option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES;
option->user_recv_ext_types[type / 8] =
(uint8_t)(option->user_recv_ext_types[type / 8] | (1 << (type & 0x7)));
}
void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_PING_ACK;
option->no_auto_ping_ack = val;
}

View File

@ -59,7 +59,9 @@ typedef enum {
NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2,
NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3,
NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4
NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4,
NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5,
NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6
} nghttp2_option_flag;
/**
@ -91,6 +93,14 @@ struct nghttp2_option {
* NGHTTP2_OPT_NO_HTTP_MESSAGING
*/
int no_http_messaging;
/**
* NGHTTP2_OPT_NO_AUTO_PING_ACK
*/
int no_auto_ping_ack;
/**
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/
uint8_t user_recv_ext_types[32];
};
#endif /* NGHTTP2_OPTION_H */

View File

@ -72,6 +72,9 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) {
case NGHTTP2_WINDOW_UPDATE:
nghttp2_frame_window_update_free(&frame->window_update);
break;
default:
nghttp2_frame_extension_free(&frame->ext);
break;
}
}

99
lib/nghttp2_rcbuf.c Normal file
View File

@ -0,0 +1,99 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "nghttp2_rcbuf.h"
#include <string.h>
#include <assert.h>
#include "nghttp2_mem.h"
int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size,
nghttp2_mem *mem) {
uint8_t *p;
p = nghttp2_mem_malloc(mem, sizeof(nghttp2_rcbuf) + size);
if (p == NULL) {
return NGHTTP2_ERR_NOMEM;
}
*rcbuf_ptr = (void *)p;
(*rcbuf_ptr)->mem_user_data = mem->mem_user_data;
(*rcbuf_ptr)->free = mem->free;
(*rcbuf_ptr)->base = p + sizeof(nghttp2_rcbuf);
(*rcbuf_ptr)->len = size;
(*rcbuf_ptr)->ref = 1;
return 0;
}
int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
size_t srclen, nghttp2_mem *mem) {
int rv;
rv = nghttp2_rcbuf_new(rcbuf_ptr, srclen + 1, mem);
if (rv != 0) {
return rv;
}
memcpy((*rcbuf_ptr)->base, src, srclen);
(*rcbuf_ptr)->len = srclen;
(*rcbuf_ptr)->base[srclen] = '\0';
return 0;
}
/*
* Frees |rcbuf| itself, regardless of its reference cout.
*/
void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf) {
nghttp2_mem_free2(rcbuf->free, rcbuf, rcbuf->mem_user_data);
}
void nghttp2_rcbuf_incref(nghttp2_rcbuf *rcbuf) {
if (rcbuf->ref == -1) {
return;
}
++rcbuf->ref;
}
void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf) {
if (rcbuf == NULL || rcbuf->ref == -1) {
return;
}
assert(rcbuf->ref > 0);
if (--rcbuf->ref == 0) {
nghttp2_rcbuf_del(rcbuf);
}
}
nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf) {
nghttp2_vec res = {rcbuf->base, rcbuf->len};
return res;
}

80
lib/nghttp2_rcbuf.h Normal file
View File

@ -0,0 +1,80 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef NGHTTP2_RCBUF_H
#define NGHTTP2_RCBUF_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* HAVE_CONFIG_H */
#include <nghttp2/nghttp2.h>
struct nghttp2_rcbuf {
/* custom memory allocator belongs to the mem parameter when
creating this object. */
void *mem_user_data;
nghttp2_free free;
/* The pointer to the underlying buffer */
uint8_t *base;
/* Size of buffer pointed by |base|. */
size_t len;
/* Reference count */
int32_t ref;
};
/*
* Allocates nghttp2_rcbuf object with |size| as initial buffer size.
* When the function succeeds, the reference count becomes 1.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM:
* Out of memory.
*/
int nghttp2_rcbuf_new(nghttp2_rcbuf **rcbuf_ptr, size_t size, nghttp2_mem *mem);
/*
* Like nghttp2_rcbuf_new(), but initializes the buffer with |src| of
* length |srclen|. This function allocates additional byte at the
* end and puts '\0' into it, so that the resulting buffer could be
* used as NULL-terminated string. Still (*rcbuf_ptr)->len equals to
* |srclen|.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
* NGHTTP2_ERR_NOMEM:
* Out of memory.
*/
int nghttp2_rcbuf_new2(nghttp2_rcbuf **rcbuf_ptr, const uint8_t *src,
size_t srclen, nghttp2_mem *mem);
/*
* Frees |rcbuf| itself, regardless of its reference cout.
*/
void nghttp2_rcbuf_del(nghttp2_rcbuf *rcbuf);
#endif /* NGHTTP2_RCBUF_H */

View File

@ -28,6 +28,7 @@
#include <stddef.h>
#include <stdio.h>
#include <assert.h>
#include <stdarg.h>
#include "nghttp2_helper.h"
#include "nghttp2_net.h"
@ -141,6 +142,62 @@ static int session_detect_idle_stream(nghttp2_session *session,
return 0;
}
static int session_call_error_callback(nghttp2_session *session,
const char *fmt, ...) {
size_t bufsize;
va_list ap;
char *buf;
int rv;
nghttp2_mem *mem;
if (!session->callbacks.error_callback) {
return 0;
}
mem = &session->mem;
va_start(ap, fmt);
rv = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
if (rv < 0) {
return NGHTTP2_ERR_NOMEM;
}
bufsize = (size_t)(rv + 1);
buf = nghttp2_mem_malloc(mem, bufsize);
if (buf == NULL) {
return NGHTTP2_ERR_NOMEM;
}
va_start(ap, fmt);
rv = vsnprintf(buf, bufsize, fmt, ap);
va_end(ap);
if (rv < 0) {
nghttp2_mem_free(mem, buf);
/* vsnprintf may return error because of various things we can
imagine, but typically we don't want to drop session just for
debug callback. */
DEBUGF(fprintf(stderr,
"error_callback: vsnprintf failed. The template was %s\n",
fmt));
return 0;
}
rv = session->callbacks.error_callback(session, buf, (size_t)rv,
session->user_data);
nghttp2_mem_free(mem, buf);
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int session_terminate_session(nghttp2_session *session,
int32_t last_stream_id,
uint32_t error_code, const char *reason) {
@ -231,6 +288,8 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
nghttp2_session_new(), we rely on the fact that
iframe->frame.hd.type is 0, so that no free is performed. */
switch (iframe->frame.hd.type) {
case NGHTTP2_DATA:
break;
case NGHTTP2_HEADERS:
nghttp2_frame_headers_free(&iframe->frame.headers, mem);
break;
@ -255,6 +314,10 @@ static void session_inbound_frame_reset(nghttp2_session *session) {
case NGHTTP2_WINDOW_UPDATE:
nghttp2_frame_window_update_free(&iframe->frame.window_update);
break;
default:
/* extension frame */
nghttp2_frame_extension_free(&iframe->frame.ext);
break;
}
memset(&iframe->frame, 0, sizeof(nghttp2_frame));
@ -405,6 +468,16 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING;
}
if (option->opt_set_mask & NGHTTP2_OPT_USER_RECV_EXT_TYPES) {
memcpy((*session_ptr)->user_recv_ext_types, option->user_recv_ext_types,
sizeof((*session_ptr)->user_recv_ext_types));
}
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) &&
option->no_auto_ping_ack) {
(*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK;
}
}
(*session_ptr)->callbacks = *callbacks;
@ -1216,11 +1289,12 @@ int nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
size_t max;
int rv;
/* Make minimum number of idle streams 16, which is arbitrary chosen
number. */
max = nghttp2_max(16,
nghttp2_min(session->local_settings.max_concurrent_streams,
session->pending_local_max_concurrent_stream));
/* Make minimum number of idle streams 16, and maximum 100, which
are arbitrary chosen numbers. */
max = nghttp2_min(
100, nghttp2_max(
16, nghttp2_min(session->local_settings.max_concurrent_streams,
session->pending_local_max_concurrent_stream)));
DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
"num_idle_streams=%zu, max=%zu\n",
@ -1748,6 +1822,41 @@ static size_t session_estimate_headers_payload(nghttp2_session *session,
additional;
}
static int session_pack_extension(nghttp2_session *session, nghttp2_bufs *bufs,
nghttp2_frame *frame) {
ssize_t rv;
nghttp2_buf *buf;
size_t buflen;
size_t framelen;
assert(session->callbacks.pack_extension_callback);
buf = &bufs->head->buf;
buflen = nghttp2_min(nghttp2_buf_avail(buf), NGHTTP2_MAX_PAYLOADLEN);
rv = session->callbacks.pack_extension_callback(session, buf->last, buflen,
frame, session->user_data);
if (rv == NGHTTP2_ERR_CANCEL) {
return (int)rv;
}
if (rv < 0 || (size_t)rv > buflen) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
framelen = (size_t)rv;
frame->hd.length = framelen;
assert(buf->pos == buf->last);
buf->last += framelen;
buf->pos -= NGHTTP2_FRAME_HDLEN;
nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
return 0;
}
/*
* This function serializes frame for transmission.
*
@ -1988,8 +2097,22 @@ static int session_prep_frame(nghttp2_session *session,
nghttp2_frame_pack_window_update(&session->aob.framebufs,
&frame->window_update);
break;
case NGHTTP2_CONTINUATION:
/* We never handle CONTINUATION here. */
assert(0);
break;
default:
return NGHTTP2_ERR_INVALID_ARGUMENT;
/* extension frame */
if (session_is_closing(session)) {
return NGHTTP2_ERR_SESSION_CLOSING;
}
rv = session_pack_extension(session, &session->aob.framebufs, frame);
if (rv != 0) {
return rv;
}
break;
}
return 0;
} else {
@ -3056,20 +3179,65 @@ static int session_call_on_begin_headers(nghttp2_session *session,
static int session_call_on_header(nghttp2_session *session,
const nghttp2_frame *frame,
const nghttp2_nv *nv) {
int rv;
if (session->callbacks.on_header_callback) {
const nghttp2_hd_nv *nv) {
int rv = 0;
if (session->callbacks.on_header_callback2) {
rv = session->callbacks.on_header_callback2(
session, frame, nv->name, nv->value, nv->flags, session->user_data);
} else if (session->callbacks.on_header_callback) {
rv = session->callbacks.on_header_callback(
session, frame, nv->name, nv->namelen, nv->value, nv->valuelen,
nv->flags, session->user_data);
if (rv == NGHTTP2_ERR_PAUSE ||
rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
session, frame, nv->name->base, nv->name->len, nv->value->base,
nv->value->len, nv->flags, session->user_data);
}
if (rv == NGHTTP2_ERR_PAUSE || rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int
session_call_on_extension_chunk_recv_callback(nghttp2_session *session,
const uint8_t *data, size_t len) {
int rv;
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
if (session->callbacks.on_extension_chunk_recv_callback) {
rv = session->callbacks.on_extension_chunk_recv_callback(
session, &frame->hd, data, len, session->user_data);
if (rv == NGHTTP2_ERR_CANCEL) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return 0;
}
static int session_call_unpack_extension_callback(nghttp2_session *session) {
int rv;
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
void *payload = NULL;
rv = session->callbacks.unpack_extension_callback(
session, &payload, &frame->hd, session->user_data);
if (rv == NGHTTP2_ERR_CANCEL) {
return rv;
}
if (rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
frame->ext.payload = payload;
return 0;
}
@ -3210,11 +3378,10 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
ssize_t proclen;
int rv;
int inflate_flags;
nghttp2_nv nv;
nghttp2_hd_nv nv;
nghttp2_stream *stream;
nghttp2_stream *subject_stream;
int trailer = 0;
int token;
*readlen_ptr = 0;
stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
@ -3231,7 +3398,7 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
for (;;) {
inflate_flags = 0;
proclen = nghttp2_hd_inflate_hd2(&session->hd_inflater, &nv, &inflate_flags,
&token, in, inlen, final);
in, inlen, final);
if (nghttp2_is_fatal((int)proclen)) {
return (int)proclen;
}
@ -3266,13 +3433,23 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
rv = 0;
if (subject_stream && session_enforce_http_messaging(session)) {
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv, token,
rv = nghttp2_http_on_header(session, subject_stream, frame, &nv,
trailer);
if (rv == NGHTTP2_ERR_HTTP_HEADER) {
DEBUGF(fprintf(
stderr, "recv: HTTP error: type=%d, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.namelen,
nv.name, (int)nv.valuelen, nv.value));
stderr, "recv: HTTP error: type=%u, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.name->len,
nv.name->base, (int)nv.value->len, nv.value->base));
rv = session_call_error_callback(
session, "Invalid HTTP header field was received: frame type: "
"%u, stream: %d, name: [%.*s], value: [%.*s]",
frame->hd.type, frame->hd.stream_id, (int)nv.name->len,
nv.name->base, (int)nv.value->len, nv.value->base);
if (nghttp2_is_fatal(rv)) {
return rv;
}
rv =
session_handle_invalid_stream2(session, subject_stream->stream_id,
@ -3286,9 +3463,9 @@ static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
if (rv == NGHTTP2_ERR_IGN_HTTP_HEADER) {
/* header is ignored */
DEBUGF(fprintf(
stderr, "recv: HTTP ignored: type=%d, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.namelen,
nv.name, (int)nv.valuelen, nv.value));
stderr, "recv: HTTP ignored: type=%u, id=%d, header %.*s: %.*s\n",
frame->hd.type, subject_stream->stream_id, (int)nv.name->len,
nv.name->base, (int)nv.value->len, nv.value->base));
}
}
if (rv == 0) {
@ -4262,7 +4439,8 @@ int nghttp2_session_on_ping_received(nghttp2_session *session,
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
"PING: stream_id != 0");
}
if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
if ((session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_PING_ACK) == 0 &&
(frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
!session_is_closing(session)) {
/* Peer sent ping, so ping it back */
rv = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK,
@ -4411,6 +4589,24 @@ static int session_process_window_update_frame(nghttp2_session *session) {
return nghttp2_session_on_window_update_received(session, frame);
}
static int session_process_extension_frame(nghttp2_session *session) {
int rv;
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
rv = session_call_unpack_extension_callback(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
/* This handles the case where rv == NGHTTP2_ERR_CANCEL as well */
if (rv != 0) {
return 0;
}
return session_call_on_frame_received(session, frame);
}
int nghttp2_session_on_data_received(nghttp2_session *session,
nghttp2_frame *frame) {
int rv = 0;
@ -5229,11 +5425,21 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
default:
DEBUGF(fprintf(stderr, "recv: unknown frame\n"));
/* Silently ignore unknown frame type. */
if (!session->callbacks.unpack_extension_callback ||
(session->user_recv_ext_types[iframe->frame.hd.type / 8] &
(1 << (iframe->frame.hd.type & 0x7))) == 0) {
/* Silently ignore unknown frame type. */
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD;
break;
}
@ -5693,7 +5899,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
if (cont_hd.type != NGHTTP2_CONTINUATION ||
cont_hd.stream_id != iframe->frame.hd.stream_id) {
DEBUGF(fprintf(stderr, "recv: expected stream_id=%d, type=%d, but "
"got stream_id=%d, type=%d\n",
"got stream_id=%d, type=%u\n",
iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION,
cont_hd.stream_id, cont_hd.type));
rv = nghttp2_session_terminate_session_with_reason(
@ -5933,6 +6139,44 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
case NGHTTP2_IB_IGN_ALL:
return (ssize_t)inlen;
case NGHTTP2_IB_READ_EXTENSION_PAYLOAD:
DEBUGF(fprintf(stderr, "recv: [IB_READ_EXTENSION_PAYLOAD]\n"));
readlen = inbound_frame_payload_readlen(iframe, in, last);
iframe->payloadleft -= readlen;
in += readlen;
DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
iframe->payloadleft));
if (readlen > 0) {
rv = session_call_on_extension_chunk_recv_callback(
session, in - readlen, readlen);
if (nghttp2_is_fatal(rv)) {
return rv;
}
if (rv != 0) {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
break;
}
}
if (iframe->payloadleft > 0) {
break;
}
rv = session_process_extension_frame(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
session_inbound_frame_reset(session);
break;
}
if (!busy && in == last) {

View File

@ -50,7 +50,8 @@ extern int nghttp2_enable_strict_preface;
typedef enum {
NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1,
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2,
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3
} nghttp2_optmask;
typedef enum {
@ -105,7 +106,8 @@ typedef enum {
NGHTTP2_IB_READ_PAD_DATA,
NGHTTP2_IB_READ_DATA,
NGHTTP2_IB_IGN_DATA,
NGHTTP2_IB_IGN_ALL
NGHTTP2_IB_IGN_ALL,
NGHTTP2_IB_READ_EXTENSION_PAYLOAD
} nghttp2_inbound_state;
#define NGHTTP2_INBOUND_NUM_IV 7
@ -304,6 +306,13 @@ struct nghttp2_session {
this session. The nonzero does not necessarily mean
WINDOW_UPDATE is not queued. */
uint8_t window_update_queued;
/* Bitfield of extension frame types that application is willing to
receive. To designate the bit of given frame type i, use
user_recv_ext_types[i / 8] & (1 << (i & 0x7)). First 10 frame
types are standard frame types and not used in this bitfield. If
bit is set, it indicates that incoming frame with that type is
passed to user defined callbacks, otherwise they are ignored. */
uint8_t user_recv_ext_types[32];
};
/* Struct used when updating initial window size of each active

View File

@ -30,14 +30,32 @@
#include "nghttp2_session.h"
#include "nghttp2_helper.h"
/* Maximum distance between any two stream's cycle in the same
prirority queue. Imagine stream A's cycle is A, and stream B's
cycle is B, and A < B. The cycle is unsigned 32 bit integer, it
may get overflow. Because of how we calculate the next cycle
value, if B - A is less than or equals to
NGHTTP2_MAX_CYCLE_DISTANCE, A and B are in the same scale, in other
words, B is really greater than or equal to A. Otherwise, A is a
result of overflow, and it is actually A > B if we consider that
fact. */
#define NGHTTP2_MAX_CYCLE_DISTANCE (16384 * 256 + 255)
static int stream_less(const void *lhsx, const void *rhsx) {
const nghttp2_stream *lhs, *rhs;
lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
return lhs->cycle < rhs->cycle ||
(lhs->cycle == rhs->cycle && lhs->seq < rhs->seq);
if (lhs->cycle == rhs->cycle) {
return lhs->seq < rhs->seq;
}
if (lhs->cycle < rhs->cycle) {
return rhs->cycle - lhs->cycle <= NGHTTP2_MAX_CYCLE_DISTANCE;
}
return lhs->cycle - rhs->cycle > NGHTTP2_MAX_CYCLE_DISTANCE;
}
void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
@ -116,14 +134,14 @@ static int stream_subtree_active(nghttp2_stream *stream) {
/*
* Returns next cycle for |stream|.
*/
static void stream_next_cycle(nghttp2_stream *stream, uint64_t last_cycle) {
size_t penalty;
static void stream_next_cycle(nghttp2_stream *stream, uint32_t last_cycle) {
uint32_t penalty;
penalty =
stream->last_writelen * NGHTTP2_MAX_WEIGHT + stream->pending_penalty;
penalty = (uint32_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT +
stream->pending_penalty;
stream->cycle = last_cycle + penalty / (uint32_t)stream->weight;
stream->pending_penalty = (uint32_t)(penalty % (uint32_t)stream->weight);
stream->pending_penalty = penalty % (uint32_t)stream->weight;
}
static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
@ -134,7 +152,7 @@ static int stream_obq_push(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
stream_next_cycle(stream, dep_stream->descendant_last_cycle);
stream->seq = dep_stream->descendant_next_seq++;
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%ld\n",
DEBUGF(fprintf(stderr, "stream: stream=%d obq push cycle=%d\n",
stream->stream_id, stream->cycle));
DEBUGF(fprintf(stderr, "stream: push stream %d to stream %d\n",
@ -220,7 +238,7 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%d\n",
stream->stream_id, stream->cycle));
dep_stream->last_writelen = stream->last_writelen;
@ -229,9 +247,9 @@ void nghttp2_stream_reschedule(nghttp2_stream *stream) {
void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
nghttp2_stream *dep_stream;
uint64_t last_cycle;
uint32_t last_cycle;
int32_t old_weight;
size_t wlen_penalty;
uint32_t wlen_penalty;
if (stream->weight == weight) {
return;
@ -254,7 +272,7 @@ void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
nghttp2_pq_remove(&dep_stream->obq, &stream->pq_entry);
wlen_penalty = stream->last_writelen * NGHTTP2_MAX_WEIGHT;
wlen_penalty = (uint32_t)stream->last_writelen * NGHTTP2_MAX_WEIGHT;
/* Compute old stream->pending_penalty we used to calculate
stream->cycle */
@ -270,7 +288,9 @@ void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
place */
stream_next_cycle(stream, last_cycle);
if (stream->cycle < dep_stream->descendant_last_cycle) {
if (stream->cycle < dep_stream->descendant_last_cycle &&
(dep_stream->descendant_last_cycle - stream->cycle) <=
NGHTTP2_MAX_CYCLE_DISTANCE) {
stream->cycle = dep_stream->descendant_last_cycle;
}
@ -278,7 +298,7 @@ void nghttp2_stream_change_weight(nghttp2_stream *stream, int32_t weight) {
nghttp2_pq_push(&dep_stream->obq, &stream->pq_entry);
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%ld\n",
DEBUGF(fprintf(stderr, "stream: stream=%d obq resched cycle=%d\n",
stream->stream_id, stream->cycle));
}

View File

@ -147,9 +147,9 @@ struct nghttp2_stream {
/* Received body so far */
int64_t recv_content_length;
/* Base last_cycle for direct descendent streams. */
uint64_t descendant_last_cycle;
uint32_t descendant_last_cycle;
/* Next scheduled time to sent item */
uint64_t cycle;
uint32_t cycle;
/* Next seq used for direct descendant streams */
uint64_t descendant_next_seq;
/* Secondary key for prioritization to break a tie for cycle. This

View File

@ -211,8 +211,9 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
nvlen, NULL, stream_user_data);
}
int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_,
int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags,
const uint8_t *opaque_data) {
flags &= NGHTTP2_FLAG_ACK;
return nghttp2_session_add_ping(session, NGHTTP2_FLAG_NONE, opaque_data);
}
@ -530,3 +531,40 @@ ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
return (ssize_t)nghttp2_frame_pack_settings_payload(buf, iv, niv);
}
int nghttp2_submit_extension(nghttp2_session *session, uint8_t type,
uint8_t flags, int32_t stream_id, void *payload) {
int rv;
nghttp2_outbound_item *item;
nghttp2_frame *frame;
nghttp2_mem *mem;
mem = &session->mem;
if (type <= NGHTTP2_CONTINUATION) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if (!session->callbacks.pack_extension_callback) {
return NGHTTP2_ERR_INVALID_STATE;
}
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
if (item == NULL) {
return NGHTTP2_ERR_NOMEM;
}
nghttp2_outbound_item_init(item);
frame = &item->frame;
nghttp2_frame_extension_init(&frame->ext, type, flags, stream_id, payload);
rv = nghttp2_session_add_item(session, item);
if (rv != 0) {
nghttp2_frame_extension_free(&frame->ext);
nghttp2_mem_free(mem, item);
return rv;
}
return 0;
}

View File

@ -3,12 +3,12 @@
TAG=$1
PREV_TAG=$2
git submodule update --init
git checkout refs/tags/$TAG
git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog
git submodule update --init
autoreconf -i
./configure --with-mruby && \
make dist-bzip2 && make dist-gzip && make dist-xz || echo "error"
make distclean

View File

@ -145,6 +145,7 @@ if(ENABLE_APP)
shrpx_ssl_test.cc
shrpx_downstream_test.cc
shrpx_config_test.cc
shrpx_worker_test.cc
shrpx_http_test.cc
http2_test.cc
util_test.cc

View File

@ -308,7 +308,6 @@ public:
}
auto handler =
make_unique<Http2Handler>(this, fd, ssl, get_next_session_id());
handler->setup_bev();
if (!ssl) {
if (handler->connection_made() != 0) {
return;
@ -575,7 +574,9 @@ struct ev_loop *Http2Handler::get_loop() const {
Http2Handler::WriteBuf *Http2Handler::get_wb() { return &wb_; }
int Http2Handler::setup_bev() { return 0; }
void Http2Handler::start_settings_timer() {
ev_timer_start(sessions_->get_loop(), &settings_timerev_);
}
int Http2Handler::fill_wb() {
if (data_pending_) {
@ -870,8 +871,6 @@ int Http2Handler::connection_made() {
}
}
ev_timer_start(sessions_->get_loop(), &settings_timerev_);
if (ssl_ && !nghttp2::ssl::check_http2_requirement(ssl_)) {
terminate_session(NGHTTP2_INADEQUATE_SECURITY);
}
@ -1236,7 +1235,7 @@ void prepare_response(Stream *stream, Http2Handler *hd,
bool last_mod_found = false;
if (ims) {
last_mod_found = true;
last_mod = util::parse_http_date(ims->value);
last_mod = util::parse_http_date(StringRef{ims->value});
}
auto query_pos = reqpath.find("?");
std::string url;
@ -1538,6 +1537,15 @@ int hd_on_frame_send_callback(nghttp2_session *session,
break;
}
case NGHTTP2_SETTINGS: {
if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
return 0;
}
hd->start_settings_timer();
break;
}
case NGHTTP2_PUSH_PROMISE: {
auto promised_stream_id = frame->push_promise.promised_stream_id;
auto promised_stream = hd->get_stream(promised_stream_id);
@ -1681,6 +1689,9 @@ void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
if (config->verbose) {
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
callbacks, verbose_on_invalid_frame_recv_callback);
nghttp2_session_callbacks_set_error_callback(callbacks,
verbose_error_callback);
}
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(

View File

@ -137,7 +137,7 @@ public:
~Http2Handler();
void remove_self();
int setup_bev();
void start_settings_timer();
int on_read();
int on_write();
int connection_made();

View File

@ -64,7 +64,7 @@ HELPER_OBJECTS = util.cc \
http2.cc timegm.c app_helper.cc nghttp2_gzip.c
HELPER_HFILES = util.h \
http2.h timegm.h app_helper.h nghttp2_config.h \
nghttp2_gzip.h
nghttp2_gzip.h network.h
HTML_PARSER_OBJECTS =
HTML_PARSER_HFILES = HtmlParser.h
@ -171,6 +171,7 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
shrpx_ssl_test.cc shrpx_ssl_test.h \
shrpx_downstream_test.cc shrpx_downstream_test.h \
shrpx_config_test.cc shrpx_config_test.h \
shrpx_worker_test.cc shrpx_worker_test.h \
shrpx_http_test.cc shrpx_http_test.h \
http2_test.cc http2_test.h \
util_test.cc util_test.h \

169
src/allocator.h Normal file
View File

@ -0,0 +1,169 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include "nghttp2_config.h"
#include <sys/uio.h>
#include "template.h"
namespace nghttp2 {
struct MemBlock {
// The next MemBlock to chain them. This is for book keeping
// purpose to free them later.
MemBlock *next;
// begin is the pointer to the beginning of buffer. last is the
// location of next write. end is the one beyond of the end of the
// buffer.
uint8_t *begin, *last, *end;
};
// BlockAllocator allocates memory block with given size at once, and
// cuts the region from it when allocation is requested. If the
// requested size is larger than given threshold, it will be allocated
// in a distinct buffer on demand.
struct BlockAllocator {
BlockAllocator(size_t block_size, size_t isolation_threshold)
: retain(nullptr),
head(nullptr),
block_size(block_size),
isolation_threshold(std::min(block_size, isolation_threshold)) {}
~BlockAllocator() {
for (auto mb = retain; mb;) {
auto next = mb->next;
delete[] reinterpret_cast<uint8_t *>(mb);
mb = next;
}
}
BlockAllocator(BlockAllocator &&) = default;
BlockAllocator &operator=(BlockAllocator &&) = default;
BlockAllocator(const BlockAllocator &) = delete;
BlockAllocator &operator=(const BlockAllocator &) = delete;
MemBlock *alloc_mem_block(size_t size) {
auto block = new uint8_t[sizeof(MemBlock) + size];
auto mb = reinterpret_cast<MemBlock *>(block);
mb->next = retain;
mb->begin = mb->last = block + sizeof(MemBlock);
mb->end = mb->begin + size;
retain = mb;
return mb;
}
void *alloc(size_t size) {
if (size >= isolation_threshold) {
auto mb = alloc_mem_block(size);
mb->last = mb->end;
return mb->begin;
}
if (!head || head->end - head->last < static_cast<ssize_t>(size)) {
head = alloc_mem_block(block_size);
}
auto res = head->last;
head->last = reinterpret_cast<uint8_t *>(
(reinterpret_cast<intptr_t>(head->last + size) + 0xf) & ~0xf);
return res;
}
// This holds live memory block to free them in dtor.
MemBlock *retain;
// Current memory block to use.
MemBlock *head;
// size of single memory block
size_t block_size;
// if allocation greater or equal to isolation_threshold bytes is
// requested, allocate dedicated block.
size_t isolation_threshold;
};
// Makes a copy of |src|. The resulting string will be
// NULL-terminated.
template <typename BlockAllocator>
StringRef make_string_ref(BlockAllocator &alloc, const StringRef &src) {
auto dst = static_cast<uint8_t *>(alloc.alloc(src.size() + 1));
auto p = dst;
p = std::copy(std::begin(src), std::end(src), p);
*p = '\0';
return StringRef{dst, src.size()};
}
// Returns the string which is the concatenation of |a| and |b| in
// this order. The resulting string will be NULL-terminated.
template <typename BlockAllocator>
StringRef concat_string_ref(BlockAllocator &alloc, const StringRef &a,
const StringRef &b) {
auto len = a.size() + b.size();
auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
auto p = dst;
p = std::copy(std::begin(a), std::end(a), p);
p = std::copy(std::begin(b), std::end(b), p);
*p = '\0';
return StringRef{dst, len};
}
// Returns the string which is the concatenation of |a|, |b| and |c|
// in this order. The resulting string will be NULL-terminated.
template <typename BlockAllocator>
StringRef concat_string_ref(BlockAllocator &alloc, const StringRef &a,
const StringRef &b, const StringRef &c) {
auto len = a.size() + b.size() + c.size();
auto dst = static_cast<uint8_t *>(alloc.alloc(len + 1));
auto p = dst;
p = std::copy(std::begin(a), std::end(a), p);
p = std::copy(std::begin(b), std::end(b), p);
p = std::copy(std::begin(c), std::end(c), p);
*p = '\0';
return StringRef{dst, len};
}
struct ByteRef {
// The pointer to the beginning of the buffer.
uint8_t *base;
// The length of the buffer.
size_t len;
};
// Makes a buffer with given size. The resulting byte string might
// not be NULL-terminated.
template <typename BlockAllocator>
ByteRef make_byte_ref(BlockAllocator &alloc, size_t size) {
auto dst = static_cast<uint8_t *>(alloc.alloc(size));
return {dst, size};
}
} // namespace aria2
#endif // ALLOCATOR_H

View File

@ -62,43 +62,6 @@
namespace nghttp2 {
namespace {
const char *strstatus(uint32_t error_code) {
switch (error_code) {
case NGHTTP2_NO_ERROR:
return "NO_ERROR";
case NGHTTP2_PROTOCOL_ERROR:
return "PROTOCOL_ERROR";
case NGHTTP2_INTERNAL_ERROR:
return "INTERNAL_ERROR";
case NGHTTP2_FLOW_CONTROL_ERROR:
return "FLOW_CONTROL_ERROR";
case NGHTTP2_SETTINGS_TIMEOUT:
return "SETTINGS_TIMEOUT";
case NGHTTP2_STREAM_CLOSED:
return "STREAM_CLOSED";
case NGHTTP2_FRAME_SIZE_ERROR:
return "FRAME_SIZE_ERROR";
case NGHTTP2_REFUSED_STREAM:
return "REFUSED_STREAM";
case NGHTTP2_CANCEL:
return "CANCEL";
case NGHTTP2_COMPRESSION_ERROR:
return "COMPRESSION_ERROR";
case NGHTTP2_CONNECT_ERROR:
return "CONNECT_ERROR";
case NGHTTP2_ENHANCE_YOUR_CALM:
return "ENHANCE_YOUR_CALM";
case NGHTTP2_INADEQUATE_SECURITY:
return "INADEQUATE_SECURITY";
case NGHTTP2_HTTP_1_1_REQUIRED:
return "HTTP_1_1_REQUIRED";
default:
return "UNKNOWN";
}
}
} // namespace
namespace {
const char *strsettingsid(int32_t id) {
switch (id) {
@ -121,7 +84,7 @@ const char *strsettingsid(int32_t id) {
} // namespace
namespace {
const char *strframetype(uint8_t type) {
std::string strframetype(uint8_t type) {
switch (type) {
case NGHTTP2_DATA:
return "DATA";
@ -141,9 +104,13 @@ const char *strframetype(uint8_t type) {
return "GOAWAY";
case NGHTTP2_WINDOW_UPDATE:
return "WINDOW_UPDATE";
default:
return "UNKNOWN";
}
std::string s = "extension(0x";
s += util::format_hex(&type, 1);
s += ')';
return s;
};
} // namespace
@ -280,7 +247,7 @@ const char *frame_name_ansi_esc(print_type ptype) {
namespace {
void print_frame(print_type ptype, const nghttp2_frame *frame) {
fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype),
strframetype(frame->hd.type), ansi_escend());
strframetype(frame->hd.type).c_str(), ansi_escend());
print_frame_hd(frame->hd);
if (frame->hd.flags) {
print_frame_attr_indent();
@ -331,7 +298,7 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) {
case NGHTTP2_RST_STREAM:
print_frame_attr_indent();
fprintf(outfile, "(error_code=%s(0x%02x))\n",
strstatus(frame->rst_stream.error_code),
nghttp2_http2_strerror(frame->rst_stream.error_code),
frame->rst_stream.error_code);
break;
case NGHTTP2_SETTINGS:
@ -360,7 +327,8 @@ void print_frame(print_type ptype, const nghttp2_frame *frame) {
print_frame_attr_indent();
fprintf(outfile, "(last_stream_id=%d, error_code=%s(0x%02x), "
"opaque_data(%u)=[%s])\n",
frame->goaway.last_stream_id, strstatus(frame->goaway.error_code),
frame->goaway.last_stream_id,
nghttp2_http2_strerror(frame->goaway.error_code),
frame->goaway.error_code,
static_cast<unsigned int>(frame->goaway.opaque_data_len),
util::ascii_dump(frame->goaway.opaque_data,
@ -446,6 +414,15 @@ int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
return 0;
}
int verbose_error_callback(nghttp2_session *session, const char *msg,
size_t len, void *user_data) {
print_timer();
fprintf(outfile, " [ERROR] %.*s\n", (int)len, msg);
fflush(outfile);
return 0;
}
namespace {
std::chrono::steady_clock::time_point base_tv;
} // namespace

View File

@ -60,6 +60,9 @@ int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data);
int verbose_error_callback(nghttp2_session *session, const char *msg,
size_t len, void *user_data);
// Returns difference between |a| and |b| in milliseconds, assuming
// |a| is more recent than |b|.
template <typename TimePoint>

View File

@ -79,8 +79,10 @@ public:
/// Start the first asynchronous operation for the connection.
void start() {
boost::system::error_code ec;
handler_ = std::make_shared<http2_handler>(
socket_.get_io_service(), socket_.lowest_layer().remote_endpoint(),
socket_.get_io_service(), socket_.lowest_layer().remote_endpoint(ec),
[this]() { do_write(); }, mux_);
if (handler_->start() != 0) {
stop();

View File

@ -77,8 +77,8 @@ bool serve_mux::handle(std::string pattern, request_cb cb) {
request_cb serve_mux::handler(request_impl &req) const {
auto &path = req.uri().path;
if (req.method() != "CONNECT") {
auto clean_path = ::nghttp2::http2::path_join(
nullptr, 0, nullptr, 0, path.c_str(), path.size(), nullptr, 0);
auto clean_path = ::nghttp2::http2::path_join(StringRef{}, StringRef{},
StringRef{path}, StringRef{});
if (clean_path != path) {
auto new_uri = util::percent_encode_path(clean_path);
auto &uref = req.uri();

View File

@ -84,7 +84,7 @@ Config::Config()
nreqs(1),
nclients(1),
nthreads(1),
max_concurrent_streams(-1),
max_concurrent_streams(1),
window_bits(30),
connection_window_bits(30),
rate(0),
@ -96,7 +96,9 @@ Config::Config()
port(0),
default_port(0),
verbose(false),
timing_script(false) {}
timing_script(false),
base_uri_unix(false),
unix_addr{} {}
Config::~Config() {
if (base_uri_unix) {
@ -200,7 +202,9 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
auto client = static_cast<Client *>(w->data);
client->restart_timeout();
if (client->do_read() != 0) {
client->fail();
if (client->try_again_or_fail() == 0) {
return;
}
delete client;
return;
}
@ -451,7 +455,7 @@ void Client::restart_timeout() {
}
}
void Client::fail() {
int Client::try_again_or_fail() {
disconnect();
if (new_connection_requested) {
@ -469,13 +473,21 @@ void Client::fail() {
// Keep using current address
if (connect() == 0) {
return;
return 0;
}
std::cerr << "client could not connect to host" << std::endl;
}
}
process_abandoned_streams();
return -1;
}
void Client::fail() {
disconnect();
process_abandoned_streams();
}
void Client::disconnect() {
@ -1384,7 +1396,7 @@ std::string get_reqline(const char *uri, const http_parser_url &u) {
std::string reqline;
if (util::has_uri_field(u, UF_PATH)) {
reqline = util::get_uri_field(uri, u, UF_PATH);
reqline = util::get_uri_field(uri, u, UF_PATH).str();
} else {
reqline = "/";
}
@ -1425,8 +1437,8 @@ bool parse_base_uri(std::string base_uri) {
return false;
}
config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA);
config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST);
config.scheme = util::get_uri_field(base_uri.c_str(), u, UF_SCHEMA).str();
config.host = util::get_uri_field(base_uri.c_str(), u, UF_HOST).str();
config.default_port = util::get_default_port(base_uri.c_str(), u);
if (util::has_uri_field(u, UF_PORT)) {
config.port = u.port;

View File

@ -307,6 +307,12 @@ struct Client {
int connect();
void disconnect();
void fail();
// Call this function when do_read() returns -1. This function
// tries to connect to the remote host again if it is requested. If
// so, this function returns 0, and this object should be retained.
// Otherwise, this function returns -1, and this object should be
// deleted.
int try_again_or_fail();
void timeout();
void restart_timeout();
int submit_request();

File diff suppressed because it is too large Load Diff

View File

@ -39,12 +39,14 @@
#include "util.h"
#include "memchunk.h"
#include "template.h"
#include "allocator.h"
namespace nghttp2 {
struct Header {
Header(std::string name, std::string value, bool no_index = false,
int16_t token = -1)
int32_t token = -1)
: name(std::move(name)),
value(std::move(value)),
token(token),
@ -62,11 +64,33 @@ struct Header {
std::string name;
std::string value;
int16_t token;
int32_t token;
bool no_index;
};
struct HeaderRef {
HeaderRef(const StringRef &name, const StringRef &value,
bool no_index = false, int32_t token = -1)
: name(name), value(value), token(token), no_index(no_index) {}
HeaderRef() : token(-1), no_index(false) {}
bool operator==(const HeaderRef &other) const {
return name == other.name && value == other.value;
}
bool operator<(const HeaderRef &rhs) const {
return name < rhs.name || (name == rhs.name && value < rhs.value);
}
StringRef name;
StringRef value;
int32_t token;
bool no_index;
};
using Headers = std::vector<Header>;
using HeaderRefs = std::vector<HeaderRef>;
namespace http2 {
@ -74,9 +98,9 @@ std::string get_status_string(unsigned int status_code);
// Returns string version of |status_code|. This function can handle
// only predefined status code. Otherwise, returns nullptr.
const char *stringify_status(unsigned int status_code);
StringRef stringify_status(unsigned int status_code);
void capitalize(DefaultMemchunks *buf, const std::string &s);
void capitalize(DefaultMemchunks *buf, const StringRef &s);
// Returns true if |value| is LWS
bool lws(const char *value);
@ -89,25 +113,22 @@ void copy_url_component(std::string &dest, const http_parser_url *u, int field,
Headers::value_type to_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token);
bool no_index, int32_t token);
// Add name/value pairs to |nva|. If |no_index| is true, this
// name/value pair won't be indexed when it is forwarded to the next
// hop. This function strips white spaces around |value|.
void add_header(Headers &nva, const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, bool no_index,
int16_t token);
int32_t token);
// Returns pointer to the entry in |nva| which has name |name|. If
// more than one entries which have the name |name|, last occurrence
// in |nva| is returned. If no such entry exist, returns nullptr.
const Headers::value_type *get_header(const Headers &nva, const char *name);
// Returns nv->second if nv is not nullptr. Otherwise, returns "".
std::string value_to_str(const Headers::value_type *nv);
// Returns true if the value of |nv| is not empty.
bool non_empty_value(const Headers::value_type *nv);
bool non_empty_value(const HeaderRefs::value_type *nv);
// Creates nghttp2_nv using |name| and |value| and returns it. The
// returned value only references the data pointer to name.c_str() and
@ -116,9 +137,15 @@ bool non_empty_value(const Headers::value_type *nv);
nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index = false);
nghttp2_nv make_nv(const StringRef &name, const StringRef &value,
bool no_index = false);
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index = false);
nghttp2_nv make_nv_nocopy(const StringRef &name, const StringRef &value,
bool no_index = false);
// Create nghttp2_nv from string literal |name| and |value|.
template <size_t N, size_t M>
constexpr nghttp2_nv make_nv_ll(const char(&name)[N], const char(&value)[M]) {
@ -163,19 +190,20 @@ nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const StringRef &value) {
// before this call (its element's token field is assigned). Certain
// headers, including disallowed headers in HTTP/2 spec and headers
// which require special handling (i.e. via), are not copied.
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva,
const HeaderRefs &headers);
// Just like copy_headers_to_nva(), but this adds
// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers);
const HeaderRefs &headers);
// Appends HTTP/1.1 style header lines to |buf| from headers in
// |headers|. |headers| must be indexed before this call (its
// element's token field is assigned). Certain headers, which
// requires special handling (i.e. via and cookie), are not appended.
void build_http1_headers_from_headers(DefaultMemchunks *buf,
const Headers &headers);
const HeaderRefs &headers);
// Return positive window_size_increment if WINDOW_UPDATE should be
// sent for the stream |stream_id|. If |stream_id| == 0, this function
@ -196,6 +224,8 @@ void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen);
// Dumps name/value pairs in |nva| to |out|.
void dump_nv(FILE *out, const Headers &nva);
void dump_nv(FILE *out, const HeaderRefs &nva);
// Rewrites redirection URI which usually appears in location header
// field. The |uri| is the URI in the location header field. The |u|
// stores the result of parsed |uri|. The |request_authority| is the
@ -209,11 +239,11 @@ void dump_nv(FILE *out, const Headers &nva);
// This function returns the new rewritten URI on success. If the
// location URI is not subject to the rewrite, this function returns
// emtpy string.
std::string rewrite_location_uri(const std::string &uri,
const http_parser_url &u,
const std::string &match_host,
const std::string &request_authority,
const std::string &upstream_scheme);
StringRef rewrite_location_uri(BlockAllocator &balloc, const StringRef &uri,
const http_parser_url &u,
const StringRef &match_host,
const StringRef &request_authority,
const StringRef &upstream_scheme);
// Checks the header name/value pair using nghttp2_check_header_name()
// and nghttp2_check_header_value(). If both function returns nonzero,
@ -222,7 +252,7 @@ int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen);
// Returns parsed HTTP status code. Returns -1 on failure.
int parse_http_status_code(const std::string &src);
int parse_http_status_code(const StringRef &src);
// Header fields to be indexed, except HD_MAXIDX which is convenient
// member to get maximum value.
@ -272,40 +302,24 @@ using HeaderIndex = std::array<int16_t, HD_MAXIDX>;
// cannot be tokenized, returns -1.
int lookup_token(const uint8_t *name, size_t namelen);
int lookup_token(const std::string &name);
int lookup_token(const StringRef &name);
// Initializes |hdidx|, header index. The |hdidx| must point to the
// array containing at least HD_MAXIDX elements.
void init_hdidx(HeaderIndex &hdidx);
// Indexes header |token| using index |idx|.
void index_header(HeaderIndex &hdidx, int16_t token, size_t idx);
// Returns true if HTTP/2 request pseudo header |token| is not indexed
// yet and not -1.
bool check_http2_request_pseudo_header(const HeaderIndex &hdidx, int16_t token);
// Returns true if HTTP/2 response pseudo header |token| is not
// indexed yet and not -1.
bool check_http2_response_pseudo_header(const HeaderIndex &hdidx,
int16_t token);
// Returns true if header field denoted by |token| is allowed for
// HTTP/2.
bool http2_header_allowed(int16_t token);
// Returns true that |hdidx| contains mandatory HTTP/2 request
// headers.
bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx);
void index_header(HeaderIndex &hdidx, int32_t token, size_t idx);
// Returns header denoted by |token| using index |hdidx|.
const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
const Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
const Headers &nva);
Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
Headers::value_type *get_header(const HeaderIndex &hdidx, int32_t token,
Headers &nva);
struct LinkHeader {
// The region of URI is [uri.first, uri.second).
std::pair<const char *, const char *> uri;
// The region of URI. This might not be NULL-terminated.
StringRef uri;
};
// Returns next URI-reference in Link header field value |src| of
@ -314,16 +328,17 @@ struct LinkHeader {
// is ignored during parsing.
std::vector<LinkHeader> parse_link_header(const char *src, size_t len);
// Constructs path by combining base path |base_path| of length
// |base_pathlen| with another path |rel_path| of length
// |rel_pathlen|. The base path and another path can have optional
// Constructs path by combining base path |base_path| with another
// path |rel_path|. The base path and another path can have optional
// query component. This function assumes |base_path| is normalized.
// In other words, it does not contain ".." or "." path components
// and starts with "/" if it is not empty.
std::string path_join(const char *base_path, size_t base_pathlen,
const char *base_query, size_t base_querylen,
const char *rel_path, size_t rel_pathlen,
const char *rel_query, size_t rel_querylen);
std::string path_join(const StringRef &base, const StringRef &base_query,
const StringRef &rel_path, const StringRef &rel_query);
StringRef path_join(BlockAllocator &balloc, const StringRef &base_path,
const StringRef &base_query, const StringRef &rel_path,
const StringRef &rel_query);
// true if response has body, taking into account the request method
// and status code.
@ -338,73 +353,37 @@ bool expect_response_body(int status_code);
// tokenized. If method name cannot be tokenized, returns -1.
int lookup_method_token(const uint8_t *name, size_t namelen);
int lookup_method_token(const std::string &name);
int lookup_method_token(const StringRef &name);
const char *to_method_string(int method_token);
// Returns string representation of |method_token|. This is wrapper
// function over http_method_str from http-parser. If |method_token|
// is not known to http-parser, "<unknown>" is returned. The returned
// StringRef is guaranteed to be NULL-terminated.
StringRef to_method_string(int method_token);
template <typename InputIt>
std::string normalize_path(InputIt first, InputIt last) {
// First, decode %XX for unreserved characters, then do
// http2::join_path
std::string result;
// We won't find %XX if length is less than 3.
if (last - first < 3) {
result.assign(first, last);
} else {
for (; first < last - 2;) {
if (*first == '%') {
if (util::is_hex_digit(*(first + 1)) &&
util::is_hex_digit(*(first + 2))) {
auto c = (util::hex_to_uint(*(first + 1)) << 4) +
util::hex_to_uint(*(first + 2));
if (util::in_rfc3986_unreserved_chars(c)) {
result += c;
first += 3;
continue;
}
result += '%';
result += util::upcase(*(first + 1));
result += util::upcase(*(first + 2));
first += 3;
continue;
}
}
result += *first++;
}
result.append(first, last);
}
return path_join(nullptr, 0, nullptr, 0, result.c_str(), result.size(),
nullptr, 0);
}
StringRef normalize_path(BlockAllocator &balloc, const StringRef &path,
const StringRef &query);
template <typename InputIt>
std::string rewrite_clean_path(InputIt first, InputIt last) {
if (first == last || *first != '/') {
return std::string(first, last);
}
// probably, not necessary most of the case, but just in case.
auto fragment = std::find(first, last, '#');
auto query = std::find(first, fragment, '?');
auto path = normalize_path(first, query);
if (query != fragment) {
path.append(query, fragment);
}
return path;
}
std::string normalize_path(const StringRef &path, const StringRef &query);
// Stores path component of |uri| in *base. Its extracted length is
// stored in *baselen. The extracted path does not include query
// component. This function returns 0 if it succeeds, or -1.
int get_pure_path_component(const char **base, size_t *baselen,
const std::string &uri);
StringRef rewrite_clean_path(BlockAllocator &balloc, const StringRef &src);
// Deduces scheme, authority and path from given |uri| of length
// |len|, and stores them in |scheme|, |authority|, and |path|
// respectively. If |uri| is relative path, path resolution is taken
// palce using path given in |base| of length |baselen|. This
// function returns 0 if it succeeds, or -1.
int construct_push_component(std::string &scheme, std::string &authority,
std::string &path, const char *base,
size_t baselen, const char *uri, size_t len);
// Returns path component of |uri|. The returned path does not
// include query component. This function returns empty string if it
// fails.
StringRef get_pure_path_component(const StringRef &uri);
// Deduces scheme, authority and path from given |uri|, and stores
// them in |scheme|, |authority|, and |path| respectively. If |uri|
// is relative path, path resolution takes place using path given in
// |base| of length |baselen|. This function returns 0 if it
// succeeds, or -1.
int construct_push_component(BlockAllocator &balloc, StringRef &scheme,
StringRef &authority, StringRef &path,
const StringRef &base, const StringRef &uri);
// Copies |src| and return its lower-cased version.
StringRef copy_lower(BlockAllocator &balloc, const StringRef &src);
} // namespace http2

File diff suppressed because it is too large Load Diff

View File

@ -40,9 +40,6 @@ void test_http2_rewrite_location_uri(void);
void test_http2_parse_http_status_code(void);
void test_http2_index_header(void);
void test_http2_lookup_token(void);
void test_http2_check_http2_pseudo_header(void);
void test_http2_http2_header_allowed(void);
void test_http2_mandatory_request_headers_presence(void);
void test_http2_parse_link_header(void);
void test_http2_path_join(void);
void test_http2_normalize_path(void);

61
src/network.h Normal file
View File

@ -0,0 +1,61 @@
/*
* nghttp2 - HTTP/2 C Library
*
* Copyright (c) 2016 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef NETWORK_H
#define NETWORK_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif // HAVE_SYS_SOCKET_H
#include <sys/un.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif // HAVE_NETINET_IN_H
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif // HAVE_ARPA_INET_H
namespace nghttp2 {
union sockaddr_union {
sockaddr_storage storage;
sockaddr sa;
sockaddr_in6 in6;
sockaddr_in in;
sockaddr_un un;
};
struct Address {
size_t len;
union sockaddr_union su;
};
} // namespace nghttp2
#endif // NETWORK_H

View File

@ -187,7 +187,7 @@ int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
std::string Request::make_reqpath() const {
std::string path = util::has_uri_field(u, UF_PATH)
? util::get_uri_field(uri.c_str(), u, UF_PATH)
? util::get_uri_field(uri.c_str(), u, UF_PATH).str()
: "/";
if (util::has_uri_field(u, UF_QUERY)) {
path += '?';
@ -200,21 +200,20 @@ std::string Request::make_reqpath() const {
namespace {
// Perform special handling |host| if it is IPv6 literal and includes
// zone ID per RFC 6874.
std::string decode_host(std::string host) {
std::string decode_host(const StringRef &host) {
auto zone_start = std::find(std::begin(host), std::end(host), '%');
if (zone_start == std::end(host) ||
!util::ipv6_numeric_addr(
std::string(std::begin(host), zone_start).c_str())) {
return host;
return host.str();
}
// case: ::1%
if (zone_start + 1 == std::end(host)) {
host.pop_back();
return host;
return StringRef{host.c_str(), host.size() - 1}.str();
}
// case: ::1%12 or ::1%1
if (zone_start + 3 >= std::end(host)) {
return host;
return host.str();
}
// If we see "%25", followed by more characters, then decode %25 as
// '%'.
@ -222,9 +221,9 @@ std::string decode_host(std::string host) {
? zone_start + 3
: zone_start + 1;
auto zone_id = util::percent_decode(zone_id_src, std::end(host));
host.erase(zone_start + 1, std::end(host));
host += zone_id;
return host;
auto res = std::string(std::begin(host), zone_start + 1);
res += zone_id;
return res;
}
} // namespace
@ -273,34 +272,7 @@ bool Request::is_ipv6_literal_addr() const {
}
}
bool Request::response_pseudo_header_allowed(int16_t token) const {
if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__STATUS:
return res_hdidx[token] == -1;
default:
return false;
}
}
bool Request::push_request_pseudo_header_allowed(int16_t token) const {
if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
return false;
}
switch (token) {
case http2::HD__AUTHORITY:
case http2::HD__METHOD:
case http2::HD__PATH:
case http2::HD__SCHEME:
return req_hdidx[token] == -1;
default:
return false;
}
}
Headers::value_type *Request::get_res_header(int16_t token) {
Headers::value_type *Request::get_res_header(int32_t token) {
auto idx = res_hdidx[token];
if (idx == -1) {
return nullptr;
@ -308,7 +280,7 @@ Headers::value_type *Request::get_res_header(int16_t token) {
return &res_nva[idx];
}
Headers::value_type *Request::get_req_header(int16_t token) {
Headers::value_type *Request::get_req_header(int32_t token) {
auto idx = req_hdidx[token];
if (idx == -1) {
return nullptr;
@ -376,7 +348,7 @@ int submit_request(HttpClient *client, const Headers &headers, Request *req) {
auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
{":path", path},
{":scheme", scheme},
{":scheme", scheme.str()},
{":authority", client->hostport},
{"accept", "*/*"},
{"accept-encoding", "gzip, deflate"},
@ -1282,7 +1254,8 @@ void HttpClient::update_hostport() {
if (reqvec.empty()) {
return;
}
scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA)
.str();
std::stringstream ss;
if (reqvec[0]->is_ipv6_literal_addr()) {
// we may have zone ID, which must start with "%25", or "%". RFC
@ -1627,7 +1600,7 @@ void check_response_header(nghttp2_session *session, Request *req) {
return;
}
auto status = http2::parse_http_status_code(status_hd->value);
auto status = http2::parse_http_status_code(StringRef{status_hd->value});
if (status == -1) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
NGHTTP2_PROTOCOL_ERROR);
@ -2285,6 +2258,9 @@ int run(char **uris, int n) {
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
callbacks, verbose_on_invalid_frame_recv_callback);
nghttp2_session_callbacks_set_error_callback(callbacks,
verbose_error_callback);
}
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
@ -2408,7 +2384,7 @@ int run(char **uris, int n) {
}
requests.clear();
}
prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA).str();
prev_host = std::move(host);
prev_port = port;
}

View File

@ -125,11 +125,8 @@ struct Request {
bool is_ipv6_literal_addr() const;
bool response_pseudo_header_allowed(int16_t token) const;
bool push_request_pseudo_header_allowed(int16_t token) const;
Headers::value_type *get_res_header(int16_t token);
Headers::value_type *get_req_header(int16_t token);
Headers::value_type *get_res_header(int32_t token);
Headers::value_type *get_req_header(int32_t token);
void record_request_start_time();
void record_response_start_time();

View File

@ -33,6 +33,7 @@
#include "shrpx_ssl_test.h"
#include "shrpx_downstream_test.h"
#include "shrpx_config_test.h"
#include "shrpx_worker_test.h"
#include "http2_test.h"
#include "util_test.h"
#include "nghttp2_gzip_test.h"
@ -89,12 +90,6 @@ int main(int argc, char *argv[]) {
shrpx::test_http2_index_header) ||
!CU_add_test(pSuite, "http2_lookup_token",
shrpx::test_http2_lookup_token) ||
!CU_add_test(pSuite, "http2_check_http2_pseudo_header",
shrpx::test_http2_check_http2_pseudo_header) ||
!CU_add_test(pSuite, "http2_http2_header_allowed",
shrpx::test_http2_http2_header_allowed) ||
!CU_add_test(pSuite, "http2_mandatory_request_headers_presence",
shrpx::test_http2_mandatory_request_headers_presence) ||
!CU_add_test(pSuite, "http2_parse_link_header",
shrpx::test_http2_parse_link_header) ||
!CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) ||
@ -106,8 +101,8 @@ int main(int argc, char *argv[]) {
shrpx::test_http2_get_pure_path_component) ||
!CU_add_test(pSuite, "http2_construct_push_component",
shrpx::test_http2_construct_push_component) ||
!CU_add_test(pSuite, "downstream_field_store_index_headers",
shrpx::test_downstream_field_store_index_headers) ||
!CU_add_test(pSuite, "downstream_field_store_append_last_header",
shrpx::test_downstream_field_store_append_last_header) ||
!CU_add_test(pSuite, "downstream_field_store_header",
shrpx::test_downstream_field_store_header) ||
!CU_add_test(pSuite, "downstream_crumble_request_cookie",
@ -124,10 +119,12 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_config_read_tls_ticket_key_file) ||
!CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256",
shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) ||
!CU_add_test(pSuite, "config_match_downstream_addr_group",
shrpx::test_shrpx_config_match_downstream_addr_group) ||
!CU_add_test(pSuite, "worker_match_downstream_addr_group",
shrpx::test_shrpx_worker_match_downstream_addr_group) ||
!CU_add_test(pSuite, "http_create_forwarded",
shrpx::test_shrpx_http_create_forwarded) ||
!CU_add_test(pSuite, "http_create_via_header_value",
shrpx::test_shrpx_http_create_via_header_value) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
!CU_add_test(pSuite, "util_inp_strlower",
@ -147,6 +144,9 @@ int main(int argc, char *argv[]) {
!CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
!CU_add_test(pSuite, "util_ipv6_numeric_addr",
shrpx::test_util_ipv6_numeric_addr) ||
!CU_add_test(pSuite, "util_utos", shrpx::test_util_utos) ||
!CU_add_test(pSuite, "util_make_string_ref_uint",
shrpx::test_util_make_string_ref_uint) ||
!CU_add_test(pSuite, "util_utos_unit", shrpx::test_util_utos_unit) ||
!CU_add_test(pSuite, "util_utos_funit", shrpx::test_util_utos_funit) ||
!CU_add_test(pSuite, "util_parse_uint_with_unit",

View File

@ -199,18 +199,18 @@ int chown_to_running_user(const char *path) {
namespace {
void save_pid() {
std::ofstream out(get_config()->pid_file.get(), std::ios::binary);
std::ofstream out(get_config()->pid_file.c_str(), std::ios::binary);
out << get_config()->pid << "\n";
out.close();
if (!out) {
LOG(ERROR) << "Could not save PID to file " << get_config()->pid_file.get();
LOG(ERROR) << "Could not save PID to file " << get_config()->pid_file;
exit(EXIT_FAILURE);
}
if (get_config()->uid != 0) {
if (chown_to_running_user(get_config()->pid_file.get()) == -1) {
if (chown_to_running_user(get_config()->pid_file.c_str()) == -1) {
auto error = errno;
LOG(WARN) << "Changing owner of pid file " << get_config()->pid_file.get()
LOG(WARN) << "Changing owner of pid file " << get_config()->pid_file
<< " failed: " << strerror(error);
}
}
@ -656,7 +656,7 @@ int create_tcp_server_socket(UpstreamAddr &faddr,
}
faddr.fd = fd;
faddr.hostport = util::make_http_hostport(host.data(), faddr.port);
faddr.hostport = util::make_http_hostport(StringRef{host.data()}, faddr.port);
LOG(NOTICE) << "Listening on " << faddr.hostport;
@ -946,7 +946,7 @@ int event_loop() {
redirect_stderr_to_errorlog();
}
if (get_config()->pid_file) {
if (!get_config()->pid_file.empty()) {
save_pid();
}
@ -1040,7 +1040,7 @@ void fill_default_config() {
*mod_config() = {};
mod_config()->num_worker = 1;
mod_config()->conf_path = strcopy("/etc/nghttpx/nghttpx.conf");
mod_config()->conf_path = "/etc/nghttpx/nghttpx.conf";
mod_config()->pid = getpid();
auto &tlsconf = mod_config()->tls;
@ -1067,8 +1067,7 @@ void fill_default_config() {
auto &ocspconf = tlsconf.ocsp;
// ocsp update interval = 14400 secs = 4 hours, borrowed from h2o
ocspconf.update_interval = 4_h;
ocspconf.fetch_ocsp_response_file =
strcopy(PKGDATADIR "/fetch-ocsp-response");
ocspconf.fetch_ocsp_response_file = PKGDATADIR "/fetch-ocsp-response";
}
{
@ -1078,10 +1077,10 @@ void fill_default_config() {
}
tlsconf.session_timeout = std::chrono::hours(12);
tlsconf.downstream_session_cache_per_worker = 10000;
auto &httpconf = mod_config()->http;
httpconf.server_name = "nghttpx nghttp2/" NGHTTP2_VERSION;
httpconf.server_name =
StringRef::from_lit("nghttpx nghttp2/" NGHTTP2_VERSION);
httpconf.no_host_rewrite = true;
httpconf.request_header_field_buffer = 64_k;
httpconf.max_request_header_fields = 100;
@ -1098,6 +1097,7 @@ void fill_default_config() {
// HTTP/2 SPDY/3.1 has connection-level flow control. The default
// window size for HTTP/2 is 64KiB - 1. SPDY/3's default is 64KiB
upstreamconf.connection_window_bits = 16;
upstreamconf.max_concurrent_streams = 100;
nghttp2_option_new(&upstreamconf.option);
nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1);
@ -1107,22 +1107,21 @@ void fill_default_config() {
{
auto &downstreamconf = http2conf.downstream;
downstreamconf.window_bits = 16;
downstreamconf.connection_window_bits = 16;
downstreamconf.connection_window_bits = 30;
downstreamconf.max_concurrent_streams = 100;
nghttp2_option_new(&downstreamconf.option);
nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1);
nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100);
}
http2conf.max_concurrent_streams = 100;
auto &loggingconf = mod_config()->logging;
{
auto &accessconf = loggingconf.access;
accessconf.format = parse_log_format(DEFAULT_ACCESSLOG_FORMAT);
auto &errorconf = loggingconf.error;
errorconf.file = strcopy("/dev/stderr");
errorconf.file = "/dev/stderr";
}
loggingconf.syslog_facility = LOG_DAEMON;
@ -1167,6 +1166,7 @@ void fill_default_config() {
downstreamconf.request_buffer_size = 16_k;
downstreamconf.response_buffer_size = 128_k;
downstreamconf.family = AF_UNSPEC;
downstreamconf.no_tls = true;
}
}
@ -1190,48 +1190,55 @@ void print_help(std::ostream &out) {
print_usage(out);
out << R"(
<PRIVATE_KEY>
Set path to server's private key. Required unless -p,
--client or --frontend-no-tls are given.
<CERT> Set path to server's certificate. Required unless -p,
--client or --frontend-no-tls are given. To make OCSP
stapling work, this must be absolute path.
Set path to server's private key. Required unless
--frontend-no-tls are given.
<CERT> Set path to server's certificate. Required unless
--frontend-no-tls are given. To make OCSP stapling
work, this must be an absolute path.
Options:
The options are categorized into several groups.
Connections:
-b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;<PATTERN>[:...]]
-b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][;proto=<PROTO>]]
Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX
domain socket can be specified by prefixing path name
with "unix:" (e.g., unix:/var/run/backend.sock).
Optionally, if <PATTERN>s are given, the backend address
is only used if request matches the pattern. If -s or
-p is used, <PATTERN>s are ignored. The pattern
matching is closely designed to ServeMux in net/http
package of Go programming language. <PATTERN> consists
of path, host + path or just host. The path must start
with "/". If it ends with "/", it matches all request
path in its subtree. To deal with the request to the
directory without trailing slash, the path which ends
with "/" also matches the request path which only lacks
trailing '/' (e.g., path "/foo/" matches request path
"/foo"). If it does not end with "/", it performs exact
match against the request path. If host is given, it
performs exact match against the request host. If host
alone is given, "/" is appended to it, so that it
matches all request paths under the host (e.g.,
specifying "nghttp2.org" equals to "nghttp2.org/").
is only used if request matches the pattern. If
--http2-proxy is used, <PATTERN>s are ignored. The
pattern matching is closely designed to ServeMux in
net/http package of Go programming language. <PATTERN>
consists of path, host + path or just host. The path
must start with "/". If it ends with "/", it matches
all request path in its subtree. To deal with the
request to the directory without trailing slash, the
path which ends with "/" also matches the request path
which only lacks trailing '/' (e.g., path "/foo/"
matches request path "/foo"). If it does not end with
"/", it performs exact match against the request path.
If host is given, it performs exact match against the
request host. If host alone is given, "/" is appended
to it, so that it matches all request paths under the
host (e.g., specifying "nghttp2.org" equals to
"nghttp2.org/").
Patterns with host take precedence over patterns with
just path. Then, longer patterns take precedence over
shorter ones, breaking a tie by the order of the
appearance in the configuration.
shorter ones.
If <PATTERN> is omitted, "/" is used as pattern, which
matches all request paths (catch-all pattern). The
catch-all backend must be given.
Host can include "*" in the left most position to
indicate wildcard match (only suffix match is done).
For example, host pattern "*www.nghttp2.org" matches
against "www.nghttp2.org" and "1www.ngttp2.org", but
does not match against "nghttp2.org". The exact hosts
match takes precedence over the wildcard hosts match.
If <PATTERN> is omitted or empty string, "/" is used as
pattern, which matches all request paths (catch-all
pattern). The catch-all backend must be given.
When doing a match, nghttpx made some normalization to
pattern, request host and path. For host part, they are
@ -1254,6 +1261,15 @@ Connections:
The backend addresses sharing same <PATTERN> are grouped
together forming load balancing group.
Optionally, backend application protocol can be
specified in <PROTO>. All that share the same <PATTERN>
must have the same <PROTO> value if it is given.
<PROTO> should be one of the following list without
quotes: "h2", "http/1.1". The default value of <PROTO>
is "http/1.1". Note that usually "h2" refers to HTTP/2
over TLS. But in this option, it may mean HTTP/2 over
cleartext TCP unless --backend-tls is used.
Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted.
@ -1292,16 +1308,8 @@ Connections:
--backend-write-timeout options.
--accept-proxy-protocol
Accept PROXY protocol version 1 on frontend connection.
--backend-no-tls
Disable SSL/TLS on backend connections. For HTTP/2
backend connections, TLS is enabled by default. For
HTTP/1 backend connections, TLS is disabled by default,
and can be enabled by --backend-http1-tls option. If
both --backend-no-tls and --backend-http1-tls options
are used, --backend-no-tls has the precedence.
--backend-http1-tls
Enable SSL/TLS on backend HTTP/1 connections. See also
--backend-no-tls option.
--backend-tls
Enable SSL/TLS on backend connections.
Performance:
-n, --workers=<N>
@ -1354,31 +1362,24 @@ Performance:
accepts. Setting 0 means unlimited.
Default: )" << get_config()->conn.upstream.worker_connections
<< R"(
--backend-http2-connections-per-worker=<N>
Set maximum number of backend HTTP/2 physical
connections per worker. If pattern is used in -b
option, this limit is applied to each pattern group (in
other words, each pattern group can have maximum <N>
HTTP/2 connections). The default value is 0, which
means that the value is adjusted to the number of
backend addresses. If pattern is used, this adjustment
is done for each pattern group.
--backend-http1-connections-per-host=<N>
Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful
when -s option is used. The origin host is determined
by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of
connections per frontend for default mode, use
--backend-http1-connections-per-frontend.
--backend-connections-per-host=<N>
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per origin host.
This option is meaningful when --http2-proxy option is
used. The origin host is determined by authority
portion of request URI (or :authority header field for
HTTP/2). To limit the number of connections per
frontend for default mode, use
--backend-connections-per-frontend.
Default: )" << get_config()->conn.downstream.connections_per_host
<< R"(
--backend-http1-connections-per-frontend=<N>
Set maximum number of backend concurrent HTTP/1
connections per frontend. This option is only used for
default mode. 0 means unlimited. To limit the number
of connections per host for HTTP/2 or SPDY proxy mode
(-s option), use --backend-http1-connections-per-host.
--backend-connections-per-frontend=<N>
Set maximum number of backend concurrent connections
(and/or streams in case of HTTP/2) per frontend. This
option is only used for default mode. 0 means
unlimited. To limit the number of connections per host
with --http2-proxy option, use
--backend-connections-per-host.
Default: )"
<< get_config()->conn.downstream.connections_per_frontend << R"(
--rlimit-nofile=<N>
@ -1579,8 +1580,8 @@ SSL/TLS:
--fetch-ocsp-response-file=<PATH>
Path to fetch-ocsp-response script file. It should be
absolute path.
Default: )"
<< get_config()->tls.ocsp.fetch_ocsp_response_file.get() << R"(
Default: )" << get_config()->tls.ocsp.fetch_ocsp_response_file
<< R"(
--ocsp-update-interval=<DURATION>
Set interval to update OCSP response cache.
Default: )"
@ -1630,17 +1631,20 @@ SSL/TLS:
Allow black listed cipher suite on HTTP/2 connection.
See https://tools.ietf.org/html/rfc7540#appendix-A for
the complete HTTP/2 cipher suites black list.
--backend-tls-session-cache-per-worker=<N>
Set the maximum number of backend TLS session cache
stored per worker.
Default: )"
<< get_config()->tls.downstream_session_cache_per_worker << R"(
HTTP/2 and SPDY:
-c, --http2-max-concurrent-streams=<N>
-c, --frontend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
HTTP/2 and SPDY session.
Default: )" << get_config()->http2.max_concurrent_streams << R"(
frontend HTTP/2 and SPDY session.
Default: )"
<< get_config()->http2.upstream.max_concurrent_streams << R"(
--backend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
backend HTTP/2 session. This sets maximum number of
concurrent opened pushed streams. The maximum number of
concurrent requests are set by a remote server.
Default: )"
<< get_config()->http2.downstream.max_concurrent_streams << R"(
--frontend-http2-window-bits=<N>
Sets the per-stream initial window size of HTTP/2 SPDY
frontend connection. For HTTP/2, the size is 2**<N>-1.
@ -1674,37 +1678,20 @@ HTTP/2 and SPDY:
Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are
HTTP/2 (which implies --http2-bridge or --client mode).
In this case, server push from backend session is
relayed to frontend, and server push via Link header
field is also supported. HTTP SPDY frontend does not
support server push.
HTTP/2 in default mode. In this case, server push from
backend session is relayed to frontend, and server push
via Link header field is also supported. SPDY frontend
does not support server push.
Mode:
(default mode)
Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. If
--frontend-no-tls is used, accept HTTP/2 and HTTP/1.1.
The incoming HTTP/1.1 connection can be upgraded to
HTTP/2 through HTTP Upgrade. The protocol to the
backend is HTTP/1.1.
HTTP/2 through HTTP Upgrade.
-s, --http2-proxy
Like default mode, but enable secure proxy mode.
--http2-bridge
Like default mode, but communicate with the backend in
HTTP/2 over SSL/TLS. Thus the incoming all connections
are converted to HTTP/2 connection and relayed to the
backend. See --backend-http-proxy-uri option if you are
behind the proxy and want to connect to the outside
HTTP/2 proxy.
--client Accept HTTP/2 and HTTP/1.1 without SSL/TLS. The
incoming HTTP/1.1 connection can be upgraded to HTTP/2
connection through HTTP Upgrade. The protocol to the
backend is HTTP/2. To use nghttpx as a forward proxy,
use -p option instead.
-p, --client-proxy
Like --client option, but it also requires the request
path from frontend must be an absolute URI, suitable for
use as a forward proxy.
Like default mode, but enable forward proxy. This is so
called HTTP/2 proxy mode.
Logging:
-L, --log-level=<LEVEL>
@ -1753,7 +1740,7 @@ Logging:
Set path to write error log. To reopen file, send USR1
signal to nghttpx. stderr will be redirected to the
error log file unless --errorlog-syslog is used.
Default: )" << get_config()->logging.error.file.get() << R"(
Default: )" << get_config()->logging.error.file << R"(
--errorlog-syslog
Send error log to syslog. If this option is used,
--errorlog-file option is ignored.
@ -1805,15 +1792,13 @@ HTTP:
--no-via Don't append to Via header field. If Via header field
is received, it is left unaltered.
--no-location-rewrite
Don't rewrite location header field on --http2-bridge,
--client and default mode. For --http2-proxy and
--client-proxy mode, location header field will not be
altered regardless of this option.
Don't rewrite location header field in default mode.
When --http2-proxy is used, location header field will
not be altered regardless of this option.
--host-rewrite
Rewrite host and :authority header fields on
--http2-bridge, --client and default mode. For
--http2-proxy and --client-proxy mode, these headers
will not be altered regardless of this option.
Rewrite host and :authority header fields in default
mode. When --http2-proxy is used, these headers will
not be altered regardless of this option.
--altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
Specify protocol ID, port, host and origin of
alternative service. <HOST> and <ORIGIN> are optional.
@ -1894,7 +1879,7 @@ Scripting:
Misc:
--conf=<PATH>
Load configuration from <PATH>.
Default: )" << get_config()->conf_path.get() << R"(
Default: )" << get_config()->conf_path << R"(
--include=<PATH>
Load additional configurations from <PATH>. File <PATH>
is read when configuration parser encountered this
@ -1920,11 +1905,11 @@ namespace {
void process_options(
int argc, char **argv,
std::vector<std::pair<const char *, const char *>> &cmdcfgs) {
if (conf_exists(get_config()->conf_path.get())) {
if (conf_exists(get_config()->conf_path.c_str())) {
std::set<std::string> include_set;
if (load_config(get_config()->conf_path.get(), include_set) == -1) {
if (load_config(get_config()->conf_path.c_str(), include_set) == -1) {
LOG(FATAL) << "Failed to load configuration from "
<< get_config()->conf_path.get();
<< get_config()->conf_path;
exit(EXIT_FAILURE);
}
assert(include_set.empty());
@ -1987,8 +1972,8 @@ void process_options(
{
auto &dumpconf = http2conf.upstream.debug.dump;
if (dumpconf.request_header_file) {
auto path = dumpconf.request_header_file.get();
if (!dumpconf.request_header_file.empty()) {
auto path = dumpconf.request_header_file.c_str();
auto f = open_file_for_write(path);
if (f == nullptr) {
@ -2008,8 +1993,8 @@ void process_options(
}
}
if (dumpconf.response_header_file) {
auto path = dumpconf.response_header_file.get();
if (!dumpconf.response_header_file.empty()) {
auto path = dumpconf.response_header_file.c_str();
auto f = open_file_for_write(path);
if (f == nullptr) {
@ -2062,31 +2047,8 @@ void process_options(
upstreamconf.worker_connections = std::numeric_limits<size_t>::max();
}
if (get_config()->http2_proxy + get_config()->http2_bridge +
get_config()->client_proxy + get_config()->client >
1) {
LOG(FATAL) << "--http2-proxy, --http2-bridge, --client-proxy and --client "
<< "cannot be used at the same time.";
exit(EXIT_FAILURE);
}
if (get_config()->client || get_config()->client_proxy) {
mod_config()->client_mode = true;
upstreamconf.no_tls = true;
}
if (get_config()->client_mode || get_config()->http2_bridge) {
downstreamconf.proto = PROTO_HTTP2;
} else {
downstreamconf.proto = PROTO_HTTP;
}
if (downstreamconf.proto == PROTO_HTTP && !downstreamconf.http1_tls) {
downstreamconf.no_tls = true;
}
if (!upstreamconf.no_tls &&
(!tlsconf.private_key_file || !tlsconf.cert_file)) {
(tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) {
print_usage(std::cerr);
LOG(FATAL) << "Too few arguments";
exit(EXIT_FAILURE);
@ -2094,10 +2056,10 @@ void process_options(
if (!upstreamconf.no_tls && !tlsconf.ocsp.disabled) {
struct stat buf;
if (stat(tlsconf.ocsp.fetch_ocsp_response_file.get(), &buf) != 0) {
if (stat(tlsconf.ocsp.fetch_ocsp_response_file.c_str(), &buf) != 0) {
tlsconf.ocsp.disabled = true;
LOG(WARN) << "--fetch-ocsp-response-file: "
<< tlsconf.ocsp.fetch_ocsp_response_file.get()
<< tlsconf.ocsp.fetch_ocsp_response_file
<< " not found. OCSP stapling has been disabled.";
}
}
@ -2105,28 +2067,55 @@ void process_options(
auto &addr_groups = downstreamconf.addr_groups;
if (addr_groups.empty()) {
DownstreamAddr addr;
DownstreamAddrConfig addr{};
addr.host = ImmutableString::from_lit(DEFAULT_DOWNSTREAM_HOST);
addr.port = DEFAULT_DOWNSTREAM_PORT;
DownstreamAddrGroup g("/");
DownstreamAddrGroupConfig g(StringRef::from_lit("/"));
g.proto = PROTO_HTTP1;
g.addrs.push_back(std::move(addr));
mod_config()->router.add_route(g.pattern.get(), 1, addr_groups.size());
mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size());
addr_groups.push_back(std::move(g));
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
} else if (get_config()->http2_proxy) {
// We don't support host mapping in these cases. Move all
// non-catch-all patterns to catch-all pattern.
DownstreamAddrGroup catch_all("/");
DownstreamAddrGroupConfig catch_all(StringRef::from_lit("/"));
auto proto = PROTO_NONE;
for (auto &g : addr_groups) {
if (proto == PROTO_NONE) {
proto = g.proto;
} else if (proto != g.proto) {
LOG(ERROR) << SHRPX_OPT_BACKEND << ": <PATTERN> was ignored with "
"--http2-proxy, and protocol must "
"be the same for all backends.";
exit(EXIT_FAILURE);
}
std::move(std::begin(g.addrs), std::end(g.addrs),
std::back_inserter(catch_all.addrs));
}
std::vector<DownstreamAddrGroup>().swap(addr_groups);
catch_all.proto = proto;
std::vector<DownstreamAddrGroupConfig>().swap(addr_groups);
std::vector<WildcardPattern>().swap(mod_config()->wildcard_patterns);
// maybe not necessary?
mod_config()->router = Router();
mod_config()->router.add_route(catch_all.pattern.get(), 1,
mod_config()->router.add_route(StringRef{catch_all.pattern},
addr_groups.size());
addr_groups.push_back(std::move(catch_all));
} else {
auto &wildcard_patterns = mod_config()->wildcard_patterns;
std::sort(std::begin(wildcard_patterns), std::end(wildcard_patterns),
[](const WildcardPattern &lhs, const WildcardPattern &rhs) {
return std::lexicographical_compare(
rhs.host.rbegin(), rhs.host.rend(), lhs.host.rbegin(),
lhs.host.rend());
});
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Reverse sorted wildcard hosts (compared from tail to head, "
"and sorted in reverse order):";
for (auto &wp : mod_config()->wildcard_patterns) {
LOG(INFO) << wp.host;
}
}
}
if (LOG_ENABLED(INFO)) {
@ -2136,12 +2125,12 @@ void process_options(
ssize_t catch_all_group = -1;
for (size_t i = 0; i < addr_groups.size(); ++i) {
auto &g = addr_groups[i];
if (util::streq(g.pattern.get(), "/")) {
if (g.pattern == "/") {
catch_all_group = i;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern.get()
<< "'";
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern
<< "', proto=" << strproto(g.proto);
for (auto &addr : g.addrs) {
LOG(INFO) << "group " << i << " -> " << addr.host.c_str()
<< (addr.host_unix ? "" : ":" + util::utos(addr.port));
@ -2150,7 +2139,7 @@ void process_options(
}
if (catch_all_group == -1) {
LOG(FATAL) << "-b: No catch-all backend address is configured";
LOG(FATAL) << "backend: No catch-all backend address is configured";
exit(EXIT_FAILURE);
}
@ -2194,7 +2183,7 @@ void process_options(
addr.hostport = ImmutableString(
util::make_http_hostport(StringRef(addr.host), addr.port));
auto hostport = util::make_hostport(addr.host.c_str(), addr.port);
auto hostport = util::make_hostport(StringRef{addr.host}, addr.port);
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
downstreamconf.family) == -1) {
@ -2202,28 +2191,28 @@ void process_options(
exit(EXIT_FAILURE);
}
LOG(NOTICE) << "Resolved backend address: " << hostport << " -> "
<< util::numeric_hostport(&addr.addr.su.sa, addr.addr.len);
<< util::to_numeric_addr(&addr.addr);
}
}
auto &proxy = mod_config()->downstream_http_proxy;
if (!proxy.host.empty()) {
auto hostport = util::make_hostport(proxy.host.c_str(), proxy.port);
auto hostport = util::make_hostport(StringRef{proxy.host}, proxy.port);
if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port,
AF_UNSPEC) == -1) {
LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport;
exit(EXIT_FAILURE);
}
LOG(NOTICE) << "Backend HTTP proxy address: " << hostport << " -> "
<< util::numeric_hostport(&proxy.addr.su.sa, proxy.addr.len);
<< util::to_numeric_addr(&proxy.addr);
}
{
auto &memcachedconf = tlsconf.session_cache.memcached;
if (memcachedconf.host) {
auto hostport =
util::make_hostport(memcachedconf.host.get(), memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.get(),
if (!memcachedconf.host.empty()) {
auto hostport = util::make_hostport(StringRef{memcachedconf.host},
memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
memcachedconf.port, memcachedconf.family) == -1) {
LOG(FATAL)
<< "Resolving memcached address for TLS session cache failed: "
@ -2231,25 +2220,23 @@ void process_options(
exit(EXIT_FAILURE);
}
LOG(NOTICE) << "Memcached address for TLS session cache: " << hostport
<< " -> " << util::numeric_hostport(&memcachedconf.addr.su.sa,
memcachedconf.addr.len);
<< " -> " << util::to_numeric_addr(&memcachedconf.addr);
}
}
{
auto &memcachedconf = tlsconf.ticket.memcached;
if (memcachedconf.host) {
auto hostport =
util::make_hostport(memcachedconf.host.get(), memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.get(),
if (!memcachedconf.host.empty()) {
auto hostport = util::make_hostport(StringRef{memcachedconf.host},
memcachedconf.port);
if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(),
memcachedconf.port, memcachedconf.family) == -1) {
LOG(FATAL) << "Resolving memcached address for TLS ticket key failed: "
<< hostport;
exit(EXIT_FAILURE);
}
LOG(NOTICE) << "Memcached address for TLS ticket key: " << hostport
<< " -> " << util::numeric_hostport(&memcachedconf.addr.su.sa,
memcachedconf.addr.len);
<< " -> " << util::to_numeric_addr(&memcachedconf.addr);
}
}
@ -2454,8 +2441,6 @@ int main(int argc, char **argv) {
{SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, required_argument, &flag, 104},
{SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, required_argument, &flag, 105},
{SHRPX_OPT_BACKEND_HTTP1_TLS, no_argument, &flag, 106},
{SHRPX_OPT_BACKEND_TLS_SESSION_CACHE_PER_WORKER, required_argument,
&flag, 107},
{SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS, no_argument, &flag, 108},
{SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, required_argument,
&flag, 109},
@ -2471,6 +2456,14 @@ int main(int argc, char **argv) {
{SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
required_argument, &flag, 115},
{SHRPX_OPT_BACKEND_ADDRESS_FAMILY, required_argument, &flag, 116},
{SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, required_argument,
&flag, 117},
{SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, required_argument,
&flag, 118},
{SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND, required_argument, &flag,
119},
{SHRPX_OPT_BACKEND_TLS, no_argument, &flag, 120},
{SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, required_argument, &flag, 121},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -2564,7 +2557,7 @@ int main(int argc, char **argv) {
break;
case 12:
// --conf
mod_config()->conf_path = strcopy(optarg);
mod_config()->conf_path = optarg;
break;
case 14:
// --syslog-facility
@ -2924,11 +2917,6 @@ int main(int argc, char **argv) {
// --backend-http1-tls
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, "yes");
break;
case 107:
// --backend-tls-session-cache-per-worker
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SESSION_CACHE_PER_WORKER,
optarg);
break;
case 108:
// --tls-session-cache-memcached-tls
cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS, "yes");
@ -2971,6 +2959,29 @@ int main(int argc, char **argv) {
// --backend-address-family
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, optarg);
break;
case 117:
// --frontend-http2-max-concurrent-streams
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
optarg);
break;
case 118:
// --backend-http2-max-concurrent-streams
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
optarg);
break;
case 119:
// --backend-connections-per-frontend
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND,
optarg);
break;
case 120:
// --backend-tls
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, "yes");
break;
case 121:
// --backend-connections-per-host
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, optarg);
break;
default:
break;
}

View File

@ -114,8 +114,6 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
int ClientHandler::noop() { return 0; }
int ClientHandler::read_clear() {
ev_timer_again(conn_.loop, &conn_.rt);
for (;;) {
if (rb_.rleft() && on_read() != 0) {
return -1;
@ -124,6 +122,10 @@ int ClientHandler::read_clear() {
rb_.reset();
} else if (rb_.wleft() == 0) {
conn_.rlimit.stopw();
if (reset_conn_rtimer_required_) {
reset_conn_rtimer_required_ = false;
ev_timer_again(conn_.loop, &conn_.rt);
}
return 0;
}
@ -134,6 +136,10 @@ int ClientHandler::read_clear() {
auto nread = conn_.read_clear(rb_.last, rb_.wleft());
if (nread == 0) {
if (reset_conn_rtimer_required_) {
reset_conn_rtimer_required_ = false;
ev_timer_again(conn_.loop, &conn_.rt);
}
return 0;
}
@ -208,8 +214,6 @@ int ClientHandler::tls_handshake() {
}
int ClientHandler::read_tls() {
ev_timer_again(conn_.loop, &conn_.rt);
ERR_clear_error();
for (;;) {
@ -221,6 +225,11 @@ int ClientHandler::read_tls() {
rb_.reset();
} else if (rb_.wleft() == 0) {
conn_.rlimit.stopw();
if (reset_conn_rtimer_required_) {
reset_conn_rtimer_required_ = false;
ev_timer_again(conn_.loop, &conn_.rt);
}
return 0;
}
@ -231,6 +240,11 @@ int ClientHandler::read_tls() {
auto nread = conn_.read_tls(rb_.last, rb_.wleft());
if (nread == 0) {
if (reset_conn_rtimer_required_) {
reset_conn_rtimer_required_ = false;
ev_timer_again(conn_.loop, &conn_.rt);
}
return 0;
}
@ -385,18 +399,14 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
get_config()->conn.upstream.ratelimit.write,
get_config()->conn.upstream.ratelimit.read, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
pinned_http2sessions_(
get_config()->conn.downstream.proto == PROTO_HTTP2
? make_unique<std::vector<ssize_t>>(
get_config()->conn.downstream.addr_groups.size(), -1)
: nullptr),
get_config()->tls.dyn_rec.idle_timeout, PROTO_NONE),
ipaddr_(ipaddr),
port_(port),
faddr_(faddr),
worker_(worker),
left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
should_close_after_write_(false) {
should_close_after_write_(false),
reset_conn_rtimer_required_(false) {
++worker_->get_worker_stat()->num_connections;
@ -502,6 +512,10 @@ void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
}
}
void ClientHandler::signal_reset_upstream_conn_rtimer() {
reset_conn_rtimer_required_ = true;
}
int ClientHandler::validate_next_proto() {
const unsigned char *next_proto = nullptr;
unsigned int next_proto_len;
@ -642,13 +656,18 @@ void ClientHandler::pool_downstream_connection(
if (!dconn->poolable()) {
return;
}
dconn->set_client_handler(nullptr);
auto group = dconn->get_downstream_addr_group();
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get()
<< " in group " << dconn->get_group();
<< " in group " << group;
}
dconn->set_client_handler(nullptr);
auto dconn_pool = worker_->get_dconn_pool();
dconn_pool->add_downstream_connection(std::move(dconn));
auto &dconn_pool = group->dconn_pool;
dconn_pool.add_downstream_connection(std::move(dconn));
}
void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
@ -656,51 +675,55 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
CLOG(INFO, this) << "Removing downstream connection DCONN:" << dconn
<< " from pool";
}
auto dconn_pool = worker_->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool.remove_downstream_connection(dconn);
}
std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection(Downstream *downstream) {
size_t group;
size_t group_idx;
auto &downstreamconf = get_config()->conn.downstream;
auto &groups = downstreamconf.addr_groups;
auto catch_all = downstreamconf.addr_group_catch_all;
auto &groups = worker_->get_downstream_addr_groups();
const auto &req = downstream->request();
// Fast path. If we have one group, it must be catch-all group.
// HTTP/2 and client proxy modes fall in this case.
// proxy mode falls in this case.
if (groups.size() == 1) {
group = 0;
group_idx = 0;
} else if (req.method == HTTP_CONNECT) {
// We don't know how to treat CONNECT request in host-path
// mapping. It most likely appears in proxy scenario. Since we
// have dealt with proxy case already, just use catch-all group.
group = catch_all;
group_idx = catch_all;
} else {
auto &router = get_config()->router;
auto &wildcard_patterns = get_config()->wildcard_patterns;
if (!req.authority.empty()) {
group = match_downstream_addr_group(router, req.authority, req.path,
groups, catch_all);
group_idx =
match_downstream_addr_group(router, wildcard_patterns, req.authority,
req.path, groups, catch_all);
} else {
auto h = req.fs.header(http2::HD_HOST);
if (h) {
group = match_downstream_addr_group(router, h->value, req.path, groups,
catch_all);
group_idx = match_downstream_addr_group(
router, wildcard_patterns, h->value, req.path, groups, catch_all);
} else {
group = match_downstream_addr_group(router, "", req.path, groups,
catch_all);
group_idx =
match_downstream_addr_group(router, wildcard_patterns, StringRef{},
req.path, groups, catch_all);
}
}
}
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Downstream address group: " << group;
CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
}
auto dconn_pool = worker_->get_dconn_pool();
auto dconn = dconn_pool->pop_downstream_connection(group);
auto &group = worker_->get_downstream_addr_groups()[group_idx];
auto &dconn_pool = group.dconn_pool;
auto dconn = dconn_pool.pop_downstream_connection();
if (!dconn) {
if (LOG_ENABLED(INFO)) {
@ -708,22 +731,34 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
<< " Create new one";
}
auto dconn_pool = worker_->get_dconn_pool();
if (downstreamconf.proto == PROTO_HTTP2) {
Http2Session *http2session;
auto &pinned = (*pinned_http2sessions_)[group];
if (pinned == -1) {
http2session = worker_->next_http2_session(group);
pinned = http2session->get_index();
} else {
auto dgrp = worker_->get_dgrp(group);
http2session = dgrp->http2sessions[pinned].get();
if (group.proto == PROTO_HTTP2) {
if (group.http2_freelist.empty()) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this)
<< "http2_freelist is empty; create new Http2Session";
}
auto session = make_unique<Http2Session>(
conn_.loop, worker_->get_cl_ssl_ctx(), worker_, &group);
group.http2_freelist.append(session.release());
}
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
auto http2session = group.http2_freelist.head;
// TODO max_concurrent_streams option must be independent from
// frontend and backend.
if (http2session->max_concurrency_reached(1)) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Maximum streams are reached for Http2Session("
<< http2session
<< "). Remove Http2Session from http2_freelist";
}
group.http2_freelist.remove(http2session);
}
dconn = make_unique<Http2DownstreamConnection>(http2session);
} else {
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, group,
conn_.loop, worker_);
dconn =
make_unique<HttpDownstreamConnection>(&group, conn_.loop, worker_);
}
dconn->set_client_handler(this);
return dconn;
@ -743,10 +778,6 @@ MemchunkPool *ClientHandler::get_mcpool() { return worker_->get_mcpool(); }
SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; }
ConnectBlocker *ClientHandler::get_connect_blocker() const {
return worker_->get_connect_blocker();
}
void ClientHandler::direct_http2_upgrade() {
upstream_ = make_unique<Http2Upstream>(this);
alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
@ -794,11 +825,11 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; }
std::string ClientHandler::get_upstream_scheme() const {
StringRef ClientHandler::get_upstream_scheme() const {
if (conn_.tls.ssl) {
return "https";
return StringRef::from_lit("https");
} else {
return "http";
return StringRef::from_lit("http");
}
}
@ -812,24 +843,35 @@ namespace {
// 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(const Request &req) {
StringRef construct_absolute_request_uri(BlockAllocator &balloc,
const Request &req) {
if (req.authority.empty()) {
return req.path;
}
std::string uri;
auto len = req.authority.size() + req.path.size();
if (req.scheme.empty()) {
len += str_size("http://");
} else {
len += req.scheme.size() + str_size("://");
}
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
if (req.scheme.empty()) {
// We may have to log the request which lacks scheme (e.g.,
// http/1.1 with origin form).
uri += "http://";
p = util::copy_lit(p, "http://");
} else {
uri += req.scheme;
uri += "://";
p = std::copy(std::begin(req.scheme), std::end(req.scheme), p);
p = util::copy_lit(p, "://");
}
uri += req.authority;
uri += req.path;
p = std::copy(std::begin(req.authority), std::end(req.authority), p);
p = std::copy(std::begin(req.path), std::end(req.path), p);
*p = '\0';
return uri;
return StringRef{iov.base, p};
}
} // namespace
@ -838,15 +880,17 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
const auto &req = downstream->request();
const auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
upstream_accesslog(
get_config()->logging.access.format,
LogSpec{
downstream, StringRef(ipaddr_), http2::to_method_string(req.method),
downstream, StringRef{ipaddr_}, http2::to_method_string(req.method),
req.method == HTTP_CONNECT
? StringRef(req.authority)
: (get_config()->http2_proxy || get_config()->client_proxy)
? StringRef(construct_absolute_request_uri(req))
: get_config()->http2_proxy
? StringRef(construct_absolute_request_uri(balloc, req))
: req.path.empty()
? req.method == HTTP_OPTIONS
? StringRef::from_lit("*")
@ -1126,18 +1170,18 @@ int ClientHandler::proxy_protocol_read() {
return on_proxy_protocol_finish();
}
StringRef ClientHandler::get_forwarded_by() {
StringRef ClientHandler::get_forwarded_by() const {
auto &fwdconf = get_config()->http.forwarded;
if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) {
return StringRef(fwdconf.by_obfuscated);
}
return StringRef(faddr_->hostport);
return StringRef{faddr_->hostport};
}
const std::string &ClientHandler::get_forwarded_for() const {
return forwarded_for_;
StringRef ClientHandler::get_forwarded_for() const {
return StringRef{forwarded_for_};
}
} // namespace shrpx

View File

@ -86,6 +86,7 @@ public:
struct ev_loop *get_loop() const;
void reset_upstream_read_timeout(ev_tstamp t);
void reset_upstream_write_timeout(ev_tstamp t);
void signal_reset_upstream_conn_rtimer();
int validate_next_proto();
const std::string &get_ipaddr() const;
const std::string &get_port() const;
@ -99,7 +100,6 @@ public:
get_downstream_connection(Downstream *downstream);
MemchunkPool *get_mcpool();
SSL *get_ssl() const;
ConnectBlocker *get_connect_blocker() const;
// Call this function when HTTP/2 connection header is received at
// the start of the connection.
void direct_http2_upgrade();
@ -109,7 +109,7 @@ public:
int perform_http2_upgrade(HttpsUpstream *http);
bool get_http2_upgrade_allowed() const;
// Returns upstream scheme, either "http" or "https"
std::string get_upstream_scheme() const;
StringRef get_upstream_scheme() const;
void start_immediate_shutdown();
// Writes upstream accesslog using |downstream|. The |downstream|
@ -136,16 +136,15 @@ public:
// Returns string suitable for use in "by" parameter of Forwarded
// header field.
StringRef get_forwarded_by();
StringRef get_forwarded_by() const;
// Returns string suitable for use in "for" parameter of Forwarded
// header field.
const std::string &get_forwarded_for() const;
StringRef get_forwarded_for() const;
private:
Connection conn_;
ev_timer reneg_shutdown_timer_;
std::unique_ptr<Upstream> upstream_;
std::unique_ptr<std::vector<ssize_t>> pinned_http2sessions_;
// IP address of client. If UNIX domain socket is used, this is
// "localhost".
std::string ipaddr_;
@ -163,6 +162,7 @@ private:
// The number of bytes of HTTP/2 client connection header to read
size_t left_connhd_len_;
bool should_close_after_write_;
bool reset_conn_rtimer_required_;
ReadBuf rb_;
};

View File

@ -69,8 +69,6 @@ Config *mod_config() { return config; }
void create_config() { config = new Config(); }
std::string EMPTY_STRING;
TicketKeys::~TicketKeys() {
/* Erase keys from memory */
for (auto &key : keys) {
@ -78,42 +76,6 @@ TicketKeys::~TicketKeys() {
}
}
DownstreamAddr::DownstreamAddr(const DownstreamAddr &other)
: addr(other.addr),
host(other.host),
hostport(other.hostport),
port(other.port),
host_unix(other.host_unix) {}
DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
if (this == &other) {
return *this;
}
addr = other.addr;
host = other.host;
hostport = other.hostport;
port = other.port;
host_unix = other.host_unix;
return *this;
}
DownstreamAddrGroup::DownstreamAddrGroup(const DownstreamAddrGroup &other)
: pattern(strcopy(other.pattern)), addrs(other.addrs) {}
DownstreamAddrGroup &DownstreamAddrGroup::
operator=(const DownstreamAddrGroup &other) {
if (this == &other) {
return *this;
}
pattern = strcopy(other.pattern);
addrs = other.addrs;
return *this;
}
namespace {
int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
const char *hostport, size_t hostportlen) {
@ -607,33 +569,71 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) {
} // namespace
namespace {
// Parses host-path mapping patterns in |src|, and stores mappings in
// config. We will store each host-path pattern found in |src| with
// |addr|. |addr| will be copied accordingly. Also we make a group
// based on the pattern. The "/" pattern is considered as catch-all.
void parse_mapping(const DownstreamAddr &addr, const char *src) {
// Parses host-path mapping patterns in |src_pattern|, and stores
// mappings in config. We will store each host-path pattern found in
// |src| with |addr|. |addr| will be copied accordingly. Also we
// make a group based on the pattern. The "/" pattern is considered
// as catch-all. We also parse protocol specified in |src_proto|.
//
// This function returns 0 if it succeeds, or -1.
int parse_mapping(const DownstreamAddrConfig &addr,
const StringRef &src_pattern, const StringRef &src_proto) {
// This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = util::split_config_str_list(src, ':');
auto mapping = util::split_str(src_pattern, ':');
assert(!mapping.empty());
auto &addr_groups = mod_config()->conn.downstream.addr_groups;
auto proto = PROTO_HTTP1;
if (!src_proto.empty()) {
if (!util::istarts_with_l(src_proto, "proto=")) {
LOG(ERROR) << "backend: proto keyword not found";
return -1;
}
auto protostr = StringRef{std::begin(src_proto) + str_size("proto="),
std::end(src_proto)};
if (protostr.empty()) {
LOG(ERROR) << "backend: protocol is empty";
return -1;
}
if (util::streq_l("h2", std::begin(protostr), protostr.size())) {
proto = PROTO_HTTP2;
} else if (util::streq_l("http/1.1", std::begin(protostr),
protostr.size())) {
proto = PROTO_HTTP1;
} else {
LOG(ERROR) << "backend: unknown protocol " << protostr;
return -1;
}
}
for (const auto &raw_pattern : mapping) {
auto done = false;
std::string pattern;
auto slash = std::find(raw_pattern.first, raw_pattern.second, '/');
if (slash == raw_pattern.second) {
auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/');
if (slash == std::end(raw_pattern)) {
// This effectively makes empty pattern to "/".
pattern.assign(raw_pattern.first, raw_pattern.second);
pattern.assign(std::begin(raw_pattern), std::end(raw_pattern));
util::inp_strlower(pattern);
pattern += '/';
} else {
pattern.assign(raw_pattern.first, slash);
pattern.assign(std::begin(raw_pattern), slash);
util::inp_strlower(pattern);
pattern += http2::normalize_path(slash, raw_pattern.second);
pattern += http2::normalize_path(StringRef{slash, std::end(raw_pattern)},
StringRef{});
}
for (auto &g : addr_groups) {
if (g.pattern.get() == pattern) {
if (g.pattern == pattern) {
if (g.proto != proto) {
LOG(ERROR) << "backend: protocol mismatch. We saw protocol "
<< strproto(g.proto) << " for pattern " << g.pattern
<< ", but another protocol " << strproto(proto);
return -1;
}
g.addrs.push_back(addr);
done = true;
break;
@ -642,14 +642,40 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
if (done) {
continue;
}
DownstreamAddrGroup g(pattern);
DownstreamAddrGroupConfig g(StringRef{pattern});
g.addrs.push_back(addr);
g.proto = proto;
mod_config()->router.add_route(g.pattern.get(), strlen(g.pattern.get()),
addr_groups.size());
if (pattern[0] == '*') {
// wildcard pattern
auto path_first =
std::find(std::begin(g.pattern), std::end(g.pattern), '/');
auto host = StringRef{std::begin(g.pattern) + 1, path_first};
auto path = StringRef{path_first, std::end(g.pattern)};
auto &wildcard_patterns = mod_config()->wildcard_patterns;
auto it = std::find_if(
std::begin(wildcard_patterns), std::end(wildcard_patterns),
[&host](const WildcardPattern &wp) { return wp.host == host; });
if (it == std::end(wildcard_patterns)) {
mod_config()->wildcard_patterns.push_back(
{ImmutableString{std::begin(host), std::end(host)}});
auto &router = mod_config()->wildcard_patterns.back().router;
router.add_route(path, addr_groups.size());
} else {
(*it).router.add_route(path, addr_groups.size());
}
} else {
mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size());
}
addr_groups.push_back(std::move(g));
}
return 0;
}
} // namespace
@ -691,12 +717,15 @@ enum {
SHRPX_OPTID_ALTSVC,
SHRPX_OPTID_BACKEND,
SHRPX_OPTID_BACKEND_ADDRESS_FAMILY,
SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND,
SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST,
SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
SHRPX_OPTID_BACKEND_HTTP1_TLS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
SHRPX_OPTID_BACKEND_IPV4,
SHRPX_OPTID_BACKEND_IPV6,
@ -705,7 +734,7 @@ enum {
SHRPX_OPTID_BACKEND_READ_TIMEOUT,
SHRPX_OPTID_BACKEND_REQUEST_BUFFER,
SHRPX_OPTID_BACKEND_RESPONSE_BUFFER,
SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER,
SHRPX_OPTID_BACKEND_TLS,
SHRPX_OPTID_BACKEND_TLS_SNI_FIELD,
SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
SHRPX_OPTID_BACKLOG,
@ -730,6 +759,7 @@ enum {
SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT,
SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS,
SHRPX_OPTID_FRONTEND_NO_TLS,
@ -949,6 +979,11 @@ int option_lookup_token(const char *name, size_t namelen) {
break;
case 11:
switch (name[10]) {
case 's':
if (util::strieq_l("backend-tl", name, 10)) {
return SHRPX_OPTID_BACKEND_TLS;
}
break;
case 't':
if (util::strieq_l("write-burs", name, 10)) {
return SHRPX_OPTID_WRITE_BURST;
@ -1360,6 +1395,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS;
}
break;
case 't':
if (util::strieq_l("backend-connections-per-hos", name, 27)) {
return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST;
}
break;
}
break;
case 30:
@ -1380,6 +1420,15 @@ int option_lookup_token(const char *name, size_t namelen) {
break;
}
break;
case 32:
switch (name[31]) {
case 'd':
if (util::strieq_l("backend-connections-per-fronten", name, 31)) {
return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND;
}
break;
}
break;
case 33:
switch (name[32]) {
case 'l':
@ -1431,14 +1480,14 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER;
}
if (util::strieq_l("backend-tls-session-cache-per-worke", name, 35)) {
return SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER;
}
break;
case 's':
if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS;
}
if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS;
}
break;
}
break;
@ -1453,6 +1502,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) {
return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS;
}
if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) {
return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS;
}
break;
}
break;
@ -1518,19 +1570,17 @@ int parse_config(const char *opt, const char *optarg,
switch (optid) {
case SHRPX_OPTID_BACKEND: {
auto optarglen = strlen(optarg);
const char *pat_delim = strchr(optarg, ';');
if (!pat_delim) {
pat_delim = optarg + optarglen;
}
DownstreamAddr addr;
auto src = StringRef{optarg};
auto addr_end = std::find(std::begin(src), std::end(src), ';');
DownstreamAddrConfig addr{};
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = ImmutableString(path, pat_delim);
auto path = std::begin(src) + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = ImmutableString(path, addr_end);
addr.host_unix = true;
} else {
if (split_host_port(host, sizeof(host), &port, optarg,
pat_delim - optarg) == -1) {
if (split_host_port(host, sizeof(host), &port, &src[0],
addr_end - std::begin(src)) == -1) {
return -1;
}
@ -1538,14 +1588,16 @@ int parse_config(const char *opt, const char *optarg,
addr.port = port;
}
auto mapping = pat_delim < optarg + optarglen ? pat_delim + 1 : pat_delim;
// We may introduce new parameter after additional ';', so don't
// allow extra ';' in pattern for now.
if (strchr(mapping, ';') != nullptr) {
LOG(ERROR) << opt << ": ';' must not be used in pattern";
auto mapping = addr_end == std::end(src) ? addr_end : addr_end + 1;
auto mapping_end = std::find(mapping, std::end(src), ';');
auto proto = mapping_end == std::end(src) ? mapping_end : mapping_end + 1;
auto proto_end = std::find(proto, std::end(src), ';');
if (parse_mapping(addr, StringRef{mapping, mapping_end},
StringRef{proto, proto_end}) != 0) {
return -1;
}
parse_mapping(addr, mapping);
return 0;
}
@ -1600,8 +1652,20 @@ int parse_config(const char *opt, const char *optarg,
#else // !NOTHREADS
return parse_uint(&mod_config()->num_worker, opt, optarg);
#endif // !NOTHREADS
case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2.max_concurrent_streams, opt, optarg);
case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: {
LOG(WARN) << opt << ": deprecated. Use "
<< SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and "
<< SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead.";
size_t n;
if (parse_uint(&n, opt, optarg) != 0) {
return -1;
}
auto &http2conf = mod_config()->http2;
http2conf.upstream.max_concurrent_streams = n;
http2conf.downstream.max_concurrent_streams = n;
return 0;
}
case SHRPX_OPTID_LOG_LEVEL:
if (Log::set_severity_level_by_name(optarg) == -1) {
LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
@ -1618,13 +1682,13 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_HTTP2_BRIDGE:
mod_config()->http2_bridge = util::strieq(optarg, "yes");
return 0;
LOG(ERROR) << opt << ": deprecated. Use backend=<addr>,<port>;;proto=h2 "
"and backend-tls";
return -1;
case SHRPX_OPTID_CLIENT_PROXY:
mod_config()->client_proxy = util::strieq(optarg, "yes");
return 0;
LOG(ERROR) << opt << ": deprecated. Use http2-proxy, frontend-no-tls, "
"backend=<addr>,<port>;;proto=h2 and backend-tls";
return -1;
case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
mod_config()->http.xff.add = util::strieq(optarg, "yes");
@ -1659,7 +1723,7 @@ int parse_config(const char *opt, const char *optarg,
return parse_duration(&mod_config()->http2.timeout.stream_write, opt,
optarg);
case SHRPX_OPTID_ACCESSLOG_FILE:
mod_config()->logging.access.file = strcopy(optarg);
mod_config()->logging.access.file = optarg;
return 0;
case SHRPX_OPTID_ACCESSLOG_SYSLOG:
@ -1671,7 +1735,7 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_ERRORLOG_FILE:
mod_config()->logging.error.file = strcopy(optarg);
mod_config()->logging.error.file = optarg;
return 0;
case SHRPX_OPTID_ERRORLOG_SYSLOG:
@ -1757,15 +1821,15 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_BACKEND_NO_TLS:
mod_config()->conn.downstream.no_tls = util::strieq(optarg, "yes");
LOG(WARN) << opt << ": deprecated. backend connection is not encrypted by "
"default. See also " << SHRPX_OPT_BACKEND_TLS;
return 0;
case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
mod_config()->tls.backend_sni_name = optarg;
return 0;
case SHRPX_OPTID_PID_FILE:
mod_config()->pid_file = strcopy(optarg);
mod_config()->pid_file = optarg;
return 0;
case SHRPX_OPTID_USER: {
@ -1775,14 +1839,14 @@ int parse_config(const char *opt, const char *optarg,
<< strerror(errno);
return -1;
}
mod_config()->user = strcopy(pwd->pw_name);
mod_config()->user = pwd->pw_name;
mod_config()->uid = pwd->pw_uid;
mod_config()->gid = pwd->pw_gid;
return 0;
}
case SHRPX_OPTID_PRIVATE_KEY_FILE:
mod_config()->tls.private_key_file = strcopy(optarg);
mod_config()->tls.private_key_file = optarg;
return 0;
case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: {
@ -1791,16 +1855,16 @@ int parse_config(const char *opt, const char *optarg,
LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
return -1;
}
mod_config()->tls.private_key_passwd = strcopy(passwd);
mod_config()->tls.private_key_passwd = passwd;
return 0;
}
case SHRPX_OPTID_CERTIFICATE_FILE:
mod_config()->tls.cert_file = strcopy(optarg);
mod_config()->tls.cert_file = optarg;
return 0;
case SHRPX_OPTID_DH_PARAM_FILE:
mod_config()->tls.dh_param_file = strcopy(optarg);
mod_config()->tls.dh_param_file = optarg;
return 0;
case SHRPX_OPTID_SUBCERT: {
@ -1841,19 +1905,19 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_CIPHERS:
mod_config()->tls.ciphers = strcopy(optarg);
mod_config()->tls.ciphers = optarg;
return 0;
case SHRPX_OPTID_CLIENT:
mod_config()->client = util::strieq(optarg, "yes");
return 0;
LOG(ERROR) << opt << ": deprecated. Use frontend-no-tls, "
"backend=<addr>,<port>;;proto=h2 and backend-tls";
return -1;
case SHRPX_OPTID_INSECURE:
mod_config()->tls.insecure = util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_CACERT:
mod_config()->tls.cacert = strcopy(optarg);
mod_config()->tls.cacert = optarg;
return 0;
case SHRPX_OPTID_BACKEND_IPV4:
@ -1944,25 +2008,23 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_VERIFY_CLIENT_CACERT:
mod_config()->tls.client_verify.cacert = strcopy(optarg);
mod_config()->tls.client_verify.cacert = optarg;
return 0;
case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE:
mod_config()->tls.client.private_key_file = strcopy(optarg);
mod_config()->tls.client.private_key_file = optarg;
return 0;
case SHRPX_OPTID_CLIENT_CERT_FILE:
mod_config()->tls.client.cert_file = strcopy(optarg);
mod_config()->tls.client.cert_file = optarg;
return 0;
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER:
mod_config()->http2.upstream.debug.dump.request_header_file =
strcopy(optarg);
mod_config()->http2.upstream.debug.dump.request_header_file = optarg;
return 0;
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER:
mod_config()->http2.upstream.debug.dump.response_header_file =
strcopy(optarg);
mod_config()->http2.upstream.debug.dump.response_header_file = optarg;
return 0;
case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING:
@ -2003,7 +2065,7 @@ int parse_config(const char *opt, const char *optarg,
return -1;
}
AltSvc altsvc;
AltSvc altsvc{};
altsvc.protocol_id = std::move(tokens[0]);
@ -2050,7 +2112,11 @@ int parse_config(const char *opt, const char *optarg,
"--host-rewrite option.";
return 0;
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST: {
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST:
LOG(WARN) << opt
<< ": deprecated. Use backend-connections-per-host instead.";
// fall through
case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST: {
int n;
if (parse_uint(&n, opt, optarg) != 0) {
@ -2068,6 +2134,10 @@ int parse_config(const char *opt, const char *optarg,
return 0;
}
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
LOG(WARN) << opt << ": deprecated. Use "
<< SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead.";
// fall through
case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND:
return parse_uint(&mod_config()->conn.downstream.connections_per_frontend,
opt, optarg);
case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
@ -2120,10 +2190,10 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
return parse_uint(&mod_config()->http2.downstream.connections_per_worker,
opt, optarg);
LOG(WARN) << opt << ": deprecated.";
return 0;
case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
mod_config()->tls.ocsp.fetch_ocsp_response_file = strcopy(optarg);
mod_config()->tls.ocsp.fetch_ocsp_response_file = optarg;
return 0;
case SHRPX_OPTID_OCSP_UPDATE_INTERVAL:
@ -2191,7 +2261,7 @@ int parse_config(const char *opt, const char *optarg,
}
auto &memcachedconf = mod_config()->tls.session_cache.memcached;
memcachedconf.host = strcopy(host);
memcachedconf.host = host;
memcachedconf.port = port;
return 0;
@ -2203,7 +2273,7 @@ int parse_config(const char *opt, const char *optarg,
}
auto &memcachedconf = mod_config()->tls.ticket.memcached;
memcachedconf.host = strcopy(host);
memcachedconf.host = host;
memcachedconf.port = port;
return 0;
@ -2244,7 +2314,7 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_MRUBY_FILE:
#ifdef HAVE_MRUBY
mod_config()->mruby_file = strcopy(optarg);
mod_config()->mruby_file = optarg;
#else // !HAVE_MRUBY
LOG(WARN) << opt
<< ": ignored because mruby support is disabled at build time.";
@ -2321,12 +2391,13 @@ int parse_config(const char *opt, const char *optarg,
return 0;
case SHRPX_OPTID_BACKEND_HTTP1_TLS:
mod_config()->conn.downstream.http1_tls = util::strieq(optarg, "yes");
LOG(WARN) << opt << ": deprecated. Use " << SHRPX_OPT_BACKEND_TLS
<< " instead.";
// fall through
case SHRPX_OPTID_BACKEND_TLS:
mod_config()->conn.downstream.no_tls = !util::strieq(optarg, "yes");
return 0;
case SHRPX_OPTID_BACKEND_TLS_SESSION_CACHE_PER_WORKER:
return parse_uint(&mod_config()->tls.downstream_session_cache_per_worker,
opt, optarg);
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS:
mod_config()->tls.session_cache.memcached.tls = util::strieq(optarg, "yes");
@ -2360,6 +2431,12 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY:
return parse_address_family(&mod_config()->conn.downstream.family, opt,
optarg);
case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2.upstream.max_concurrent_streams, opt,
optarg);
case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2.downstream.max_concurrent_streams,
opt, optarg);
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";
@ -2539,99 +2616,20 @@ int int_syslog_facility(const char *strfacility) {
return -1;
}
namespace {
size_t
match_downstream_addr_group_host(const Router &router, const std::string &host,
const char *path, size_t pathlen,
const std::vector<DownstreamAddrGroup> &groups,
size_t catch_all) {
if (pathlen == 0 || *path != '/') {
auto group = router.match(host, "/", 1);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host
<< ", matched pattern=" << groups[group].pattern.get();
}
return group;
}
return catch_all;
StringRef strproto(shrpx_proto proto) {
switch (proto) {
case PROTO_NONE:
return StringRef::from_lit("none");
case PROTO_HTTP1:
return StringRef::from_lit("http/1.1");
case PROTO_HTTP2:
return StringRef::from_lit("h2");
case PROTO_MEMCACHED:
return StringRef::from_lit("memcached");
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Perform mapping selection, using host=" << host
<< ", path=" << std::string(path, pathlen);
}
auto group = router.match(host, path, pathlen);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host
<< std::string(path, pathlen)
<< ", matched pattern=" << groups[group].pattern.get();
}
return group;
}
group = router.match("", path, pathlen);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << std::string(path, pathlen)
<< ", matched pattern=" << groups[group].pattern.get();
}
return group;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "None match. Use catch-all pattern";
}
return catch_all;
}
} // namespace
size_t
match_downstream_addr_group(const Router &router, const std::string &hostport,
const std::string &raw_path,
const std::vector<DownstreamAddrGroup> &groups,
size_t catch_all) {
if (std::find(std::begin(hostport), std::end(hostport), '/') !=
std::end(hostport)) {
// We use '/' specially, and if '/' is included in host, it breaks
// our code. Select catch-all case.
return catch_all;
}
auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
auto query = std::find(std::begin(raw_path), fragment, '?');
auto path = raw_path.c_str();
auto pathlen = query - std::begin(raw_path);
if (hostport.empty()) {
return match_downstream_addr_group_host(router, hostport, path, pathlen,
groups, catch_all);
}
std::string host;
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return catch_all;
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return catch_all;
}
host.assign(std::begin(hostport), p + 1);
} else {
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return catch_all;
}
host.assign(std::begin(hostport), p);
}
util::inp_strlower(host);
return match_downstream_addr_group_host(router, host, path, pathlen, groups,
catch_all);
// gcc needs this.
assert(0);
}
} // namespace shrpx

View File

@ -53,12 +53,15 @@
#include "shrpx_router.h"
#include "template.h"
#include "http2.h"
#include "network.h"
using namespace nghttp2;
namespace shrpx {
struct LogFragment;
class ConnectBlocker;
class Http2Session;
namespace ssl {
@ -208,8 +211,6 @@ constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
constexpr char SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST[] =
"no-http2-cipher-black-list";
constexpr char SHRPX_OPT_BACKEND_HTTP1_TLS[] = "backend-http1-tls";
constexpr char SHRPX_OPT_BACKEND_TLS_SESSION_CACHE_PER_WORKER[] =
"backend-tls-session-cache-per-worker";
constexpr char SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS[] =
"tls-session-cache-memcached-tls";
constexpr char SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE[] =
@ -227,23 +228,19 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE[] =
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY[] =
"tls-ticket-key-memcached-address-family";
constexpr char SHRPX_OPT_BACKEND_ADDRESS_FAMILY[] = "backend-address-family";
constexpr char SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS[] =
"frontend-http2-max-concurrent-streams";
constexpr char SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS[] =
"backend-http2-max-concurrent-streams";
constexpr char SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND[] =
"backend-connections-per-frontend";
constexpr char SHRPX_OPT_BACKEND_TLS[] = "backend-tls";
constexpr char SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST[] =
"backend-connections-per-host";
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
union sockaddr_union {
sockaddr_storage storage;
sockaddr sa;
sockaddr_in6 in6;
sockaddr_in in;
sockaddr_un un;
};
struct Address {
size_t len;
union sockaddr_union su;
};
enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP };
enum shrpx_proto { PROTO_NONE, PROTO_HTTP1, PROTO_HTTP2, PROTO_MEMCACHED };
enum shrpx_forwarded_param {
FORWARDED_NONE = 0,
@ -258,13 +255,7 @@ enum shrpx_forwarded_node_type {
FORWARDED_NODE_IP,
};
// Used inside function if it has to return const reference to empty
// string without defining empty string each time.
extern std::string EMPTY_STRING;
struct AltSvc {
AltSvc() : port(0) {}
std::string protocol_id, host, origin, service;
uint16_t port;
@ -288,17 +279,21 @@ struct UpstreamAddr {
int fd;
};
struct DownstreamAddr {
DownstreamAddr() : addr{}, port(0), host_unix(false) {}
DownstreamAddr(const DownstreamAddr &other);
DownstreamAddr(DownstreamAddr &&) = default;
DownstreamAddr &operator=(const DownstreamAddr &other);
DownstreamAddr &operator=(DownstreamAddr &&other) = default;
struct TLSSessionCache {
// ASN1 representation of SSL_SESSION object. See
// i2d_SSL_SESSION(3SSL).
std::vector<uint8_t> session_data;
// The last time stamp when this cache entry is created or updated.
ev_tstamp last_updated;
};
struct DownstreamAddrConfig {
Address addr;
// backend address. If |host_unix| is true, this is UNIX domain
// socket path.
ImmutableString host;
// <HOST>:<PORT>. This does not treat 80 and 443 specially. If
// |host_unix| is true, this is "localhost".
ImmutableString hostport;
// backend port. 0 if |host_unix| is true.
uint16_t port;
@ -306,15 +301,14 @@ struct DownstreamAddr {
bool host_unix;
};
struct DownstreamAddrGroup {
DownstreamAddrGroup(const std::string &pattern) : pattern(strcopy(pattern)) {}
DownstreamAddrGroup(const DownstreamAddrGroup &other);
DownstreamAddrGroup(DownstreamAddrGroup &&) = default;
DownstreamAddrGroup &operator=(const DownstreamAddrGroup &other);
DownstreamAddrGroup &operator=(DownstreamAddrGroup &&) = default;
struct DownstreamAddrGroupConfig {
DownstreamAddrGroupConfig(const StringRef &pattern)
: pattern(pattern.c_str(), pattern.size()), proto(PROTO_HTTP1) {}
std::unique_ptr<char[]> pattern;
std::vector<DownstreamAddr> addrs;
ImmutableString pattern;
std::vector<DownstreamAddrConfig> addrs;
// Application protocol used in this group
shrpx_proto proto;
};
struct TicketKey {
@ -352,7 +346,9 @@ struct TLSConfig {
struct {
Address addr;
uint16_t port;
std::unique_ptr<char[]> host;
// Hostname of memcached server. This is also used as SNI field
// if TLS is enabled.
ImmutableString host;
// Client private key and certificate for authentication
ImmutableString private_key_file;
ImmutableString cert_file;
@ -379,7 +375,9 @@ struct TLSConfig {
struct {
Address addr;
uint16_t port;
std::unique_ptr<char[]> host;
// Hostname of memcached server. This is also used as SNI field
// if TLS is enabled.
ImmutableString host;
// Client private key and certificate for authentication
ImmutableString private_key_file;
ImmutableString cert_file;
@ -399,7 +397,7 @@ struct TLSConfig {
// OCSP realted configurations
struct {
ev_tstamp update_interval;
std::unique_ptr<char[]> fetch_ocsp_response_file;
ImmutableString fetch_ocsp_response_file;
bool disabled;
} ocsp;
@ -407,14 +405,14 @@ struct TLSConfig {
struct {
// Path to file containing CA certificate solely used for client
// certificate validation
std::unique_ptr<char[]> cacert;
ImmutableString cacert;
bool enabled;
} client_verify;
// Client private key and certificate used in backend connections.
struct {
std::unique_ptr<char[]> private_key_file;
std::unique_ptr<char[]> cert_file;
ImmutableString private_key_file;
ImmutableString cert_file;
} client;
// The list of (private key file, certificate file) pair
@ -425,18 +423,17 @@ struct TLSConfig {
std::vector<std::string> npn_list;
// list of supported SSL/TLS protocol strings.
std::vector<std::string> tls_proto_list;
size_t downstream_session_cache_per_worker;
// Bit mask to disable SSL/TLS protocol versions. This will be
// passed to SSL_CTX_set_options().
long int tls_proto_mask;
std::string backend_sni_name;
std::chrono::seconds session_timeout;
std::unique_ptr<char[]> private_key_file;
std::unique_ptr<char[]> private_key_passwd;
std::unique_ptr<char[]> cert_file;
std::unique_ptr<char[]> dh_param_file;
std::unique_ptr<char[]> ciphers;
std::unique_ptr<char[]> cacert;
ImmutableString private_key_file;
ImmutableString private_key_passwd;
ImmutableString cert_file;
ImmutableString dh_param_file;
ImmutableString ciphers;
ImmutableString cacert;
bool insecure;
bool no_http2_cipher_black_list;
};
@ -478,8 +475,8 @@ struct Http2Config {
struct {
struct {
struct {
std::unique_ptr<char[]> request_header_file;
std::unique_ptr<char[]> response_header_file;
ImmutableString request_header_file;
ImmutableString response_header_file;
FILE *request_header;
FILE *response_header;
} dump;
@ -489,19 +486,19 @@ struct Http2Config {
nghttp2_session_callbacks *callbacks;
size_t window_bits;
size_t connection_window_bits;
size_t max_concurrent_streams;
} upstream;
struct {
nghttp2_option *option;
nghttp2_session_callbacks *callbacks;
size_t window_bits;
size_t connection_window_bits;
size_t connections_per_worker;
size_t max_concurrent_streams;
} downstream;
struct {
ev_tstamp stream_read;
ev_tstamp stream_write;
} timeout;
size_t max_concurrent_streams;
bool no_cookie_crumbling;
bool no_server_push;
};
@ -509,12 +506,12 @@ struct Http2Config {
struct LoggingConfig {
struct {
std::vector<LogFragment> format;
std::unique_ptr<char[]> file;
ImmutableString file;
// Send accesslog to syslog, ignoring accesslog_file.
bool syslog;
} access;
struct {
std::unique_ptr<char[]> file;
ImmutableString file;
// Send errorlog to syslog, ignoring errorlog_file.
bool syslog;
} error;
@ -560,15 +557,13 @@ struct ConnectionConfig {
ev_tstamp write;
ev_tstamp idle_read;
} timeout;
std::vector<DownstreamAddrGroup> addr_groups;
std::vector<DownstreamAddrGroupConfig> addr_groups;
// The index of catch-all group in downstream_addr_groups.
size_t addr_group_catch_all;
size_t connections_per_host;
size_t connections_per_frontend;
size_t request_buffer_size;
size_t response_buffer_size;
// downstream protocol; this will be determined by given options.
shrpx_proto proto;
// Address family of backend connection. One of either AF_INET,
// AF_INET6 or AF_UNSPEC. This is ignored if backend connection
// is made via Unix domain socket.
@ -578,18 +573,27 @@ struct ConnectionConfig {
} downstream;
};
// Wildcard host pattern routing. We strips left most '*' from host
// field. router includes all path pattern sharing same wildcard
// host.
struct WildcardPattern {
ImmutableString host;
Router router;
};
struct Config {
Router router;
std::vector<WildcardPattern> wildcard_patterns;
HttpProxy downstream_http_proxy;
HttpConfig http;
Http2Config http2;
TLSConfig tls;
LoggingConfig logging;
ConnectionConfig conn;
std::unique_ptr<char[]> pid_file;
std::unique_ptr<char[]> conf_path;
std::unique_ptr<char[]> user;
std::unique_ptr<char[]> mruby_file;
ImmutableString pid_file;
ImmutableString conf_path;
ImmutableString user;
ImmutableString mruby_file;
char **original_argv;
char **argv;
char *cwd;
@ -603,11 +607,6 @@ struct Config {
bool verbose;
bool daemon;
bool http2_proxy;
bool http2_bridge;
bool client_proxy;
bool client;
// true if --client or --client-proxy are enabled.
bool client_mode;
};
const Config *get_config();
@ -654,15 +653,8 @@ std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<std::string> &files,
const EVP_CIPHER *cipher, const EVP_MD *hmac);
// Selects group based on request's |hostport| and |path|. |hostport|
// is the value taken from :authority or host header field, and may
// contain port. The |path| may contain query part. We require the
// catch-all pattern in place, so this function always selects one
// group. The catch-all group index is given in |catch_all|. All
// patterns are given in |groups|.
size_t match_downstream_addr_group(
const Router &router, const std::string &hostport, const std::string &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all);
// Returns string representation of |proto|.
StringRef strproto(shrpx_proto proto);
} // namespace shrpx

View File

@ -238,120 +238,4 @@ void test_shrpx_config_read_tls_ticket_key_file_aes_256(void) {
"a..............................b"));
}
void test_shrpx_config_match_downstream_addr_group(void) {
auto groups = std::vector<DownstreamAddrGroup>{
{"nghttp2.org/"},
{"nghttp2.org/alpha/bravo/"},
{"nghttp2.org/alpha/charlie"},
{"nghttp2.org/delta%3A"},
{"www.nghttp2.org/"},
{"[::1]/"},
{"nghttp2.org/alpha/bravo/delta"},
// Check that match is done in the single node
{"example.com/alpha/bravo"},
{"192.168.0.1/alpha/"},
};
Router router;
for (size_t i = 0; i < groups.size(); ++i) {
auto &g = groups[i];
router.add_route(g.pattern.get(), strlen(g.pattern.get()), i);
}
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org", "/", groups,
255));
// port is removed
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org:8080", "/",
groups, 255));
// host is case-insensitive
CU_ASSERT(4 == match_downstream_addr_group(router, "WWW.nghttp2.org",
"/alpha", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/bravo/", groups, 255));
// /alpha/bravo also matches /alpha/bravo/
CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/bravo", groups, 255));
// path part is case-sensitive
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org",
"/Alpha/bravo", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/bravo/charlie", groups,
255));
CU_ASSERT(2 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/charlie", groups, 255));
// pattern which does not end with '/' must match its entirely. So
// this matches to group 0, not group 2.
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/charlie/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.org", "/",
groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "", "/", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "", "alpha", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "foo/bar", "/", groups, 255));
// If path is "*", only match with host + "/".
CU_ASSERT(0 == match_downstream_addr_group(router, "nghttp2.org", "*", groups,
255));
CU_ASSERT(5 ==
match_downstream_addr_group(router, "[::1]", "/", groups, 255));
CU_ASSERT(
5 == match_downstream_addr_group(router, "[::1]:8080", "/", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "[::1", "/", groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, "[::1]8000", "/", groups, 255));
// Check the case where adding route extends tree
CU_ASSERT(6 == match_downstream_addr_group(
router, "nghttp2.org", "/alpha/bravo/delta", groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(router, "nghttp2.org",
"/alpha/bravo/delta/", groups,
255));
// Check the case where query is done in a single node
CU_ASSERT(7 == match_downstream_addr_group(router, "example.com",
"/alpha/bravo", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.com",
"/alpha/bravo/", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "example.com", "/alpha",
groups, 255));
// Check the case where quey is done in a single node
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1", "/alpha",
groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1", "/alpha/",
groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(router, "192.168.0.1",
"/alpha/bravo", groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "192.168.0.1", "/alph",
groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, "192.168.0.1", "/",
groups, 255));
router.dump();
}
} // namespace shrpx

View File

@ -26,20 +26,16 @@
namespace shrpx {
namespace {
const ev_tstamp INITIAL_SLEEP = 2.;
} // namespace
namespace {
void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "unblock downstream connection";
LOG(INFO) << "Unblock";
}
}
} // namespace
ConnectBlocker::ConnectBlocker(struct ev_loop *loop)
: loop_(loop), sleep_(INITIAL_SLEEP) {
ConnectBlocker::ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop)
: gen_(gen), loop_(loop), fail_count_(0) {
ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
}
@ -47,18 +43,27 @@ ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
void ConnectBlocker::on_success() { fail_count_ = 0; }
namespace {
constexpr size_t MAX_BACKOFF_EXP = 10;
} // namespace
void ConnectBlocker::on_failure() {
if (ev_is_active(&timer_)) {
return;
}
sleep_ = std::min(128., sleep_ * 2);
++fail_count_;
LOG(WARN) << "connect failure, start sleeping " << sleep_;
auto max_backoff = (1 << std::min(MAX_BACKOFF_EXP, fail_count_)) - 1;
auto dist = std::uniform_int_distribution<>(0, max_backoff);
auto backoff = dist(gen_);
ev_timer_set(&timer_, sleep_, 0.);
LOG(WARN) << "Could not connect " << fail_count_
<< " times in a row; sleep for " << backoff << " seconds";
ev_timer_set(&timer_, backoff, 0.);
ev_timer_start(loop_, &timer_);
}

View File

@ -27,13 +27,15 @@
#include "shrpx.h"
#include <random>
#include <ev.h>
namespace shrpx {
class ConnectBlocker {
public:
ConnectBlocker(struct ev_loop *loop);
ConnectBlocker(std::mt19937 &gen, struct ev_loop *loop);
~ConnectBlocker();
// Returns true if making connection is not allowed.
@ -41,14 +43,18 @@ public:
// Call this function if connect operation succeeded. This will
// reset sleep_ to minimum value.
void on_success();
// Call this function if connect operation failed. This will start
// timer and blocks connection establishment for sleep_ seconds.
// Call this function if connect operations failed. This will start
// timer and blocks connection establishment with exponential
// backoff.
void on_failure();
private:
std::mt19937 gen_;
ev_timer timer_;
struct ev_loop *loop_;
ev_tstamp sleep_;
// The number of consecutive connection failure. Reset to 0 on
// success.
size_t fail_count_;
};
} // namespace

View File

@ -47,7 +47,7 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
const RateLimitConfig &read_limit, IOCb writecb,
IOCb readcb, TimerCb timeoutcb, void *data,
size_t tls_dyn_rec_warmup_threshold,
ev_tstamp tls_dyn_rec_idle_timeout)
ev_tstamp tls_dyn_rec_idle_timeout, shrpx_proto proto)
: tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool)},
wlimit(loop, &wev, write_limit.rate, write_limit.burst),
rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
@ -58,7 +58,8 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
data(data),
fd(fd),
tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout) {
tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout),
proto(proto) {
ev_io_init(&wev, writecb, fd, EV_WRITE);
ev_io_init(&rev, readcb, fd, EV_READ);

View File

@ -77,7 +77,7 @@ struct Connection {
const RateLimitConfig &write_limit,
const RateLimitConfig &read_limit, IOCb writecb, IOCb readcb,
TimerCb timeoutcb, void *data, size_t tls_dyn_rec_warmup_threshold,
ev_tstamp tls_dyn_rec_idle_timeout);
ev_tstamp tls_dyn_rec_idle_timeout, shrpx_proto proto);
~Connection();
void disconnect();
@ -133,6 +133,10 @@ struct Connection {
int fd;
size_t tls_dyn_rec_warmup_threshold;
ev_tstamp tls_dyn_rec_idle_timeout;
// Application protocol used over the connection. This field is not
// used in this object at the moment. The rest of the program may
// use this value when it is useful.
shrpx_proto proto;
};
} // namespace shrpx

View File

@ -202,9 +202,8 @@ int ConnectionHandler::create_single_worker() {
#ifdef HAVE_NEVERBLEED
nb_.get(),
#endif // HAVE_NEVERBLEED
StringRef::from_maybe_nullptr(tlsconf.cacert.get()),
StringRef(memcachedconf.cert_file),
StringRef(memcachedconf.private_key_file), StringRef(), nullptr);
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
}
@ -253,9 +252,8 @@ int ConnectionHandler::create_worker_thread(size_t num) {
#ifdef HAVE_NEVERBLEED
nb_.get(),
#endif // HAVE_NEVERBLEED
StringRef::from_maybe_nullptr(tlsconf.cacert.get()),
StringRef(memcachedconf.cert_file),
StringRef(memcachedconf.private_key_file), StringRef(), nullptr);
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(session_cache_ssl_ctx);
}
auto worker =
@ -463,7 +461,8 @@ int ConnectionHandler::start_ocsp_update(const char *cert_file) {
assert(!ev_is_active(&ocsp_.chldev));
char *const argv[] = {
const_cast<char *>(get_config()->tls.ocsp.fetch_ocsp_response_file.get()),
const_cast<char *>(
get_config()->tls.ocsp.fetch_ocsp_response_file.c_str()),
const_cast<char *>(cert_file), nullptr};
char *const envp[] = {nullptr};
@ -767,9 +766,8 @@ SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() {
#ifdef HAVE_NEVERBLEED
nb_.get(),
#endif // HAVE_NEVERBLEED
StringRef::from_maybe_nullptr(tlsconf.cacert.get()),
StringRef(memcachedconf.cert_file),
StringRef(memcachedconf.private_key_file), StringRef(), nullptr);
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(ssl_ctx);

View File

@ -116,6 +116,9 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool,
: dlnext(nullptr),
dlprev(nullptr),
response_sent_body_length(0),
balloc_(1024, 1024),
req_(balloc_),
resp_(balloc_),
request_start_time_(std::chrono::high_resolution_clock::now()),
request_buf_(mcpool),
response_buf_(mcpool),
@ -179,6 +182,10 @@ Downstream::~Downstream() {
// explicitly.
dconn_.reset();
for (auto rcbuf : rcbufs_) {
nghttp2_rcbuf_decref(rcbuf);
}
if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "Deleted";
}
@ -237,15 +244,16 @@ void Downstream::force_resume_read() {
}
namespace {
const Headers::value_type *search_header_linear(const Headers &headers,
const StringRef &name) {
const Headers::value_type *res = nullptr;
for (auto &kv : headers) {
const HeaderRefs::value_type *
search_header_linear_backwards(const HeaderRefs &headers,
const StringRef &name) {
for (auto it = headers.rbegin(); it != headers.rend(); ++it) {
auto &kv = *it;
if (kv.name == name) {
res = &kv;
return &kv;
}
}
return res;
return nullptr;
}
} // namespace
@ -253,16 +261,29 @@ std::string Downstream::assemble_request_cookie() const {
std::string cookie;
cookie = "";
for (auto &kv : req_.fs.headers()) {
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq_l("cooki", kv.name.c_str(), 5)) {
if (kv.token != http2::HD_COOKIE) {
continue;
}
auto end = kv.value.find_last_not_of(" ;");
if (end == std::string::npos) {
if (kv.value.empty()) {
continue;
}
auto end = std::end(kv.value);
for (auto it = std::begin(kv.value) + kv.value.size();
it != std::begin(kv.value); --it) {
auto c = *(it - 1);
if (c == ' ' || c == ';') {
continue;
}
end = it;
break;
}
if (end == std::end(kv.value)) {
cookie += kv.value;
} else {
cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
cookie.append(std::begin(kv.value), end);
}
cookie += "; ";
}
@ -280,18 +301,14 @@ size_t Downstream::count_crumble_request_cookie() {
!util::streq_l("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size();
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
if (j == std::string::npos) {
break;
for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
if (*it == '\t' || *it == ' ' || *it == ';') {
++it;
continue;
}
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
}
it = std::find(it, std::end(kv.value), ';');
++n;
}
@ -305,22 +322,19 @@ void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
!util::streq_l("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size();
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
if (j == std::string::npos) {
break;
}
auto first = j;
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
for (auto it = std::begin(kv.value); it != std::end(kv.value);) {
if (*it == '\t' || *it == ' ' || *it == ';') {
++it;
continue;
}
nva.push_back({(uint8_t *)"cookie", (uint8_t *)kv.value.c_str() + first,
str_size("cookie"), j - first,
auto first = it;
it = std::find(it, std::end(kv.value), ';');
nva.push_back({(uint8_t *)"cookie", (uint8_t *)first, str_size("cookie"),
(size_t)(it - first),
(uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE |
(kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
@ -329,146 +343,124 @@ void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
}
namespace {
void add_header(bool &key_prev, size_t &sum, Headers &headers, std::string name,
std::string value) {
void add_header(bool &key_prev, size_t &sum, HeaderRefs &headers,
const StringRef &name, const StringRef &value, bool no_index,
int32_t token) {
key_prev = true;
sum += name.size() + value.size();
headers.emplace_back(std::move(name), std::move(value));
headers.emplace_back(name, value, no_index, token);
}
} // namespace
namespace {
void add_header(size_t &sum, Headers &headers, const uint8_t *name,
size_t namelen, const uint8_t *value, size_t valuelen,
bool no_index, int16_t token) {
sum += namelen + valuelen;
headers.emplace_back(
std::string(reinterpret_cast<const char *>(name), namelen),
std::string(reinterpret_cast<const char *>(value), valuelen), no_index,
token);
}
} // namespace
namespace {
void append_last_header_key(bool &key_prev, size_t &sum, Headers &headers,
const char *data, size_t len) {
void append_last_header_key(BlockAllocator &balloc, bool &key_prev, size_t &sum,
HeaderRefs &headers, const char *data, size_t len) {
assert(key_prev);
sum += len;
auto &item = headers.back();
item.name.append(data, len);
auto iov = make_byte_ref(balloc, item.name.size() + len + 1);
auto p = iov.base;
p = std::copy(std::begin(item.name), std::end(item.name), p);
p = std::copy_n(data, len, p);
util::inp_strlower(p - len, p);
*p = '\0';
item.name = StringRef{iov.base, p};
item.token = http2::lookup_token(item.name);
}
} // namespace
namespace {
void append_last_header_value(bool &key_prev, size_t &sum, Headers &headers,
void append_last_header_value(BlockAllocator &balloc, bool &key_prev,
size_t &sum, HeaderRefs &headers,
const char *data, size_t len) {
key_prev = false;
sum += len;
auto &item = headers.back();
item.value.append(data, len);
item.value = concat_string_ref(balloc, item.value, StringRef{data, len});
}
} // namespace
int FieldStore::index_headers() {
http2::init_hdidx(hdidx_);
int FieldStore::parse_content_length() {
content_length = -1;
for (size_t i = 0; i < headers_.size(); ++i) {
auto &kv = headers_[i];
util::inp_strlower(kv.name);
auto token = http2::lookup_token(
reinterpret_cast<const uint8_t *>(kv.name.c_str()), kv.name.size());
if (token < 0) {
for (auto &kv : headers_) {
if (kv.token != http2::HD_CONTENT_LENGTH) {
continue;
}
kv.token = token;
http2::index_header(hdidx_, token, i);
if (token == http2::HD_CONTENT_LENGTH) {
auto len = util::parse_uint(kv.value);
if (len == -1) {
return -1;
}
if (content_length != -1) {
return -1;
}
content_length = len;
auto len = util::parse_uint(kv.value);
if (len == -1) {
return -1;
}
if (content_length != -1) {
return -1;
}
content_length = len;
}
return 0;
}
const Headers::value_type *FieldStore::header(int16_t token) const {
return http2::get_header(hdidx_, token, headers_);
const HeaderRefs::value_type *FieldStore::header(int32_t token) const {
for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
auto &kv = *it;
if (kv.token == token) {
return &kv;
}
}
return nullptr;
}
Headers::value_type *FieldStore::header(int16_t token) {
return http2::get_header(hdidx_, token, headers_);
HeaderRefs::value_type *FieldStore::header(int32_t token) {
for (auto it = headers_.rbegin(); it != headers_.rend(); ++it) {
auto &kv = *it;
if (kv.token == token) {
return &kv;
}
}
return nullptr;
}
const Headers::value_type *FieldStore::header(const StringRef &name) const {
return search_header_linear(headers_, name);
const HeaderRefs::value_type *FieldStore::header(const StringRef &name) const {
return search_header_linear_backwards(headers_, name);
}
void FieldStore::add_header(std::string name, std::string value) {
shrpx::add_header(header_key_prev_, buffer_size_, headers_, std::move(name),
std::move(value));
}
void FieldStore::add_header(std::string name, std::string value,
int16_t token) {
http2::index_header(hdidx_, token, headers_.size());
buffer_size_ += name.size() + value.size();
headers_.emplace_back(std::move(name), std::move(value), false, token);
}
void FieldStore::add_header(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token) {
http2::index_header(hdidx_, token, headers_.size());
shrpx::add_header(buffer_size_, headers_, name, namelen, value, valuelen,
void FieldStore::add_header_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token) {
shrpx::add_header(header_key_prev_, buffer_size_, headers_, name, value,
no_index, token);
}
void FieldStore::append_last_header_key(const char *data, size_t len) {
shrpx::append_last_header_key(header_key_prev_, buffer_size_, headers_, data,
len);
shrpx::append_last_header_key(balloc_, header_key_prev_, buffer_size_,
headers_, data, len);
}
void FieldStore::append_last_header_value(const char *data, size_t len) {
shrpx::append_last_header_value(header_key_prev_, buffer_size_, headers_,
data, len);
shrpx::append_last_header_value(balloc_, header_key_prev_, buffer_size_,
headers_, data, len);
}
void FieldStore::clear_headers() {
headers_.clear();
http2::init_hdidx(hdidx_);
}
void FieldStore::clear_headers() { headers_.clear(); }
void FieldStore::add_trailer(const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen,
bool no_index, int16_t token) {
// we never index trailer fields. Header size limit should be
// applied to all header and trailer fields combined.
shrpx::add_header(buffer_size_, trailers_, name, namelen, value, valuelen,
no_index, -1);
}
void FieldStore::add_trailer(std::string name, std::string value) {
shrpx::add_header(trailer_key_prev_, buffer_size_, trailers_, std::move(name),
std::move(value));
void FieldStore::add_trailer_token(const StringRef &name,
const StringRef &value, bool no_index,
int32_t token) {
// Header size limit should be applied to all header and trailer
// fields combined.
shrpx::add_header(trailer_key_prev_, buffer_size_, trailers_, name, value,
no_index, token);
}
void FieldStore::append_last_trailer_key(const char *data, size_t len) {
shrpx::append_last_header_key(trailer_key_prev_, buffer_size_, trailers_,
data, len);
shrpx::append_last_header_key(balloc_, trailer_key_prev_, buffer_size_,
trailers_, data, len);
}
void FieldStore::append_last_trailer_value(const char *data, size_t len) {
shrpx::append_last_header_value(trailer_key_prev_, buffer_size_, trailers_,
data, len);
shrpx::append_last_header_value(balloc_, trailer_key_prev_, buffer_size_,
trailers_, data, len);
}
void Downstream::set_request_start_time(
@ -549,7 +541,7 @@ int Downstream::end_upload_data() {
}
void Downstream::rewrite_location_response_header(
const std::string &upstream_scheme) {
const StringRef &upstream_scheme) {
auto hd = resp_.fs.header(http2::HD_LOCATION);
if (!hd) {
return;
@ -565,14 +557,15 @@ void Downstream::rewrite_location_response_header(
return;
}
auto new_uri = http2::rewrite_location_uri(
hd->value, u, request_downstream_host_, req_.authority, upstream_scheme);
auto new_uri = http2::rewrite_location_uri(balloc_, hd->value, u,
request_downstream_host_,
req_.authority, upstream_scheme);
if (new_uri.empty()) {
return;
}
hd->value = std::move(new_uri);
hd->value = new_uri;
}
bool Downstream::get_chunked_response() const { return chunked_response_; }
@ -709,10 +702,10 @@ bool Downstream::get_http2_upgrade_request() const {
response_state_ == INITIAL;
}
const std::string &Downstream::get_http2_settings() const {
StringRef Downstream::get_http2_settings() const {
auto http2_settings = req_.fs.header(http2::HD_HTTP2_SETTINGS);
if (!http2_settings) {
return EMPTY_STRING;
return StringRef{};
}
return http2_settings->value;
}
@ -867,8 +860,8 @@ void Downstream::add_retry() { ++num_retry_; }
bool Downstream::no_more_retry() const { return num_retry_ > 5; }
void Downstream::set_request_downstream_host(std::string host) {
request_downstream_host_ = std::move(host);
void Downstream::set_request_downstream_host(const StringRef &host) {
request_downstream_host_ = host;
}
void Downstream::set_request_pending(bool f) { request_pending_ = f; }
@ -914,4 +907,11 @@ void Downstream::set_assoc_stream_id(int32_t stream_id) {
int32_t Downstream::get_assoc_stream_id() const { return assoc_stream_id_; }
BlockAllocator &Downstream::get_block_allocator() { return balloc_; }
void Downstream::add_rcbuf(nghttp2_rcbuf *rcbuf) {
nghttp2_rcbuf_incref(rcbuf);
rcbufs_.push_back(rcbuf);
}
} // namespace shrpx

View File

@ -40,6 +40,7 @@
#include "shrpx_io_control.h"
#include "http2.h"
#include "memchunk.h"
#include "allocator.h"
using namespace nghttp2;
@ -51,19 +52,19 @@ struct BlockedLink;
class FieldStore {
public:
FieldStore(size_t headers_initial_capacity)
FieldStore(BlockAllocator &balloc, size_t headers_initial_capacity)
: content_length(-1),
balloc_(balloc),
buffer_size_(0),
header_key_prev_(false),
trailer_key_prev_(false) {
http2::init_hdidx(hdidx_);
headers_.reserve(headers_initial_capacity);
}
const Headers &headers() const { return headers_; }
const Headers &trailers() const { return trailers_; }
const HeaderRefs &headers() const { return headers_; }
const HeaderRefs &trailers() const { return trailers_; }
Headers &headers() { return headers_; }
HeaderRefs &headers() { return headers_; }
const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
size_t buffer_size() const { return buffer_size_; }
@ -74,33 +75,29 @@ public:
// multiple header have |name| as name, return last occurrence from
// the beginning. If no such header is found, returns nullptr.
// This function must be called after headers are indexed
const Headers::value_type *header(int16_t token) const;
Headers::value_type *header(int16_t token);
const HeaderRefs::value_type *header(int32_t token) const;
HeaderRefs::value_type *header(int32_t token);
// Returns pointer to the header field with the name |name|. If no
// such header is found, returns nullptr.
const Headers::value_type *header(const StringRef &name) const;
const HeaderRefs::value_type *header(const StringRef &name) const;
void add_header(std::string name, std::string value);
void add_header(std::string name, std::string value, int16_t token);
void add_header(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, bool no_index, int16_t token);
void add_header_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token);
void append_last_header_key(const char *data, size_t len);
void append_last_header_value(const char *data, size_t len);
bool header_key_prev() const { return header_key_prev_; }
// Lower the header field names and indexes header fields. If there
// is any invalid headers (e.g., multiple Content-Length having
// different values), returns -1.
int index_headers();
// Parses content-length, and records it in the field. If there are
// multiple Content-Length, returns -1.
int parse_content_length();
// Empties headers.
void clear_headers();
void add_trailer(const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, bool no_index, int16_t token);
void add_trailer(std::string name, std::string value);
void add_trailer_token(const StringRef &name, const StringRef &value,
bool no_index, int32_t token);
void append_last_trailer_key(const char *data, size_t len);
void append_last_trailer_value(const char *data, size_t len);
@ -111,11 +108,11 @@ public:
int64_t content_length;
private:
Headers headers_;
BlockAllocator &balloc_;
HeaderRefs headers_;
// trailer fields. For HTTP/1.1, trailer fields are only included
// with chunked encoding. For HTTP/2, there is no such limit.
Headers trailers_;
http2::HeaderIndex hdidx_;
HeaderRefs trailers_;
// Sum of the length of name and value in headers_ and trailers_.
// This could also be increased by add_extra_buffer_size() to take
// into account for request URI in case of HTTP/1.x request.
@ -125,8 +122,8 @@ private:
};
struct Request {
Request()
: fs(16),
Request(BlockAllocator &balloc)
: fs(balloc, 16),
recv_body_length(0),
unconsumed_body_length(0),
method(-1),
@ -146,17 +143,17 @@ struct Request {
FieldStore fs;
// Request scheme. For HTTP/2, this is :scheme header field value.
// For HTTP/1.1, this is deduced from URI or connection.
std::string scheme;
StringRef scheme;
// Request authority. This is HTTP/2 :authority header field value
// or host header field value. We may deduce it from absolute-form
// HTTP/1 request. We also store authority-form HTTP/1 request.
// This could be empty if request comes from HTTP/1.0 without Host
// header field and origin-form.
std::string authority;
StringRef authority;
// Request path, including query component. For HTTP/1.1, this is
// request-target. For HTTP/2, this is :path header field value.
// For CONNECT request, this is empty.
std::string path;
StringRef path;
// the length of request body received so far
int64_t recv_body_length;
// The number of bytes not consumed by the application yet.
@ -181,8 +178,8 @@ struct Request {
};
struct Response {
Response()
: fs(32),
Response(BlockAllocator &balloc)
: fs(balloc, 32),
recv_body_length(0),
unconsumed_body_length(0),
http_status(0),
@ -247,7 +244,7 @@ public:
// Returns true if the request is HTTP Upgrade for HTTP/2
bool get_http2_upgrade_request() const;
// Returns the value of HTTP2-Settings request header field.
const std::string &get_http2_settings() const;
StringRef get_http2_settings() const;
// downstream request API
const Request &request() const { return req_; }
@ -274,7 +271,7 @@ public:
// Validates that received request body length and content-length
// matches.
bool validate_request_recv_body_length() const;
void set_request_downstream_host(std::string host);
void set_request_downstream_host(const StringRef &host);
bool expect_response_body() const;
enum {
INITIAL,
@ -305,7 +302,7 @@ public:
Response &response() { return resp_; }
// Rewrites the location response header field.
void rewrite_location_response_header(const std::string &upstream_scheme);
void rewrite_location_response_header(const StringRef &upstream_scheme);
bool get_chunked_response() const;
void set_chunked_response(bool f);
@ -375,6 +372,10 @@ public:
DefaultMemchunks pop_response_buf();
BlockAllocator &get_block_allocator();
void add_rcbuf(nghttp2_rcbuf *rcbuf);
enum {
EVENT_ERROR = 0x1,
EVENT_TIMEOUT = 0x2,
@ -394,6 +395,10 @@ public:
int64_t response_sent_body_length;
private:
BlockAllocator balloc_;
std::vector<nghttp2_rcbuf *> rcbufs_;
Request req_;
Response resp_;
@ -402,7 +407,7 @@ private:
// host we requested to downstream. This is used to rewrite
// location header field to decide the location should be rewritten
// or not.
std::string request_downstream_host_;
StringRef request_downstream_host_;
DefaultMemchunks request_buf_;
DefaultMemchunks response_buf_;

View File

@ -26,12 +26,11 @@
#include "shrpx_client_handler.h"
#include "shrpx_downstream.h"
#include "shrpx_downstream_connection_pool.h"
namespace shrpx {
DownstreamConnection::DownstreamConnection(DownstreamConnectionPool *dconn_pool)
: dconn_pool_(dconn_pool), client_handler_(nullptr), downstream_(nullptr) {}
DownstreamConnection::DownstreamConnection()
: client_handler_(nullptr), downstream_(nullptr) {}
DownstreamConnection::~DownstreamConnection() {}
@ -45,8 +44,4 @@ ClientHandler *DownstreamConnection::get_client_handler() {
Downstream *DownstreamConnection::get_downstream() { return downstream_; }
DownstreamConnectionPool *DownstreamConnection::get_dconn_pool() const {
return dconn_pool_;
}
} // namespace shrpx

View File

@ -34,11 +34,11 @@ namespace shrpx {
class ClientHandler;
class Upstream;
class Downstream;
class DownstreamConnectionPool;
struct DownstreamAddrGroup;
class DownstreamConnection {
public:
DownstreamConnection(DownstreamConnectionPool *dconn_pool);
DownstreamConnection();
virtual ~DownstreamConnection();
virtual int attach_downstream(Downstream *downstream) = 0;
virtual void detach_downstream(Downstream *downstream) = 0;
@ -56,18 +56,17 @@ public:
virtual int on_timeout() { return 0; }
virtual void on_upstream_change(Upstream *uptream) = 0;
virtual size_t get_group() const = 0;
// true if this object is poolable.
virtual bool poolable() const = 0;
virtual DownstreamAddrGroup *get_downstream_addr_group() const = 0;
void set_client_handler(ClientHandler *client_handler);
ClientHandler *get_client_handler();
Downstream *get_downstream();
DownstreamConnectionPool *get_dconn_pool() const;
protected:
DownstreamConnectionPool *dconn_pool_;
ClientHandler *client_handler_;
Downstream *downstream_;
};

View File

@ -27,42 +27,35 @@
namespace shrpx {
DownstreamConnectionPool::DownstreamConnectionPool(size_t num_groups)
: gpool_(num_groups) {}
DownstreamConnectionPool::DownstreamConnectionPool() {}
DownstreamConnectionPool::~DownstreamConnectionPool() {
for (auto &pool : gpool_) {
for (auto dconn : pool) {
delete dconn;
}
for (auto dconn : pool_) {
delete dconn;
}
}
void DownstreamConnectionPool::add_downstream_connection(
std::unique_ptr<DownstreamConnection> dconn) {
auto group = dconn->get_group();
assert(gpool_.size() > group);
gpool_[group].insert(dconn.release());
pool_.insert(dconn.release());
}
std::unique_ptr<DownstreamConnection>
DownstreamConnectionPool::pop_downstream_connection(size_t group) {
assert(gpool_.size() > group);
auto &pool = gpool_[group];
if (pool.empty()) {
DownstreamConnectionPool::pop_downstream_connection() {
if (pool_.empty()) {
return nullptr;
}
auto dconn = std::unique_ptr<DownstreamConnection>(*std::begin(pool));
pool.erase(std::begin(pool));
auto it = std::begin(pool_);
auto dconn = std::unique_ptr<DownstreamConnection>(*it);
pool_.erase(it);
return dconn;
}
void DownstreamConnectionPool::remove_downstream_connection(
DownstreamConnection *dconn) {
auto group = dconn->get_group();
assert(gpool_.size() > group);
gpool_[group].erase(dconn);
pool_.erase(dconn);
delete dconn;
}

View File

@ -36,15 +36,15 @@ class DownstreamConnection;
class DownstreamConnectionPool {
public:
DownstreamConnectionPool(size_t num_groups);
DownstreamConnectionPool();
~DownstreamConnectionPool();
void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
std::unique_ptr<DownstreamConnection> pop_downstream_connection(size_t group);
std::unique_ptr<DownstreamConnection> pop_downstream_connection();
void remove_downstream_connection(DownstreamConnection *dconn);
private:
std::vector<std::set<DownstreamConnection *>> gpool_;
std::set<DownstreamConnection *> pool_;
};
} // namespace shrpx

View File

@ -71,14 +71,11 @@ DownstreamQueue::find_host_entry(const std::string &host) {
return (*itr).second;
}
const std::string &
DownstreamQueue::make_host_key(const std::string &host) const {
static std::string empty_key;
return unified_host_ ? empty_key : host;
std::string DownstreamQueue::make_host_key(const StringRef &host) const {
return unified_host_ ? "" : host.str();
}
const std::string &
DownstreamQueue::make_host_key(Downstream *downstream) const {
std::string DownstreamQueue::make_host_key(Downstream *downstream) const {
return make_host_key(downstream->request().authority);
}
@ -99,7 +96,7 @@ void DownstreamQueue::mark_blocked(Downstream *downstream) {
ent.blocked.append(link);
}
bool DownstreamQueue::can_activate(const std::string &host) const {
bool DownstreamQueue::can_activate(const StringRef &host) const {
auto itr = host_entries_.find(make_host_key(host));
if (itr == std::end(host_entries_)) {
return true;
@ -127,7 +124,7 @@ Downstream *DownstreamQueue::remove_and_get_blocked(Downstream *downstream,
downstreams_.remove(downstream);
auto &host = make_host_key(downstream);
auto host = make_host_key(downstream);
auto &ent = find_host_entry(host);
if (downstream->get_dispatch_state() == Downstream::DISPATCH_ACTIVE) {

View File

@ -77,7 +77,7 @@ public:
void mark_blocked(Downstream *downstream);
// Returns true if we can make downstream connection to given
// |host|.
bool can_activate(const std::string &host) const;
bool can_activate(const StringRef &host) const;
// Removes and frees |downstream| object. If |downstream| is in
// Downstream::DISPATCH_ACTIVE, and |next_blocked| is true, this
// function may return Downstream object with the same target host
@ -87,8 +87,8 @@ public:
bool next_blocked = true);
Downstream *get_downstreams() const;
HostEntry &find_host_entry(const std::string &host);
const std::string &make_host_key(const std::string &host) const;
const std::string &make_host_key(Downstream *downstream) const;
std::string make_host_key(const StringRef &host) const;
std::string make_host_key(Downstream *downstream) const;
private:
// Per target host structure to keep track of the number of

View File

@ -32,56 +32,62 @@
namespace shrpx {
void test_downstream_field_store_index_headers(void) {
FieldStore fs(0);
fs.add_header("1", "0");
fs.add_header("2", "1");
fs.add_header("Charlie", "2");
fs.add_header("Alpha", "3");
fs.add_header("Delta", "4");
fs.add_header("BravO", "5");
fs.add_header(":method", "6");
fs.add_header(":authority", "7");
fs.index_headers();
void test_downstream_field_store_append_last_header(void) {
BlockAllocator balloc(4096, 4096);
FieldStore fs(balloc, 0);
fs.add_header_token(StringRef::from_lit("alpha"), StringRef{}, false, -1);
auto bravo = StringRef::from_lit("BRAVO");
fs.append_last_header_key(bravo.c_str(), bravo.size());
auto charlie = StringRef::from_lit("Charlie");
fs.append_last_header_value(charlie.c_str(), charlie.size());
auto delta = StringRef::from_lit("deltA");
fs.append_last_header_value(delta.c_str(), delta.size());
fs.add_header_token(StringRef::from_lit("echo"),
StringRef::from_lit("foxtrot"), false, -1);
auto ans = Headers{{"1", "0"},
{"2", "1"},
{"charlie", "2"},
{"alpha", "3"},
{"delta", "4"},
{"bravo", "5"},
{":method", "6"},
{":authority", "7"}};
auto ans = HeaderRefs{
{StringRef::from_lit("alphabravo"), StringRef::from_lit("CharliedeltA")},
{StringRef::from_lit("echo"), StringRef::from_lit("foxtrot")}};
CU_ASSERT(ans == fs.headers());
}
void test_downstream_field_store_header(void) {
FieldStore fs(0);
fs.add_header("alpha", "0");
fs.add_header(":authority", "1");
fs.add_header("content-length", "2");
fs.index_headers();
BlockAllocator balloc(4096, 4096);
FieldStore fs(balloc, 0);
fs.add_header_token(StringRef::from_lit("alpha"), StringRef::from_lit("0"),
false, -1);
fs.add_header_token(StringRef::from_lit(":authority"),
StringRef::from_lit("1"), false, http2::HD__AUTHORITY);
fs.add_header_token(StringRef::from_lit("content-length"),
StringRef::from_lit("2"), false,
http2::HD_CONTENT_LENGTH);
// By token
CU_ASSERT(Header(":authority", "1") == *fs.header(http2::HD__AUTHORITY));
CU_ASSERT(HeaderRef(StringRef{":authority"}, StringRef{"1"}) ==
*fs.header(http2::HD__AUTHORITY));
CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
// By name
CU_ASSERT(Header("alpha", "0") == *fs.header("alpha"));
CU_ASSERT(nullptr == fs.header("bravo"));
CU_ASSERT(HeaderRef(StringRef{"alpha"}, StringRef{"0"}) ==
*fs.header(StringRef::from_lit("alpha")));
CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo")));
}
void test_downstream_crumble_request_cookie(void) {
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
req.fs.add_header(":method", "get");
req.fs.add_header(":path", "/");
auto val = "alpha; bravo; ; ;; charlie;;";
req.fs.add_header(
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
req.fs.add_header("cookie", ";delta");
req.fs.add_header("cookie", "echo");
req.fs.add_header_token(StringRef::from_lit(":method"),
StringRef::from_lit("get"), false, -1);
req.fs.add_header_token(StringRef::from_lit(":path"),
StringRef::from_lit("/"), false, -1);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("alpha; bravo; ; ;; charlie;;"),
true, http2::HD_COOKIE);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit(";delta"), false,
http2::HD_COOKIE);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("echo"), false, http2::HD_COOKIE);
std::vector<nghttp2_nv> nva;
d.crumble_request_cookie(nva);
@ -91,19 +97,20 @@ void test_downstream_crumble_request_cookie(void) {
CU_ASSERT(5 == nva.size());
CU_ASSERT(5 == num_cookies);
Headers cookies;
HeaderRefs cookies;
std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
[](const nghttp2_nv &nv) {
return Header(std::string(nv.name, nv.name + nv.namelen),
std::string(nv.value, nv.value + nv.valuelen),
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
return HeaderRef(StringRef{nv.name, nv.namelen},
StringRef{nv.value, nv.valuelen},
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
});
Headers ans = {{"cookie", "alpha"},
{"cookie", "bravo"},
{"cookie", "charlie"},
{"cookie", "delta"},
{"cookie", "echo"}};
HeaderRefs ans = {
{StringRef::from_lit("cookie"), StringRef::from_lit("alpha")},
{StringRef::from_lit("cookie"), StringRef::from_lit("bravo")},
{StringRef::from_lit("cookie"), StringRef::from_lit("charlie")},
{StringRef::from_lit("cookie"), StringRef::from_lit("delta")},
{StringRef::from_lit("cookie"), StringRef::from_lit("echo")}};
CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index);
@ -114,12 +121,23 @@ void test_downstream_crumble_request_cookie(void) {
void test_downstream_assemble_request_cookie(void) {
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
req.fs.add_header(":method", "get");
req.fs.add_header(":path", "/");
req.fs.add_header("cookie", "alpha");
req.fs.add_header("cookie", "bravo;");
req.fs.add_header("cookie", "charlie; ");
req.fs.add_header("cookie", "delta;;");
req.fs.add_header_token(StringRef::from_lit(":method"),
StringRef::from_lit("get"), false, -1);
req.fs.add_header_token(StringRef::from_lit(":path"),
StringRef::from_lit("/"), false, -1);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("alpha"), false,
http2::HD_COOKIE);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("bravo;"), false,
http2::HD_COOKIE);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("charlie; "), false,
http2::HD_COOKIE);
req.fs.add_header_token(StringRef::from_lit("cookie"),
StringRef::from_lit("delta;;"), false,
http2::HD_COOKIE);
CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie());
}
@ -127,11 +145,12 @@ void test_downstream_rewrite_location_response_header(void) {
Downstream d(nullptr, nullptr, 0);
auto &req = d.request();
auto &resp = d.response();
d.set_request_downstream_host("localhost2");
req.authority = "localhost:8443";
resp.fs.add_header("location", "http://localhost2:3000/");
resp.fs.index_headers();
d.rewrite_location_response_header("https");
d.set_request_downstream_host(StringRef::from_lit("localhost2"));
req.authority = StringRef::from_lit("localhost:8443");
resp.fs.add_header_token(StringRef::from_lit("location"),
StringRef::from_lit("http://localhost2:3000/"),
false, http2::HD_LOCATION);
d.rewrite_location_response_header(StringRef::from_lit("https"));
auto location = resp.fs.header(http2::HD_LOCATION);
CU_ASSERT("https://localhost:8443/" == (*location).value);
}

View File

@ -31,7 +31,7 @@
namespace shrpx {
void test_downstream_field_store_index_headers(void);
void test_downstream_field_store_append_last_header(void);
void test_downstream_field_store_header(void);
void test_downstream_crumble_request_cookie(void);
void test_downstream_assemble_request_cookie(void);

View File

@ -50,70 +50,75 @@ std::string create_error_html(unsigned int status_code) {
return res;
}
std::string create_via_header_value(int major, int minor) {
std::string hdrs;
hdrs += static_cast<char>(major + '0');
if (major < 2) {
hdrs += '.';
hdrs += static_cast<char>(minor + '0');
StringRef create_forwarded(BlockAllocator &balloc, int params,
const StringRef &node_by, const StringRef &node_for,
const StringRef &host, const StringRef &proto) {
size_t len = 0;
if ((params & FORWARDED_BY) && !node_by.empty()) {
len += str_size("by=\"") + node_by.size() + str_size("\";");
}
if ((params & FORWARDED_FOR) && !node_for.empty()) {
len += str_size("for=\"") + node_for.size() + str_size("\";");
}
if ((params & FORWARDED_HOST) && !host.empty()) {
len += str_size("host=\"") + host.size() + str_size("\";");
}
if ((params & FORWARDED_PROTO) && !proto.empty()) {
len += str_size("proto=") + proto.size() + str_size(";");
}
hdrs += " nghttpx";
return hdrs;
}
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host,
const std::string &proto) {
std::string res;
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
if ((params & FORWARDED_BY) && !node_by.empty()) {
// This must be quoted-string unless it is obfuscated version
// (which starts with "_") or some special value (e.g.,
// "localhost" for UNIX domain socket), since ':' is not allowed
// in token. ':' is used to separate host and port.
if (node_by[0] == '_' || node_by[0] == 'l') {
res += "by=";
res += node_by;
res += ";";
p = util::copy_lit(p, "by=");
p = std::copy(std::begin(node_by), std::end(node_by), p);
p = util::copy_lit(p, ";");
} else {
res += "by=\"";
res += node_by;
res += "\";";
p = util::copy_lit(p, "by=\"");
p = std::copy(std::begin(node_by), std::end(node_by), p);
p = util::copy_lit(p, "\";");
}
}
if ((params & FORWARDED_FOR) && !node_for.empty()) {
// We only quote IPv6 literal address only, which starts with '['.
if (node_for[0] == '[') {
res += "for=\"";
res += node_for;
res += "\";";
p = util::copy_lit(p, "for=\"");
p = std::copy(std::begin(node_for), std::end(node_for), p);
p = util::copy_lit(p, "\";");
} else {
res += "for=";
res += node_for;
res += ";";
p = util::copy_lit(p, "for=");
p = std::copy(std::begin(node_for), std::end(node_for), p);
p = util::copy_lit(p, ";");
}
}
if ((params & FORWARDED_HOST) && !host.empty()) {
// Just be quoted to skip checking characters.
res += "host=\"";
res += host;
res += "\";";
p = util::copy_lit(p, "host=\"");
p = std::copy(std::begin(host), std::end(host), p);
p = util::copy_lit(p, "\";");
}
if ((params & FORWARDED_PROTO) && !proto.empty()) {
// Scheme production rule only allow characters which are all in
// token.
res += "proto=";
res += proto;
res += ";";
p = util::copy_lit(p, "proto=");
p = std::copy(std::begin(proto), std::end(proto), p);
*p++ = ';';
}
if (res.empty()) {
return res;
if (iov.base == p) {
return StringRef{};
}
res.erase(res.size() - 1);
--p;
*p = '\0';
return res;
return StringRef{iov.base, p};
}
std::string colorizeHeaders(const char *hdrs) {

View File

@ -31,20 +31,31 @@
#include <nghttp2/nghttp2.h>
#include "util.h"
#include "allocator.h"
namespace shrpx {
namespace http {
std::string create_error_html(unsigned int status_code);
std::string create_via_header_value(int major, int minor);
template <typename OutputIt>
OutputIt create_via_header_value(OutputIt dst, int major, int minor) {
*dst++ = static_cast<char>(major + '0');
if (major < 2) {
*dst++ = '.';
*dst++ = static_cast<char>(minor + '0');
}
return util::copy_lit(dst, " nghttpx");
}
// Returns generated RFC 7239 Forwarded header field value. The
// |params| is bitwise-OR of zero or more of shrpx_forwarded_param
// defined in shrpx_config.h.
std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for,
const std::string &host, const std::string &proto);
StringRef create_forwarded(BlockAllocator &balloc, int params,
const StringRef &node_by, const StringRef &node_for,
const StringRef &host, const StringRef &proto);
// Adds ANSI color codes to HTTP headers |hdrs|.
std::string colorizeHeaders(const char *hdrs);

View File

@ -37,6 +37,7 @@
#include "shrpx_error.h"
#include "shrpx_http.h"
#include "shrpx_http2_session.h"
#include "shrpx_worker.h"
#include "http2.h"
#include "util.h"
@ -44,10 +45,8 @@ using namespace nghttp2;
namespace shrpx {
Http2DownstreamConnection::Http2DownstreamConnection(
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
: DownstreamConnection(dconn_pool),
dlnext(nullptr),
Http2DownstreamConnection::Http2DownstreamConnection(Http2Session *http2session)
: dlnext(nullptr),
dlprev(nullptr),
http2session_(http2session),
sd_(nullptr) {}
@ -98,11 +97,19 @@ int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
http2session_->add_downstream_connection(this);
if (http2session_->get_state() == Http2Session::DISCONNECTED) {
http2session_->signal_write();
if (http2session_->get_state() == Http2Session::DISCONNECTED) {
return -1;
}
}
downstream_ = downstream;
downstream_->reset_downstream_rtimer();
auto &req = downstream_->request();
// HTTP/2 disables HTTP Upgrade.
req.upgrade_request = false;
return 0;
}
@ -259,12 +266,14 @@ int Http2DownstreamConnection::push_request_headers() {
const auto &req = downstream_->request();
auto &balloc = downstream_->get_block_allocator();
auto &httpconf = get_config()->http;
auto &http2conf = get_config()->http2;
auto no_host_rewrite =
httpconf.no_host_rewrite || get_config()->http2_proxy ||
get_config()->client_proxy || req.method == HTTP_CONNECT;
auto no_host_rewrite = httpconf.no_host_rewrite ||
get_config()->http2_proxy ||
req.method == HTTP_CONNECT;
// http2session_ has already in CONNECTED state, so we can get
// addr_idx here.
@ -275,10 +284,10 @@ int Http2DownstreamConnection::push_request_headers() {
auto authority = StringRef(downstream_hostport);
if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority);
authority = req.authority;
}
downstream_->set_request_downstream_host(authority.str());
downstream_->set_request_downstream_host(authority);
size_t num_cookies = 0;
if (!http2conf.no_cookie_crumbling) {
@ -300,7 +309,7 @@ int Http2DownstreamConnection::push_request_headers() {
httpconf.add_request_headers.size());
nva.push_back(
http2::make_nv_lc_nocopy(":method", http2::to_method_string(req.method)));
http2::make_nv_ls_nocopy(":method", http2::to_method_string(req.method)));
if (req.method != HTTP_CONNECT) {
assert(!req.scheme.empty());
@ -338,8 +347,6 @@ int Http2DownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
std::string forwarded_value;
auto &fwdconf = httpconf.forwarded;
auto fwd =
@ -348,30 +355,28 @@ int Http2DownstreamConnection::push_request_headers() {
if (fwdconf.params) {
auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy ||
req.method == HTTP_CONNECT) {
if (get_config()->http2_proxy || req.method == HTTP_CONNECT) {
params &= ~FORWARDED_PROTO;
}
auto value = http::create_forwarded(params, handler->get_forwarded_by(),
handler->get_forwarded_for(),
req.authority, req.scheme);
auto value = http::create_forwarded(
balloc, params, handler->get_forwarded_by(),
handler->get_forwarded_for(), req.authority, req.scheme);
if (fwd || !value.empty()) {
if (fwd) {
forwarded_value = fwd->value;
if (!value.empty()) {
forwarded_value += ", ";
if (value.empty()) {
value = fwd->value;
} else {
value = concat_string_ref(balloc, fwd->value,
StringRef::from_lit(", "), value);
}
}
forwarded_value += value;
nva.push_back(http2::make_nv_ls("forwarded", forwarded_value));
nva.push_back(http2::make_nv_ls_nocopy("forwarded", value));
}
} else if (fwd) {
nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
forwarded_value = fwd->value;
}
auto &xffconf = httpconf.xff;
@ -379,38 +384,47 @@ int Http2DownstreamConnection::push_request_headers() {
auto xff = xffconf.strip_incoming ? nullptr
: req.fs.header(http2::HD_X_FORWARDED_FOR);
std::string xff_value;
if (xffconf.add) {
StringRef xff_value;
auto addr = StringRef{upstream->get_client_handler()->get_ipaddr()};
if (xff) {
xff_value = (*xff).value;
xff_value += ", ";
xff_value = concat_string_ref(balloc, xff->value,
StringRef::from_lit(", "), addr);
} else {
xff_value = addr;
}
xff_value += upstream->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff_value));
} else if (xff) {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", xff->value));
}
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
req.method != HTTP_CONNECT) {
if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) {
// We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
}
std::string via_value;
auto via = req.fs.header(http2::HD_VIA);
if (httpconf.no_via) {
if (via) {
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
}
} else {
size_t vialen = 16;
if (via) {
via_value = (*via).value;
via_value += ", ";
vialen += via->value.size() + 2;
}
via_value += http::create_via_header_value(req.http_major, req.http_minor);
nva.push_back(http2::make_nv_ls("via", via_value));
auto iov = make_byte_ref(balloc, vialen + 1);
auto p = iov.base;
if (via) {
p = std::copy(std::begin(via->value), std::end(via->value), p);
p = util::copy_lit(p, ", ");
}
p = http::create_via_header_value(p, req.http_major, req.http_minor);
*p = '\0';
nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
}
auto te = req.fs.header(http2::HD_TE);
@ -554,10 +568,9 @@ int Http2DownstreamConnection::on_timeout() {
return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR);
}
size_t Http2DownstreamConnection::get_group() const {
// HTTP/2 backend connections are managed by Http2Session object,
// and it stores group index.
return http2session_->get_group();
DownstreamAddrGroup *
Http2DownstreamConnection::get_downstream_addr_group() const {
return http2session_->get_downstream_addr_group();
}
} // namespace shrpx

View File

@ -41,8 +41,7 @@ class DownstreamConnectionPool;
class Http2DownstreamConnection : public DownstreamConnection {
public:
Http2DownstreamConnection(DownstreamConnectionPool *dconn_pool,
Http2Session *http2session);
Http2DownstreamConnection(Http2Session *http2session);
virtual ~Http2DownstreamConnection();
virtual int attach_downstream(Downstream *downstream);
virtual void detach_downstream(Downstream *downstream);
@ -60,12 +59,13 @@ public:
virtual int on_timeout();
virtual void on_upstream_change(Upstream *upstream) {}
virtual size_t get_group() const;
// This object is not poolable because we dont' have facility to
// migrate to another Http2Session object.
virtual bool poolable() const { return false; }
virtual DownstreamAddrGroup *get_downstream_addr_group() const;
int send();
void attach_stream_data(StreamData *sd);

View File

@ -74,6 +74,10 @@ void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
SSLOG(INFO, http2session) << "ping timeout";
}
http2session->disconnect();
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return;
default:
if (LOG_ENABLED(INFO)) {
@ -92,6 +96,9 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
SSLOG(INFO, http2session) << "SETTINGS timeout";
if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
http2session->disconnect();
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return;
}
http2session->signal_write();
@ -109,6 +116,9 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
http2session->disconnect(http2session->get_state() ==
Http2Session::CONNECTING);
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
}
} // namespace
@ -120,6 +130,9 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_read();
if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return;
}
http2session->connection_alive();
@ -127,6 +140,9 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_write();
if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return;
}
}
@ -140,6 +156,9 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_write();
if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return;
}
http2session->reset_connection_check_timer_if_not_checking();
@ -147,25 +166,23 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
} // namespace
Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
ConnectBlocker *connect_blocker, Worker *worker,
size_t group, size_t idx)
: conn_(loop, -1, nullptr, worker->get_mcpool(),
Worker *worker, DownstreamAddrGroup *group)
: dlnext(nullptr),
dlprev(nullptr),
conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP2),
wb_(worker->get_mcpool()),
worker_(worker),
connect_blocker_(connect_blocker),
ssl_ctx_(ssl_ctx),
group_(group),
addr_(nullptr),
session_(nullptr),
group_(group),
index_(idx),
state_(DISCONNECTED),
connection_check_state_(CONNECTION_CHECK_NONE),
flow_control_(false) {
read_ = write_ = &Http2Session::noop;
on_read_ = &Http2Session::read_noop;
@ -184,7 +201,16 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
settings_timer_.data = this;
}
Http2Session::~Http2Session() { disconnect(); }
Http2Session::~Http2Session() {
disconnect();
if (in_freelist()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Removed from http2_freelist";
}
group_->http2_freelist.remove(this);
}
}
int Http2Session::disconnect(bool hard) {
if (LOG_ENABLED(INFO)) {
@ -254,30 +280,57 @@ int Http2Session::disconnect(bool hard) {
int Http2Session::initiate_connection() {
int rv = 0;
auto &addrs = get_config()->conn.downstream.addr_groups[group_].addrs;
auto &addrs = group_->addrs;
auto worker_blocker = worker_->get_connect_blocker();
if (state_ == DISCONNECTED) {
if (connect_blocker_->blocked()) {
if (worker_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this)
<< "Downstream connection was blocked by connect_blocker";
SSLOG(INFO, this)
<< "Worker wide backend connection was blocked temporarily";
}
return -1;
}
auto &next_downstream = worker_->get_dgrp(group_)->next;
addr_ = &addrs[next_downstream];
auto &next_downstream = group_->next;
auto end = next_downstream;
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Using downstream address idx=" << next_downstream
<< " out of " << addrs.size();
}
for (;;) {
auto &addr = addrs[next_downstream];
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
if (++next_downstream >= addrs.size()) {
next_downstream = 0;
}
auto &connect_blocker = addr.connect_blocker;
if (connect_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Backend server "
<< util::to_numeric_addr(&addr.addr)
<< " was not available temporarily";
}
if (end == next_downstream) {
return -1;
}
continue;
}
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Using downstream address idx=" << next_downstream
<< " out of " << addrs.size();
}
addr_ = &addr;
break;
}
}
auto &connect_blocker = addr_->connect_blocker;
const auto &proxy = get_config()->downstream_http_proxy;
if (!proxy.host.empty() && state_ == DISCONNECTED) {
if (LOG_ENABLED(INFO)) {
@ -288,15 +341,25 @@ int Http2Session::initiate_connection() {
conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this) << "Backend proxy socket() failed; addr="
<< util::to_numeric_addr(&proxy.addr)
<< ", errno=" << error;
worker_blocker->on_failure();
return -1;
}
worker_blocker->on_success();
rv = connect(conn_.fd, &proxy.addr.su.sa, proxy.addr.len);
if (rv != 0 && errno != EINPROGRESS) {
SSLOG(ERROR, this) << "Failed to connect to the proxy " << proxy.host
<< ":" << proxy.port;
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this) << "Backend proxy connect() failed; addr="
<< util::to_numeric_addr(&proxy.addr)
<< ", errno=" << error;
connect_blocker->on_failure();
return -1;
}
@ -335,6 +398,8 @@ int Http2Session::initiate_connection() {
return -1;
}
ssl::setup_downstream_http2_alpn(ssl);
conn_.set_ssl(ssl);
}
@ -348,6 +413,13 @@ int Http2Session::initiate_connection() {
// at the time of this writing).
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
}
auto tls_session = ssl::reuse_tls_session(addr_);
if (tls_session) {
SSL_set_session(conn_.tls.ssl, tls_session);
SSL_SESSION_free(tls_session);
}
// If state_ == PROXY_CONNECTED, we has connected to the proxy
// using conn_.fd and tunnel has been established.
if (state_ == DISCONNECTED) {
@ -356,16 +428,28 @@ int Http2Session::initiate_connection() {
conn_.fd =
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this)
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
worker_blocker->on_failure();
return -1;
}
worker_blocker->on_success();
rv = connect(conn_.fd,
// TODO maybe not thread-safe?
const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
if (rv != 0 && errno != EINPROGRESS) {
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
connect_blocker->on_failure();
return -1;
}
@ -383,14 +467,26 @@ int Http2Session::initiate_connection() {
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
if (conn_.fd == -1) {
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this)
<< "socket() failed; addr=" << util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
worker_blocker->on_failure();
return -1;
}
worker_blocker->on_success();
rv = connect(conn_.fd, const_cast<sockaddr *>(&addr_->addr.su.sa),
addr_->addr.len);
if (rv != 0 && errno != EINPROGRESS) {
connect_blocker_->on_failure();
auto error = errno;
SSLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr_->addr)
<< ", errno=" << error;
connect_blocker->on_failure();
return -1;
}
@ -538,6 +634,17 @@ void Http2Session::remove_downstream_connection(
Http2DownstreamConnection *dconn) {
dconns_.remove(dconn);
dconn->detach_stream_data();
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Remove downstream";
}
if (!in_freelist() && !max_concurrency_reached()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Append to Http2Session freelist";
}
group_->http2_freelist.append(this);
}
}
void Http2Session::remove_stream_data(StreamData *sd) {
@ -694,10 +801,9 @@ void Http2Session::stop_settings_timer() {
}
namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
nghttp2_rcbuf *name, nghttp2_rcbuf *value,
uint8_t flags, void *user_data) {
auto http2session = static_cast<Http2Session *>(user_data);
auto sd = static_cast<StreamData *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
@ -709,6 +815,9 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
auto &resp = downstream->response();
auto &httpconf = get_config()->http;
@ -717,13 +826,14 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
!downstream->get_expect_final_response();
if (resp.fs.buffer_size() + namelen + valuelen >
if (resp.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.response_header_field_buffer ||
resp.fs.num_fields() >= httpconf.max_response_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "Too large or many header field size="
<< resp.fs.buffer_size() + namelen + valuelen
<< ", num=" << resp.fs.num_fields() + 1;
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< resp.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << resp.fs.num_fields() + 1;
}
if (trailer) {
@ -735,17 +845,23 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(namebuf.base, namebuf.len);
auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
downstream->add_rcbuf(name);
downstream->add_rcbuf(value);
if (trailer) {
// just store header fields for trailer part
resp.fs.add_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
resp.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len},
no_index, token);
return 0;
}
auto token = http2::lookup_token(name, namelen);
resp.fs.add_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
resp.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0;
}
case NGHTTP2_PUSH_PROMISE: {
@ -759,27 +875,35 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto promised_downstream = promised_sd->dconn->get_downstream();
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
assert(promised_downstream);
auto &promised_req = promised_downstream->request();
// We use request header limit for PUSH_PROMISE
if (promised_req.fs.buffer_size() + namelen + valuelen >
if (promised_req.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.request_header_field_buffer ||
promised_req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (LOG_ENABLED(INFO)) {
DLOG(INFO, downstream)
<< "Too large or many header field size="
<< promised_req.fs.buffer_size() + namelen + valuelen
<< promised_req.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << promised_req.fs.num_fields() + 1;
}
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
auto token = http2::lookup_token(name, namelen);
promised_req.fs.add_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
promised_downstream->add_rcbuf(name);
promised_downstream->add_rcbuf(value);
auto token = http2::lookup_token(namebuf.base, namebuf.len);
promised_req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len},
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
return 0;
}
}
@ -926,8 +1050,9 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
// Otherwise, use chunked encoding to keep upstream connection
// open. In HTTP2, we are supporsed not to receive
// transfer-encoding.
resp.fs.add_header("transfer-encoding", "chunked",
http2::HD_TRANSFER_ENCODING);
resp.fs.add_header_token(StringRef::from_lit("transfer-encoding"),
StringRef::from_lit("chunked"), false,
http2::HD_TRANSFER_ENCODING);
downstream->set_chunked_response(true);
}
}
@ -1286,8 +1411,8 @@ nghttp2_session_callbacks *create_http2_downstream_callbacks() {
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
@ -1345,13 +1470,12 @@ int Http2Session::connection_made() {
std::array<nghttp2_settings_entry, 3> entry;
size_t nentry = 2;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = http2conf.max_concurrent_streams;
entry[0].value = http2conf.downstream.max_concurrent_streams;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << http2conf.downstream.window_bits) - 1;
if (http2conf.no_server_push || get_config()->http2_proxy ||
get_config()->client_proxy) {
if (http2conf.no_server_push || get_config()->http2_proxy) {
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
entry[nentry].value = 0;
++nentry;
@ -1612,11 +1736,20 @@ int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; }
int Http2Session::write_noop() { return 0; }
int Http2Session::connected() {
auto &connect_blocker = addr_->connect_blocker;
if (!util::check_socket_connected(conn_.fd)) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr);
}
connect_blocker->on_failure();
return -1;
}
connect_blocker_->on_success();
connect_blocker->on_success();
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Connection established";
@ -1728,6 +1861,13 @@ int Http2Session::tls_handshake() {
return -1;
}
if (!SSL_session_reused(conn_.tls.ssl)) {
auto tls_session = SSL_get0_session(conn_.tls.ssl);
if (tls_session) {
ssl::try_cache_tls_session(addr_, tls_session, ev_now(conn_.loop));
}
}
read_ = &Http2Session::read_tls;
write_ = &Http2Session::write_tls;
@ -1816,11 +1956,7 @@ bool Http2Session::should_hard_fail() const {
}
}
const DownstreamAddr *Http2Session::get_addr() const { return addr_; }
size_t Http2Session::get_group() const { return group_; }
size_t Http2Session::get_index() const { return index_; }
DownstreamAddr *Http2Session::get_addr() const { return addr_; }
int Http2Session::handle_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) {
@ -1839,10 +1975,8 @@ int Http2Session::handle_downstream_push_promise(Downstream *downstream,
// promised_downstream->get_stream() still returns 0.
auto handler = upstream->get_client_handler();
auto worker = handler->get_worker();
auto promised_dconn =
make_unique<Http2DownstreamConnection>(worker->get_dconn_pool(), this);
auto promised_dconn = make_unique<Http2DownstreamConnection>(this);
promised_dconn->set_client_handler(handler);
auto ptr = promised_dconn.get();
@ -1867,6 +2001,8 @@ int Http2Session::handle_downstream_push_promise_complete(
Downstream *downstream, Downstream *promised_downstream) {
auto &promised_req = promised_downstream->request();
auto &promised_balloc = promised_downstream->get_block_allocator();
auto authority = promised_req.fs.header(http2::HD__AUTHORITY);
auto path = promised_req.fs.header(http2::HD__PATH);
auto method = promised_req.fs.header(http2::HD__METHOD);
@ -1888,16 +2024,19 @@ int Http2Session::handle_downstream_push_promise_complete(
// TODO Rewrite authority if we enabled rewrite host. But we
// really don't know how to rewrite host. Should we use the same
// host in associated stream?
promised_req.authority = http2::value_to_str(authority);
if (authority) {
promised_req.authority = authority->value;
}
promised_req.method = method_token;
// libnghttp2 ensures that we don't have CONNECT method in
// PUSH_PROMISE, and guarantees that :scheme exists.
promised_req.scheme = http2::value_to_str(scheme);
if (scheme) {
promised_req.scheme = scheme->value;
}
// For server-wide OPTIONS request, path is empty.
if (method_token != HTTP_OPTIONS || path->value != "*") {
promised_req.path = http2::rewrite_clean_path(std::begin(path->value),
std::end(path->value));
promised_req.path = http2::rewrite_clean_path(promised_balloc, path->value);
}
promised_downstream->inspect_http2_request();
@ -1914,4 +2053,29 @@ int Http2Session::handle_downstream_push_promise_complete(
return 0;
}
size_t Http2Session::get_num_dconns() const { return dconns_.size(); }
bool Http2Session::in_freelist() const {
return dlnext != nullptr || dlprev != nullptr ||
group_->http2_freelist.head == this ||
group_->http2_freelist.tail == this;
}
bool Http2Session::max_concurrency_reached(size_t extra) const {
if (!session_) {
return dconns_.size() + extra >= 100;
}
// If session does not allow further requests, it effectively means
// that maximum concurrency is reached.
return !nghttp2_session_check_request_allowed(session_) ||
dconns_.size() + extra >=
nghttp2_session_get_remote_settings(
session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
}
DownstreamAddrGroup *Http2Session::get_downstream_addr_group() const {
return group_;
}
} // namespace shrpx

View File

@ -48,7 +48,8 @@ namespace shrpx {
class Http2DownstreamConnection;
class Worker;
class ConnectBlocker;
struct DownstreamAddrGroup;
struct DownstreamAddr;
struct StreamData {
StreamData *dlnext, *dlprev;
@ -57,9 +58,8 @@ struct StreamData {
class Http2Session {
public:
Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
ConnectBlocker *connect_blocker, Worker *worker, size_t group,
size_t idx);
Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Worker *worker,
DownstreamAddrGroup *group);
~Http2Session();
// If hard is true, all pending requests are abandoned and
@ -147,17 +147,31 @@ public:
void submit_pending_requests();
const DownstreamAddr *get_addr() const;
DownstreamAddr *get_addr() const;
size_t get_group() const;
size_t get_index() const;
DownstreamAddrGroup *get_downstream_addr_group() const;
int handle_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id);
int handle_downstream_push_promise_complete(Downstream *downstream,
Downstream *promised_downstream);
// Returns number of downstream connections, including pushed
// streams.
size_t get_num_dconns() const;
// Returns true if this object is included in freelist. See
// DownstreamAddrGroup object.
bool in_freelist() const;
// Returns true if the maximum concurrency is reached. In other
// words, the number of currently participated streams in this
// session is equal or greater than the max concurrent streams limit
// advertised by server. If |extra| is nonzero, it is added to the
// number of current concurrent streams when comparing against
// server initiated concurrency limit.
bool max_concurrency_reached(size_t extra = 0) const;
enum {
// Disconnected
DISCONNECTED,
@ -186,6 +200,8 @@ public:
using ReadBuf = Buffer<8_k>;
Http2Session *dlnext, *dlprev;
private:
Connection conn_;
DefaultMemchunks wb_;
@ -203,16 +219,12 @@ private:
// Used to parse the response from HTTP proxy
std::unique_ptr<http_parser> proxy_htp_;
Worker *worker_;
ConnectBlocker *connect_blocker_;
// NULL if no TLS is configured
SSL_CTX *ssl_ctx_;
DownstreamAddrGroup *group_;
// Address of remote endpoint
const DownstreamAddr *addr_;
DownstreamAddr *addr_;
nghttp2_session *session_;
size_t group_;
// index inside group, this is used to pin frontend to certain
// HTTP/2 backend for better throughput.
size_t index_;
int state_;
int connection_check_state_;
bool flow_control_;

View File

@ -108,7 +108,7 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
int rv;
auto http2_settings = http->get_downstream()->get_http2_settings();
auto http2_settings = http->get_downstream()->get_http2_settings().str();
util::to_base64(http2_settings);
auto settings_payload =
@ -154,13 +154,15 @@ void Http2Upstream::stop_settings_timer() {
}
namespace {
int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen,
const uint8_t *value, size_t valuelen, uint8_t flags,
void *user_data) {
int on_header_callback2(nghttp2_session *session, const nghttp2_frame *frame,
nghttp2_rcbuf *name, nghttp2_rcbuf *value,
uint8_t flags, void *user_data) {
auto namebuf = nghttp2_rcbuf_get_buf(name);
auto valuebuf = nghttp2_rcbuf_get_buf(value);
if (get_config()->http2.upstream.debug.frame_debug) {
verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
flags, user_data);
verbose_on_header_callback(session, frame, namebuf.base, namebuf.len,
valuebuf.base, valuebuf.len, flags, user_data);
}
if (frame->hd.type != NGHTTP2_HEADERS) {
return 0;
@ -176,7 +178,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
auto &httpconf = get_config()->http;
if (req.fs.buffer_size() + namelen + valuelen >
if (req.fs.buffer_size() + namebuf.len + valuebuf.len >
httpconf.request_header_field_buffer ||
req.fs.num_fields() >= httpconf.max_request_header_fields) {
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
@ -185,7 +187,7 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
if (LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "Too large or many header field size="
<< req.fs.buffer_size() + namelen + valuelen
<< req.fs.buffer_size() + namebuf.len + valuebuf.len
<< ", num=" << req.fs.num_fields() + 1;
}
@ -201,17 +203,23 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
return 0;
}
auto token = http2::lookup_token(namebuf.base, namebuf.len);
auto no_index = flags & NGHTTP2_NV_FLAG_NO_INDEX;
downstream->add_rcbuf(name);
downstream->add_rcbuf(value);
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
// just store header fields for trailer part
req.fs.add_trailer(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
req.fs.add_trailer_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0;
}
auto token = http2::lookup_token(name, namelen);
req.fs.add_header(name, namelen, value, valuelen,
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
req.fs.add_header_token(StringRef{namebuf.base, namebuf.len},
StringRef{valuebuf.base, valuebuf.len}, no_index,
token);
return 0;
}
} // namespace
@ -302,7 +310,9 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
}
req.method = method_token;
req.scheme = http2::value_to_str(scheme);
if (scheme) {
req.scheme = scheme->value;
}
// nghttp2 library guarantees either :authority or host exist
if (!authority) {
@ -310,16 +320,18 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
authority = req.fs.header(http2::HD_HOST);
}
req.authority = http2::value_to_str(authority);
if (authority) {
req.authority = authority->value;
}
if (path) {
if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty.
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
req.path = http2::value_to_str(path);
} else if (get_config()->http2_proxy) {
req.path = path->value;
} else {
const auto &value = path->value;
req.path = http2::rewrite_clean_path(std::begin(value), std::end(value));
req.path = http2::rewrite_clean_path(downstream->get_block_allocator(),
path->value);
}
}
@ -410,6 +422,9 @@ int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
verbose_on_frame_recv_callback(session, frame, user_data);
}
auto upstream = static_cast<Http2Upstream *>(user_data);
auto handler = upstream->get_client_handler();
handler->signal_reset_upstream_conn_rtimer();
switch (frame->hd.type) {
case NGHTTP2_DATA: {
@ -576,25 +591,33 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
req.http_major = 2;
req.http_minor = 0;
auto &promised_balloc = promised_downstream->get_block_allocator();
for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
auto &nv = frame->push_promise.nva[i];
auto name =
make_string_ref(promised_balloc, StringRef{nv.name, nv.namelen});
auto value =
make_string_ref(promised_balloc, StringRef{nv.value, nv.valuelen});
auto token = http2::lookup_token(nv.name, nv.namelen);
switch (token) {
case http2::HD__METHOD:
req.method = http2::lookup_method_token(nv.value, nv.valuelen);
req.method = http2::lookup_method_token(value);
break;
case http2::HD__SCHEME:
req.scheme.assign(nv.value, nv.value + nv.valuelen);
req.scheme = value;
break;
case http2::HD__AUTHORITY:
req.authority.assign(nv.value, nv.value + nv.valuelen);
req.authority = value;
break;
case http2::HD__PATH:
req.path = http2::rewrite_clean_path(nv.value, nv.value + nv.valuelen);
req.path = http2::rewrite_clean_path(promised_balloc, value);
break;
}
req.fs.add_header(nv.name, nv.namelen, nv.value, nv.valuelen,
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
req.fs.add_header_token(name, value, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
token);
}
promised_downstream->inspect_http2_request();
@ -800,8 +823,8 @@ nghttp2_session_callbacks *create_http2_upstream_callbacks() {
nghttp2_session_callbacks_set_on_frame_not_send_callback(
callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_header_callback2(callbacks,
on_header_callback2);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
@ -814,6 +837,11 @@ nghttp2_session_callbacks *create_http2_upstream_callbacks() {
callbacks, http::select_padding_callback);
}
if (get_config()->http2.upstream.debug.frame_debug) {
nghttp2_session_callbacks_set_error_callback(callbacks,
verbose_error_callback);
}
return callbacks;
}
@ -822,9 +850,7 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
downstream_queue_(
get_config()->http2_proxy
? get_config()->conn.downstream.connections_per_host
: get_config()->conn.downstream.proto == PROTO_HTTP
? get_config()->conn.downstream.connections_per_frontend
: 0,
: get_config()->conn.downstream.connections_per_frontend,
!get_config()->http2_proxy),
handler_(handler),
session_(nullptr),
@ -844,7 +870,7 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
// TODO Maybe call from outside?
std::array<nghttp2_settings_entry, 2> entry;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = http2conf.max_concurrent_streams;
entry[0].value = http2conf.upstream.max_concurrent_streams;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << http2conf.upstream.window_bits) - 1;
@ -1208,20 +1234,20 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
const auto &resp = downstream->response();
auto &httpconf = get_config()->http;
auto &balloc = downstream->get_block_allocator();
const auto &headers = resp.fs.headers();
auto nva = std::vector<nghttp2_nv>();
// 2 for :status and server
nva.reserve(2 + headers.size() + httpconf.add_response_headers.size());
std::string status_code_str;
auto response_status_const = http2::stringify_status(resp.http_status);
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
status_code_str = util::utos(resp.http_status);
nva.push_back(http2::make_nv_ls(":status", status_code_str));
auto response_status = http2::stringify_status(resp.http_status);
if (response_status.empty()) {
response_status = util::make_string_ref_uint(balloc, resp.http_status);
}
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
@ -1269,6 +1295,8 @@ int Http2Upstream::error_reply(Downstream *downstream,
int rv;
auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
auto html = http::create_error_html(status_code);
resp.http_status = status_code;
auto body = downstream->get_response_buf();
@ -1282,20 +1310,20 @@ int Http2Upstream::error_reply(Downstream *downstream,
auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now());
auto response_status_const = http2::stringify_status(status_code);
auto content_length = util::utos(html.size());
auto response_status = http2::stringify_status(status_code);
if (response_status.empty()) {
response_status = util::make_string_ref_uint(balloc, status_code);
}
std::string status_code_str;
auto content_length = util::make_string_ref_uint(balloc, html.size());
auto date = make_string_ref(balloc, StringRef{lgconf->time_http_str});
auto nva = make_array(
response_status_const
? http2::make_nv_lc_nocopy(":status", response_status_const)
: http2::make_nv_ls(":status",
(status_code_str = util::utos(status_code))),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ls("date", lgconf->time_http_str));
auto nva = std::array<nghttp2_nv, 5>{
{http2::make_nv_ls_nocopy(":status", response_status),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_ls_nocopy("server", get_config()->http.server_name),
http2::make_nv_ls_nocopy("content-length", content_length),
http2::make_nv_ls_nocopy("date", date)}};
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
nva.data(), nva.size(), &data_prd);
@ -1336,6 +1364,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
const auto &req = downstream->request();
auto &resp = downstream->response();
auto &balloc = downstream->get_block_allocator();
if (LOG_ENABLED(INFO)) {
if (downstream->get_non_final_response()) {
DLOG(INFO, downstream) << "HTTP non-final response header";
@ -1346,8 +1376,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!httpconf.no_location_rewrite) {
if (!get_config()->http2_proxy && !httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header(req.scheme);
}
@ -1375,17 +1404,14 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
// field.
nva.reserve(resp.fs.headers().size() + 4 +
httpconf.add_response_headers.size());
std::string via_value;
std::string response_status;
auto response_status_const = http2::stringify_status(resp.http_status);
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
response_status = util::utos(resp.http_status);
nva.push_back(http2::make_nv_ls(":status", response_status));
auto response_status = http2::stringify_status(resp.http_status);
if (response_status.empty()) {
response_status = util::make_string_ref_uint(balloc, resp.http_status);
}
nva.push_back(http2::make_nv_ls_nocopy(":status", response_status));
if (downstream->get_non_final_response()) {
http2::copy_headers_to_nva(nva, resp.fs.headers());
@ -1416,7 +1442,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers());
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
if (!get_config()->http2_proxy) {
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
} else {
auto server = resp.fs.header(http2::HD_SERVER);
@ -1431,13 +1457,23 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
}
} else {
// we don't create more than 16 bytes in
// http::create_via_header_value.
size_t len = 16;
if (via) {
via_value = (*via).value;
via_value += ", ";
len += via->value.size() + 2;
}
via_value +=
http::create_via_header_value(resp.http_major, resp.http_minor);
nva.push_back(http2::make_nv_ls("via", via_value));
auto iov = make_byte_ref(balloc, len + 1);
auto p = iov.base;
if (via) {
p = std::copy(std::begin(via->value), std::end(via->value), p);
p = util::copy_lit(p, ", ");
}
p = http::create_via_header_value(p, resp.http_major, resp.http_minor);
*p = '\0';
nva.push_back(http2::make_nv_ls_nocopy("via", StringRef{iov.base, p}));
}
for (auto &p : httpconf.add_response_headers) {
@ -1489,9 +1525,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
if (!http2conf.no_server_push &&
nghttp2_session_get_remote_settings(session_,
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
!get_config()->http2_proxy && !get_config()->client_proxy &&
(downstream->get_stream_id() % 2) && resp.fs.header(http2::HD_LINK) &&
resp.http_status == 200 &&
!get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
resp.fs.header(http2::HD_LINK) && resp.http_status == 200 &&
(req.method == HTTP_GET || req.method == HTTP_POST)) {
if (prepare_push_promise(downstream) != 0) {
@ -1693,6 +1728,11 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
for (auto downstream = downstream_queue_.get_downstreams(); downstream;
downstream = downstream->dlnext) {
if (downstream->get_dispatch_state() != Downstream::DISPATCH_ACTIVE) {
// This is error condition when we failed push_request_headers()
// in initiate_downstream(). Otherwise, we have
// Downstream::DISPATCH_ACTIVE state, or we did not set
// DownstreamConnection.
downstream->pop_downstream_connection();
continue;
}
@ -1736,17 +1776,17 @@ int Http2Upstream::on_downstream_reset(bool no_retry) {
int Http2Upstream::prepare_push_promise(Downstream *downstream) {
int rv;
const char *base;
size_t baselen;
const auto &req = downstream->request();
const auto &resp = downstream->response();
rv = http2::get_pure_path_component(&base, &baselen, req.path);
if (rv != 0) {
auto base = http2::get_pure_path_component(req.path);
if (base.empty()) {
return 0;
}
auto &balloc = downstream->get_block_allocator();
for (auto &kv : resp.fs.headers()) {
if (kv.token != http2::HD_LINK) {
continue;
@ -1754,31 +1794,23 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
for (auto &link :
http2::parse_link_header(kv.value.c_str(), kv.value.size())) {
auto uri = link.uri.first;
auto len = link.uri.second - link.uri.first;
StringRef scheme, authority, path;
const std::string *scheme_ptr, *authority_ptr;
std::string scheme, authority, path;
rv = http2::construct_push_component(scheme, authority, path, base,
baselen, uri, len);
rv = http2::construct_push_component(balloc, scheme, authority, path,
base, link.uri);
if (rv != 0) {
continue;
}
if (scheme.empty()) {
scheme_ptr = &req.scheme;
} else {
scheme_ptr = &scheme;
scheme = req.scheme;
}
if (authority.empty()) {
authority_ptr = &req.authority;
} else {
authority_ptr = &authority;
authority = req.authority;
}
rv = submit_push_promise(*scheme_ptr, *authority_ptr, path, downstream);
rv = submit_push_promise(scheme, authority, path, downstream);
if (rv != 0) {
return -1;
}
@ -1787,9 +1819,9 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) {
return 0;
}
int Http2Upstream::submit_push_promise(const std::string &scheme,
const std::string &authority,
const std::string &path,
int Http2Upstream::submit_push_promise(const StringRef &scheme,
const StringRef &authority,
const StringRef &path,
Downstream *downstream) {
const auto &req = downstream->request();
@ -1799,9 +1831,9 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
// juse use "GET" for now
nva.push_back(http2::make_nv_ll(":method", "GET"));
nva.push_back(http2::make_nv_ls(":scheme", scheme));
nva.push_back(http2::make_nv_ls(":path", path));
nva.push_back(http2::make_nv_ls(":authority", authority));
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
nva.push_back(http2::make_nv_ls_nocopy(":path", path));
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
for (auto &kv : req.fs.headers()) {
switch (kv.token) {
@ -1852,49 +1884,42 @@ bool Http2Upstream::push_enabled() const {
return !(get_config()->http2.no_server_push ||
nghttp2_session_get_remote_settings(
session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
get_config()->http2_proxy || get_config()->client_proxy);
get_config()->http2_proxy);
}
int Http2Upstream::initiate_push(Downstream *downstream, const char *uri,
size_t len) {
int Http2Upstream::initiate_push(Downstream *downstream, const StringRef &uri) {
int rv;
if (len == 0 || !push_enabled() || (downstream->get_stream_id() % 2)) {
if (uri.empty() || !push_enabled() || (downstream->get_stream_id() % 2)) {
return 0;
}
const char *base;
size_t baselen;
const auto &req = downstream->request();
rv = http2::get_pure_path_component(&base, &baselen, req.path);
if (rv != 0) {
auto base = http2::get_pure_path_component(req.path);
if (base.empty()) {
return -1;
}
const std::string *scheme_ptr, *authority_ptr;
std::string scheme, authority, path;
auto &balloc = downstream->get_block_allocator();
rv = http2::construct_push_component(scheme, authority, path, base, baselen,
uri, len);
StringRef scheme, authority, path;
rv = http2::construct_push_component(balloc, scheme, authority, path, base,
uri);
if (rv != 0) {
return -1;
}
if (scheme.empty()) {
scheme_ptr = &req.scheme;
} else {
scheme_ptr = &scheme;
scheme = req.scheme;
}
if (authority.empty()) {
authority_ptr = &req.authority;
} else {
authority_ptr = &authority;
authority = req.authority;
}
rv = submit_push_promise(*scheme_ptr, *authority_ptr, path, downstream);
rv = submit_push_promise(scheme, authority, path, downstream);
if (rv != 0) {
return -1;
@ -1952,7 +1977,7 @@ int Http2Upstream::on_downstream_push_promise_complete(
nva.reserve(headers.size());
for (auto &kv : headers) {
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
}
auto promised_stream_id = nghttp2_submit_push_promise(

View File

@ -81,8 +81,7 @@ public:
virtual int on_downstream_reset(bool no_retry);
virtual int send_reply(Downstream *downstream, const uint8_t *body,
size_t bodylen);
virtual int initiate_push(Downstream *downstream, const char *uri,
size_t len);
virtual int initiate_push(Downstream *downstream, const StringRef &uri);
virtual int response_riovec(struct iovec *iov, int iovcnt) const;
virtual void response_drain(size_t n);
virtual bool response_empty() const;
@ -112,9 +111,8 @@ public:
void check_shutdown();
int prepare_push_promise(Downstream *downstream);
int submit_push_promise(const std::string &scheme,
const std::string &authority, const std::string &path,
Downstream *downstream);
int submit_push_promise(const StringRef &scheme, const StringRef &authority,
const StringRef &path, Downstream *downstream);
int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);

View File

@ -111,64 +111,56 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
}
} // namespace
HttpDownstreamConnection::HttpDownstreamConnection(
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop,
Worker *worker)
: DownstreamConnection(dconn_pool),
conn_(loop, -1, nullptr, worker->get_mcpool(),
HttpDownstreamConnection::HttpDownstreamConnection(DownstreamAddrGroup *group,
struct ev_loop *loop,
Worker *worker)
: conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, connectcb,
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout),
get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP1),
do_read_(&HttpDownstreamConnection::noop),
do_write_(&HttpDownstreamConnection::noop),
worker_(worker),
ssl_ctx_(worker->get_cl_ssl_ctx()),
group_(group),
addr_(nullptr),
ioctrl_(&conn_.rlimit),
response_htp_{0},
group_(group) {}
response_htp_{0} {}
HttpDownstreamConnection::~HttpDownstreamConnection() {
if (conn_.tls.ssl && conn_.tls.initial_handshake_done) {
auto session = SSL_get0_session(conn_.tls.ssl);
if (session) {
worker_->cache_client_tls_session(&addr_->addr, session,
ev_now(conn_.loop));
}
}
}
HttpDownstreamConnection::~HttpDownstreamConnection() {}
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
}
auto worker_blocker = worker_->get_connect_blocker();
if (worker_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this)
<< "Worker wide backend connection was blocked temporarily";
}
return SHRPX_ERR_NETWORK;
}
auto &downstreamconf = get_config()->conn.downstream;
if (conn_.fd == -1) {
auto connect_blocker = client_handler_->get_connect_blocker();
if (connect_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this)
<< "Downstream connection was blocked by connect_blocker";
}
return -1;
}
if (ssl_ctx_) {
auto ssl = ssl::create_ssl(ssl_ctx_);
if (!ssl) {
return -1;
}
ssl::setup_downstream_http1_alpn(ssl);
conn_.set_ssl(ssl);
}
auto &next_downstream = worker_->get_dgrp(group_)->next;
auto &addrs = group_->addrs;
auto &next_downstream = group_->next;
auto end = next_downstream;
auto &addrs = downstreamconf.addr_groups[group_].addrs;
for (;;) {
auto &addr = addrs[next_downstream];
@ -176,22 +168,44 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
next_downstream = 0;
}
auto &connect_blocker = addr.connect_blocker;
if (connect_blocker->blocked()) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "Backend server "
<< util::to_numeric_addr(&addr.addr)
<< " was not available temporarily";
}
if (end == next_downstream) {
return SHRPX_ERR_NETWORK;
}
continue;
}
conn_.fd = util::create_nonblock_socket(addr.addr.su.storage.ss_family);
if (conn_.fd == -1) {
auto error = errno;
DCLOG(WARN, this) << "socket() failed; errno=" << error;
DCLOG(WARN, this) << "socket() failed; addr="
<< util::to_numeric_addr(&addr.addr)
<< ", errno=" << error;
connect_blocker->on_failure();
worker_blocker->on_failure();
return SHRPX_ERR_NETWORK;
}
worker_blocker->on_success();
int rv;
rv = connect(conn_.fd, &addr.addr.su.sa, addr.addr.len);
if (rv != 0 && errno != EINPROGRESS) {
auto error = errno;
DCLOG(WARN, this) << "connect() failed; errno=" << error;
DCLOG(WARN, this) << "connect() failed; addr="
<< util::to_numeric_addr(&addr.addr)
<< ", errno=" << error;
connect_blocker->on_failure();
close(conn_.fd);
@ -219,7 +233,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
}
auto session = worker_->reuse_client_tls_session(&addr_->addr);
auto session = ssl::reuse_tls_session(addr_);
if (session) {
SSL_set_session(conn_.tls.ssl, session);
SSL_SESSION_free(session);
@ -258,6 +272,8 @@ int HttpDownstreamConnection::push_request_headers() {
const auto &downstream_hostport = addr_->hostport;
const auto &req = downstream_->request();
auto &balloc = downstream_->get_block_allocator();
auto connect_method = req.method == HTTP_CONNECT;
auto &httpconf = get_config()->http;
@ -265,26 +281,25 @@ int HttpDownstreamConnection::push_request_headers() {
// For HTTP/1.0 request, there is no authority in request. In that
// case, we use backend server's host nonetheless.
auto authority = StringRef(downstream_hostport);
auto no_host_rewrite = httpconf.no_host_rewrite ||
get_config()->http2_proxy ||
get_config()->client_proxy || connect_method;
auto no_host_rewrite =
httpconf.no_host_rewrite || get_config()->http2_proxy || connect_method;
if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority);
authority = req.authority;
}
downstream_->set_request_downstream_host(authority.str());
downstream_->set_request_downstream_host(authority);
auto buf = downstream_->get_request_buf();
// Assume that method and request path do not contain \r\n.
auto meth = http2::to_method_string(req.method);
buf->append(meth, strlen(meth));
buf->append(meth);
buf->append(" ");
if (connect_method) {
buf->append(authority);
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
} else if (get_config()->http2_proxy) {
// Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy.
assert(!req.scheme.empty());
@ -349,14 +364,14 @@ int HttpDownstreamConnection::push_request_headers() {
if (fwdconf.params) {
auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy ||
connect_method) {
if (get_config()->http2_proxy || connect_method) {
params &= ~FORWARDED_PROTO;
}
auto value = http::create_forwarded(params, handler->get_forwarded_by(),
handler->get_forwarded_for(),
req.authority, req.scheme);
auto value = http::create_forwarded(
balloc, params, handler->get_forwarded_by(),
handler->get_forwarded_for(), req.authority, req.scheme);
if (fwd || !value.empty()) {
buf->append("Forwarded: ");
if (fwd) {
@ -393,8 +408,7 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append((*xff).value);
buf->append("\r\n");
}
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
!connect_method) {
if (!get_config()->http2_proxy && !connect_method) {
buf->append("X-Forwarded-Proto: ");
assert(!req.scheme.empty());
buf->append(req.scheme);
@ -413,7 +427,10 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append((*via).value);
buf->append(", ");
}
buf->append(http::create_via_header_value(req.http_major, req.http_minor));
std::array<char, 16> viabuf;
auto end = http::create_via_header_value(viabuf.data(), req.http_major,
req.http_minor);
buf->append(viabuf.data(), end - viabuf.data());
buf->append("\r\n");
}
@ -494,8 +511,8 @@ void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection EOF";
}
auto dconn_pool = dconn->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool.remove_downstream_connection(dconn);
// dconn was deleted
}
} // namespace
@ -507,8 +524,8 @@ void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection timeout";
}
auto dconn_pool = dconn->get_dconn_pool();
dconn_pool->remove_downstream_connection(dconn);
auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool.remove_downstream_connection(dconn);
// dconn was deleted
}
} // namespace
@ -572,7 +589,7 @@ int htp_hdrs_completecb(http_parser *htp) {
resp.http_major = htp->http_major;
resp.http_minor = htp->http_minor;
if (resp.fs.index_headers() != 0) {
if (resp.fs.parse_content_length() != 0) {
downstream->set_response_state(Downstream::MSG_BAD_HEADER);
return -1;
}
@ -679,6 +696,7 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
auto downstream = static_cast<Downstream *>(htp->data);
auto &resp = downstream->response();
auto &httpconf = get_config()->http;
auto &balloc = downstream->get_block_allocator();
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
return -1;
@ -691,7 +709,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
if (ensure_max_header_fields(downstream, httpconf) != 0) {
return -1;
}
resp.fs.add_header(std::string(data, len), "");
auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
resp.fs.add_header_token(name, StringRef{}, false, token);
}
} else {
// trailer part
@ -704,7 +724,9 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
// wrong place or crash if trailer fields are currently empty.
return -1;
}
resp.fs.add_trailer(std::string(data, len), "");
auto name = http2::copy_lower(balloc, StringRef{data, len});
auto token = http2::lookup_token(name);
resp.fs.add_trailer_token(name, StringRef{}, false, token);
}
}
return 0;
@ -869,6 +891,13 @@ int HttpDownstreamConnection::tls_handshake() {
return -1;
}
if (!SSL_session_reused(conn_.tls.ssl)) {
auto session = SSL_get0_session(conn_.tls.ssl);
if (session) {
ssl::try_cache_tls_session(addr_, session, ev_now(conn_.loop));
}
}
do_read_ = &HttpDownstreamConnection::read_tls;
do_write_ = &HttpDownstreamConnection::write_tls;
@ -1012,22 +1041,25 @@ int HttpDownstreamConnection::process_input(const uint8_t *data,
}
int HttpDownstreamConnection::connected() {
auto connect_blocker = client_handler_->get_connect_blocker();
auto &connect_blocker = addr_->connect_blocker;
if (!util::check_socket_connected(conn_.fd)) {
conn_.wlimit.stopw();
if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "downstream connect failed";
DCLOG(INFO, this) << "Backend connect failed; addr="
<< util::to_numeric_addr(&addr_->addr);
}
connect_blocker->on_failure();
downstream_->set_request_state(Downstream::CONNECT_FAIL);
return -1;
}
if (LOG_ENABLED(INFO)) {
DLOG(INFO, this) << "Connected to downstream host";
DCLOG(INFO, this) << "Connected to downstream host";
}
connect_blocker->on_success();
@ -1059,8 +1091,11 @@ void HttpDownstreamConnection::signal_write() {
ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE);
}
size_t HttpDownstreamConnection::get_group() const { return group_; }
int HttpDownstreamConnection::noop() { return 0; }
DownstreamAddrGroup *
HttpDownstreamConnection::get_downstream_addr_group() const {
return group_;
}
} // namespace shrpx

Some files were not shown because too many files have changed in this diff Show More