Merge branch 'master' into simple-extensions
This commit is contained in:
commit
fc39f2d9d2
|
@ -17,7 +17,7 @@ BreakBeforeTernaryOperators: true
|
|||
BreakConstructorInitializersBeforeComma: false
|
||||
BinPackParameters: true
|
||||
ColumnLimit: 80
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
DerivePointerAlignment: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
IndentCaseLabels: false
|
||||
|
|
13
README.rst
13
README.rst
|
@ -1462,3 +1462,16 @@ full real name when contributing!
|
|||
See `Contribution Guidelines
|
||||
<https://nghttp2.org/documentation/contribute.html>`_ for more
|
||||
details.
|
||||
|
||||
Release schedule
|
||||
----------------
|
||||
|
||||
In general, we follow `Semantic Versioning <http://semver.org/>`_. We
|
||||
release MINOR version update every month, and usually we ship it
|
||||
around 25th day of every month.
|
||||
|
||||
We may release PATCH releases between the regular releases, mainly for
|
||||
severe security bug fixes.
|
||||
|
||||
We have no plan to break API compatibility changes involving soname
|
||||
bump, so MAJOR version will stay 1 for the foreseeable future.
|
||||
|
|
59
configure.ac
59
configure.ac
|
@ -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.6.1-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
AC_INIT([nghttp2], [1.7.1-DEV], [t-tujikawa@users.sourceforge.net])
|
||||
AC_CONFIG_AUX_DIR([.])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
@ -47,7 +47,7 @@ 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, 0)
|
||||
AC_SUBST(LT_REVISION, 1)
|
||||
AC_SUBST(LT_AGE, 4)
|
||||
|
||||
major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
|
||||
|
@ -189,10 +189,20 @@ else
|
|||
d])
|
||||
fi
|
||||
|
||||
save_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS=
|
||||
|
||||
AX_CXX_COMPILE_STDCXX_11([noext], [optional])
|
||||
|
||||
CXX1XCXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$save_CXXFLAGS"
|
||||
AC_SUBST([CXX1XCXXFLAGS])
|
||||
|
||||
AC_LANG_PUSH(C++)
|
||||
|
||||
save_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $CXX1XCXXFLAGS"
|
||||
|
||||
# Check that std::future is available.
|
||||
AC_MSG_CHECKING([whether std::future is available])
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
|
||||
|
@ -226,6 +236,8 @@ std::map<int, int>().emplace(1, 2);
|
|||
[have_std_map_emplace=no
|
||||
AC_MSG_RESULT([no])])
|
||||
|
||||
CXXFLAGS=$save_CXXFLAGS
|
||||
|
||||
AC_LANG_POP()
|
||||
|
||||
# Checks for libraries.
|
||||
|
@ -256,9 +268,16 @@ if test "x${have_zlib}" = "xno"; then
|
|||
fi
|
||||
|
||||
# dl: openssl requires libdl when it is statically linked.
|
||||
LIBS_OLD=$LIBS
|
||||
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
|
||||
LIBS=$LIBS_OLD
|
||||
case "${host_os}" in
|
||||
*bsd*)
|
||||
# dlopen is in libc on *BSD
|
||||
;;
|
||||
*)
|
||||
save_LIBS=$LIBS
|
||||
AC_SEARCH_LIBS([dlopen], [dl], [APPLDFLAGS="-ldl $APPLDFLAGS"], [], [])
|
||||
LIBS=$save_LIBS
|
||||
;;
|
||||
esac
|
||||
|
||||
# cunit
|
||||
PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
|
||||
|
@ -291,7 +310,7 @@ AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
|
|||
|
||||
# libev (for src)
|
||||
# libev does not have pkg-config file. Check it in an old way.
|
||||
LIBS_OLD=$LIBS
|
||||
save_LIBS=$LIBS
|
||||
# android requires -lm for floor
|
||||
AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm])
|
||||
if test "x${have_libev}" = "xyes"; then
|
||||
|
@ -303,7 +322,7 @@ if test "x${have_libev}" = "xyes"; then
|
|||
AC_SUBST([LIBEV_CFLAGS])
|
||||
fi
|
||||
fi
|
||||
LIBS=$LIBS_OLD
|
||||
LIBS=$save_LIBS
|
||||
|
||||
# openssl (for src)
|
||||
PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
|
||||
|
@ -349,10 +368,9 @@ AM_CONDITIONAL([HAVE_LIBXML2], [ test "x${have_libxml2}" = "xyes" ])
|
|||
# jemalloc
|
||||
have_jemalloc=no
|
||||
if test "x${request_jemalloc}" != "xno"; then
|
||||
LIBS_OLD=$LIBS
|
||||
save_LIBS=$LIBS
|
||||
AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
|
||||
[$PTHREAD_LDFLAGS])
|
||||
LIBS=$LIBS_OLD
|
||||
|
||||
if test "x${have_jemalloc}" = "xyes"; then
|
||||
jemalloc_libs=${ac_cv_search_malloc_stats_print}
|
||||
|
@ -360,13 +378,14 @@ if test "x${request_jemalloc}" != "xno"; then
|
|||
# On Darwin, malloc_stats_print is je_malloc_stats_print
|
||||
AC_SEARCH_LIBS([je_malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
|
||||
[$PTHREAD_LDFLAGS])
|
||||
LIBS=$LIBS_OLD
|
||||
|
||||
if test "x${have_jemalloc}" = "xyes"; then
|
||||
jemalloc_libs=${ac_cv_search_je_malloc_stats_print}
|
||||
fi
|
||||
fi
|
||||
|
||||
LIBS=$save_LIBS
|
||||
|
||||
if test "x${have_jemalloc}" = "xyes" &&
|
||||
test "x${jemalloc_libs}" != "xnone required"; then
|
||||
JEMALLOC_LIBS=${jemalloc_libs}
|
||||
|
@ -642,10 +661,14 @@ AM_CONDITIONAL([ENABLE_TINY_NGHTTPD],
|
|||
[ test "x${have_epoll}" = "xyes" &&
|
||||
test "x${have_timerfd_create}" = "xyes"])
|
||||
|
||||
ac_save_CFLAGS=$CFLAGS
|
||||
save_CFLAGS=$CFLAGS
|
||||
save_CXXFLAGS=$CXXFLAGS
|
||||
|
||||
CFLAGS=
|
||||
CXXFLAGS=
|
||||
|
||||
if test "x$werror" != "xno"; then
|
||||
# For C compiler
|
||||
AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wextra], [CFLAGS="$CFLAGS -Wextra"])
|
||||
AX_CHECK_COMPILE_FLAG([-Werror], [CFLAGS="$CFLAGS -Werror"])
|
||||
|
@ -691,12 +714,23 @@ if test "x$werror" != "xno"; then
|
|||
AX_CHECK_COMPILE_FLAG([-Wredundant-decls], [CFLAGS="$CFLAGS -Wredundant-decls"])
|
||||
# Only work with Clang for the moment
|
||||
AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"])
|
||||
|
||||
# For C++ compiler
|
||||
AC_LANG_PUSH(C++)
|
||||
AX_CHECK_COMPILE_FLAG([-Wall], [CXXFLAGS="$CXXFLAGS -Wall"])
|
||||
AX_CHECK_COMPILE_FLAG([-Werror], [CXXFLAGS="$CXXFLAGS -Werror"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wformat-security], [CXXFLAGS="$CXXFLAGS -Wformat-security"])
|
||||
AC_LANG_POP()
|
||||
fi
|
||||
|
||||
WARNCFLAGS=$CFLAGS
|
||||
CFLAGS=$ac_save_CFLAGS
|
||||
WARNCXXFLAGS=$CXXFLAGS
|
||||
|
||||
CFLAGS=$save_CFLAGS
|
||||
CXXFLAGS=$save_CXXFLAGS
|
||||
|
||||
AC_SUBST([WARNCFLAGS])
|
||||
AC_SUBST([WARNCXXFLAGS])
|
||||
|
||||
EXTRACFLAG=
|
||||
AX_CHECK_COMPILE_FLAG([-fvisibility=hidden], [EXTRACFLAG="-fvisibility=hidden"])
|
||||
|
@ -782,6 +816,7 @@ AC_MSG_NOTICE([summary of build options:
|
|||
C preprocessor: ${CPP}
|
||||
CPPFLAGS: ${CPPFLAGS}
|
||||
WARNCFLAGS: ${WARNCFLAGS}
|
||||
CXX1XCXXFLAGS: ${CXX1XCXXFLAGS}
|
||||
EXTRACFLAG: ${EXTRACFLAG}
|
||||
LIBS: ${LIBS}
|
||||
Library:
|
||||
|
|
|
@ -8,7 +8,7 @@ _nghttpd()
|
|||
_get_comp_words_by_ref cur prev
|
||||
case $cur in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W '--mime-types-file --error-gzip --push --header-table-size --trailer --htdocs --address --padding --verbose --version --help --hexdump --dh-param-file --daemon --verify-client --echo-upload --workers --no-tls --color --early-response --max-concurrent-streams ' -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W '--htdocs --verbose --daemon --echo-upload --error-gzip --push --header-table-size --padding --hexdump --max-concurrent-streams --no-tls --mime-types-file --no-content-length --workers --version --color --early-response --dh-param-file --trailer --address --verify-client --help ' -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
|
|
|
@ -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 --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --mruby-file --stream-read-timeout --tls-ticket-key-memcached --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 --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 --syslog-facility --fastopen --no-location-rewrite --tls-session-cache-memcached --no-ocsp --backend-response-buffer --workers --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 --header-field-buffer --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --padding --stream-write-timeout --cacert --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --backend-no-tls --client-private-key-file --client-cert-file --accept-proxy-protocol --tls-dyn-rec-idle-timeout --verify-client --read-rate ' -- "$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 --backend-request-buffer --backend-http2-connection-window-bits --conf --worker-write-burst --npn-list --fetch-ocsp-response-file --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 --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 --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 --header-field-buffer --frontend-read-timeout --tls-ticket-key-memcached-max-fail --backlog --write-burst --no-server-push --backend-http2-window-bits --padding --stream-write-timeout --cacert --forwarded-by --version --add-response-header --backend-read-timeout --frontend --accesslog-file --http2-proxy --max-header-fields --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
|
||||
|
|
11
doc/h2load.1
11
doc/h2load.1
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "H2LOAD" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
|
||||
.TH "H2LOAD" "1" "January 25, 2016" "1.7.0" "nghttp2"
|
||||
.SH NAME
|
||||
h2load \- HTTP/2 benchmarking tool
|
||||
.
|
||||
|
@ -208,11 +208,16 @@ port values.
|
|||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-B, \-\-base\-uri=<URI>
|
||||
.B \-B, \-\-base\-uri=(<URI>|unix:<PATH>)
|
||||
Specify URI from which the scheme, host and port will be
|
||||
used for all requests. The base URI overrides all
|
||||
values defined either at the command line or inside
|
||||
input files.
|
||||
input files. If argument starts with "unix:", then the
|
||||
rest of the argument will be treated as UNIX domain
|
||||
socket path. The connection is made through that path
|
||||
instead of TCP. In this case, scheme is inferred from
|
||||
the first URI appeared in the command line or inside
|
||||
input files as usual.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
|
|
|
@ -171,12 +171,17 @@ OPTIONS
|
|||
Definition of a base URI overrides all scheme, host or
|
||||
port values.
|
||||
|
||||
.. option:: -B, --base-uri=<URI>
|
||||
.. option:: -B, --base-uri=(<URI>|unix:<PATH>)
|
||||
|
||||
Specify URI from which the scheme, host and port will be
|
||||
used for all requests. The base URI overrides all
|
||||
values defined either at the command line or inside
|
||||
input files.
|
||||
input files. If argument starts with "unix:", then the
|
||||
rest of the argument will be treated as UNIX domain
|
||||
socket path. The connection is made through that path
|
||||
instead of TCP. In this case, scheme is inferred from
|
||||
the first URI appeared in the command line or inside
|
||||
input files as usual.
|
||||
|
||||
.. option:: --npn-list=<LIST>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTP" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
|
||||
.TH "NGHTTP" "1" "January 25, 2016" "1.7.0" "nghttp2"
|
||||
.SH NAME
|
||||
nghttp \- HTTP/2 client
|
||||
.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPD" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
|
||||
.TH "NGHTTPD" "1" "January 25, 2016" "1.7.0" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpd \- HTTP/2 server
|
||||
.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "NGHTTPX" "1" "January 11, 2016" "1.6.1-DEV" "nghttp2"
|
||||
.TH "NGHTTPX" "1" "January 25, 2016" "1.7.0" "nghttp2"
|
||||
.SH NAME
|
||||
nghttpx \- HTTP/2 proxy
|
||||
.
|
||||
|
@ -305,7 +305,7 @@ Default: \fB16K\fP
|
|||
.B \-\-backend\-response\-buffer=<SIZE>
|
||||
Set buffer size used to store backend response.
|
||||
.sp
|
||||
Default: \fB16K\fP
|
||||
Default: \fB128K\fP
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
|
@ -384,7 +384,7 @@ After accepting connection failed, connection listener
|
|||
is disabled for a given amount of time. Specifying 0
|
||||
disables this feature.
|
||||
.sp
|
||||
Default: \fB0\fP
|
||||
Default: \fB30s\fP
|
||||
.UNINDENT
|
||||
.SS SSL/TLS
|
||||
.INDENT 0.0
|
||||
|
@ -845,6 +845,51 @@ requests.
|
|||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-add\-forwarded=<LIST>
|
||||
Append RFC 7239 Forwarded header field with parameters
|
||||
specified in comma delimited list <LIST>. The supported
|
||||
parameters are "by", "for", "host", and "proto". By
|
||||
default, the value of "by" and "for" parameters are
|
||||
obfuscated string. See \fI\%\-\-forwarded\-by\fP and
|
||||
\fI\%\-\-forwarded\-for\fP options respectively. Note that nghttpx
|
||||
does not translate non\-standard X\-Forwarded\-* header
|
||||
fields into Forwarded header field, and vice versa.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-strip\-incoming\-forwarded
|
||||
Strip Forwarded header field from inbound client
|
||||
requests.
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-forwarded\-by=(obfuscated|ip|<VALUE>)
|
||||
Specify the parameter value sent out with "by" parameter
|
||||
of Forwarded header field. If "obfuscated" is given,
|
||||
the string is randomly generated at startup. If "ip" is
|
||||
given, the interface address of the connection,
|
||||
including port number, is sent with "by" parameter.
|
||||
User can also specify the static obfuscated string. The
|
||||
limitation is that it must start with "_", and only
|
||||
consists of character set [A\-Za\-z0\-9._\-], as described
|
||||
in RFC 7239.
|
||||
.sp
|
||||
Default: \fBobfuscated\fP
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-forwarded\-for=(obfuscated|ip)
|
||||
Specify the parameter value sent out with "for"
|
||||
parameter of Forwarded header field. If "obfuscated" is
|
||||
given, the string is randomly generated for each client
|
||||
connection. If "ip" is given, the remote client address
|
||||
of the connection, without port number, is sent with
|
||||
"for" parameter.
|
||||
.sp
|
||||
Default: \fBobfuscated\fP
|
||||
.UNINDENT
|
||||
.INDENT 0.0
|
||||
.TP
|
||||
.B \-\-no\-via
|
||||
Don\(aqt append to Via header field. If Via header field
|
||||
is received, it is left unaltered.
|
||||
|
@ -1010,6 +1055,34 @@ option name with leading \fB\-\-\fP stripped (e.g., \fBfrontend\fP). Put
|
|||
\fB=\fP between option name and value. Don\(aqt put extra leading or
|
||||
trailing spaces.
|
||||
.sp
|
||||
When specifying arguments including characters which have special
|
||||
meaning to a shell, we usually use quotes so that shell does not
|
||||
interpret them. When writing this configuration file, quotes for
|
||||
this purpose must not be used. For example, specify additional
|
||||
request header field, do this:
|
||||
.INDENT 7.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
add\-request\-header=foo: bar
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
instead of:
|
||||
.INDENT 7.0
|
||||
.INDENT 3.5
|
||||
.sp
|
||||
.nf
|
||||
.ft C
|
||||
add\-request\-header="foo: bar"
|
||||
.ft P
|
||||
.fi
|
||||
.UNINDENT
|
||||
.UNINDENT
|
||||
.sp
|
||||
The options which do not take argument in the command\-line \fItake\fP
|
||||
argument in the configuration file. Specify \fByes\fP as an argument
|
||||
(e.g., \fBhttp2\-proxy=yes\fP). If other string is given, it is
|
||||
|
|
|
@ -270,7 +270,7 @@ Performance
|
|||
|
||||
Set buffer size used to store backend response.
|
||||
|
||||
Default: ``16K``
|
||||
Default: ``128K``
|
||||
|
||||
.. option:: --fastopen=<N>
|
||||
|
||||
|
@ -341,7 +341,7 @@ Timeout
|
|||
is disabled for a given amount of time. Specifying 0
|
||||
disables this feature.
|
||||
|
||||
Default: ``0``
|
||||
Default: ``30s``
|
||||
|
||||
|
||||
SSL/TLS
|
||||
|
@ -751,6 +751,47 @@ HTTP
|
|||
Strip X-Forwarded-For header field from inbound client
|
||||
requests.
|
||||
|
||||
.. option:: --add-forwarded=<LIST>
|
||||
|
||||
Append RFC 7239 Forwarded header field with parameters
|
||||
specified in comma delimited list <LIST>. The supported
|
||||
parameters are "by", "for", "host", and "proto". By
|
||||
default, the value of "by" and "for" parameters are
|
||||
obfuscated string. See :option:`--forwarded-by` and
|
||||
:option:`--forwarded-for` options respectively. Note that nghttpx
|
||||
does not translate non-standard X-Forwarded-\* header
|
||||
fields into Forwarded header field, and vice versa.
|
||||
|
||||
.. option:: --strip-incoming-forwarded
|
||||
|
||||
Strip Forwarded header field from inbound client
|
||||
requests.
|
||||
|
||||
.. option:: --forwarded-by=(obfuscated|ip|<VALUE>)
|
||||
|
||||
Specify the parameter value sent out with "by" parameter
|
||||
of Forwarded header field. If "obfuscated" is given,
|
||||
the string is randomly generated at startup. If "ip" is
|
||||
given, the interface address of the connection,
|
||||
including port number, is sent with "by" parameter.
|
||||
User can also specify the static obfuscated string. The
|
||||
limitation is that it must start with "_", and only
|
||||
consists of character set [A-Za-z0-9._-], as described
|
||||
in RFC 7239.
|
||||
|
||||
Default: ``obfuscated``
|
||||
|
||||
.. option:: --forwarded-for=(obfuscated|ip)
|
||||
|
||||
Specify the parameter value sent out with "for"
|
||||
parameter of Forwarded header field. If "obfuscated" is
|
||||
given, the string is randomly generated for each client
|
||||
connection. If "ip" is given, the remote client address
|
||||
of the connection, without port number, is sent with
|
||||
"for" parameter.
|
||||
|
||||
Default: ``obfuscated``
|
||||
|
||||
.. option:: --no-via
|
||||
|
||||
Don't append to Via header field. If Via header field
|
||||
|
@ -913,6 +954,22 @@ FILES
|
|||
``=`` between option name and value. Don't put extra leading or
|
||||
trailing spaces.
|
||||
|
||||
When specifying arguments including characters which have special
|
||||
meaning to a shell, we usually use quotes so that shell does not
|
||||
interpret them. When writing this configuration file, quotes for
|
||||
this purpose must not be used. For example, specify additional
|
||||
request header field, do this:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
add-request-header=foo: bar
|
||||
|
||||
instead of:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
add-request-header="foo: bar"
|
||||
|
||||
The options which do not take argument in the command-line *take*
|
||||
argument in the configuration file. Specify ``yes`` as an argument
|
||||
(e.g., ``http2-proxy=yes``). If other string is given, it is
|
||||
|
|
|
@ -13,6 +13,22 @@ FILES
|
|||
``=`` between option name and value. Don't put extra leading or
|
||||
trailing spaces.
|
||||
|
||||
When specifying arguments including characters which have special
|
||||
meaning to a shell, we usually use quotes so that shell does not
|
||||
interpret them. When writing this configuration file, quotes for
|
||||
this purpose must not be used. For example, specify additional
|
||||
request header field, do this:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
add-request-header=foo: bar
|
||||
|
||||
instead of:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
add-request-header="foo: bar"
|
||||
|
||||
The options which do not take argument in the command-line *take*
|
||||
argument in the configuration file. Specify ``yes`` as an argument
|
||||
(e.g., ``http2-proxy=yes``). If other string is given, it is
|
||||
|
|
|
@ -14,9 +14,10 @@ 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
By default, this mode's frontend connection is encrypted using
|
||||
SSL/TLS. So server's private key and certificate must be supplied to
|
||||
|
@ -30,6 +31,10 @@ available on the frontend and a 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.
|
||||
|
||||
The backend is supposed to be HTTP/1 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
|
||||
|
|
|
@ -24,8 +24,8 @@
|
|||
if ENABLE_EXAMPLES
|
||||
|
||||
AM_CFLAGS = $(WARNCFLAGS)
|
||||
AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
|
||||
AM_CPPFLAGS = \
|
||||
-Wall \
|
||||
-I$(top_srcdir)/lib/includes \
|
||||
-I$(top_builddir)/lib/includes \
|
||||
-I$(top_srcdir)/src/includes \
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python
|
||||
import sys
|
||||
|
||||
def name(i):
|
||||
if i < 0x21:
|
||||
return \
|
||||
['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ',
|
||||
'BS ', 'HT ', 'LF ', 'VT ', 'FF ', 'CR ', 'SO ', 'SI ',
|
||||
'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ',
|
||||
'CAN ', 'EM ', 'SUB ', 'ESC ', 'FS ', 'GS ', 'RS ', 'US ',
|
||||
'SPC '][i]
|
||||
elif i == 0x7f:
|
||||
return 'DEL '
|
||||
|
||||
for i in range(256):
|
||||
if chr(i) in [
|
||||
"-", ".", "_", "~",
|
||||
"!", "$", "&", "'", "(", ")",
|
||||
"*", "+", ",", ";", "=",
|
||||
"%", "@", ":", "[", "]"] or\
|
||||
('0' <= chr(i) and chr(i) <= '9') or \
|
||||
('A' <= chr(i) and chr(i) <= 'Z') or \
|
||||
('a' <= chr(i) and chr(i) <= 'z'):
|
||||
sys.stdout.write('1 /* {} */, '.format(chr(i)))
|
||||
elif (0x21 <= i and i < 0x7f):
|
||||
sys.stdout.write('0 /* {} */, '.format(chr(i)))
|
||||
elif 0x80 <= i:
|
||||
sys.stdout.write('0 /* {} */, '.format(hex(i)))
|
||||
else:
|
||||
sys.stdout.write('0 /* {} */, '.format(name(i)))
|
||||
if (i + 1)%4 == 0:
|
||||
sys.stdout.write('\n')
|
|
@ -17,6 +17,7 @@ HEADERS = [
|
|||
"http2-settings",
|
||||
"server",
|
||||
"via",
|
||||
"forwarded",
|
||||
"x-forwarded-for",
|
||||
"x-forwarded-proto",
|
||||
"alt-svc",
|
||||
|
@ -29,6 +30,7 @@ HEADERS = [
|
|||
"cache-control",
|
||||
"user-agent",
|
||||
"date",
|
||||
"content-type",
|
||||
# disallowed h1 headers
|
||||
'connection',
|
||||
'keep-alive',
|
||||
|
|
|
@ -88,8 +88,6 @@ OPTIONS = [
|
|||
"fetch-ocsp-response-file",
|
||||
"ocsp-update-interval",
|
||||
"no-ocsp",
|
||||
"header-field-buffer",
|
||||
"max-header-fields",
|
||||
"include",
|
||||
"tls-ticket-key-cipher",
|
||||
"host-rewrite",
|
||||
|
@ -103,7 +101,20 @@ OPTIONS = [
|
|||
"conf",
|
||||
"fastopen",
|
||||
"tls-dyn-rec-warmup-threshold",
|
||||
"tls-dyn-rec-idle-timeout"
|
||||
"tls-dyn-rec-idle-timeout",
|
||||
"add-forwarded",
|
||||
"strip-incoming-forwarded",
|
||||
"forwarded-by",
|
||||
"forwarded-for",
|
||||
"response-header-field-buffer",
|
||||
"max-response-header-fields",
|
||||
"request-header-field-buffer",
|
||||
"max-request-header-fields",
|
||||
"header-field-buffer",
|
||||
"max-header-fields",
|
||||
"no-http2-cipher-black-list",
|
||||
"backend-http1-tls",
|
||||
"backend-tls-session-cache-per-worker"
|
||||
]
|
||||
|
||||
LOGVARS = [
|
||||
|
|
|
@ -185,6 +185,66 @@ func TestH1H1HostRewrite(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH1H1BadHost tests that server rejects request including bad
|
||||
// characters in host header field.
|
||||
func TestH1H1BadHost(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H1HBadHost\r\nHost: foo\"bar\r\n\r\n"); err != nil {
|
||||
t.Fatalf("Error io.WriteString() = %v", err)
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error http.ReadResponse() = %v", err)
|
||||
}
|
||||
if got, want := resp.StatusCode, 400; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1BadAuthority tests that server rejects request including
|
||||
// bad characters in authority component of requset URI.
|
||||
func TestH1H1BadAuthority(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
if _, err := io.WriteString(st.conn, "GET http://foo\"bar/ HTTP/1.1\r\nTest-Case: TestH1H1HBadAuthority\r\nHost: foobar\r\n\r\n"); err != nil {
|
||||
t.Fatalf("Error io.WriteString() = %v", err)
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error http.ReadResponse() = %v", err)
|
||||
}
|
||||
if got, want := resp.StatusCode, 400; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1BadScheme tests that server rejects request including
|
||||
// bad characters in scheme component of requset URI.
|
||||
func TestH1H1BadScheme(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
if _, err := io.WriteString(st.conn, "GET http*://example.com/ HTTP/1.1\r\nTest-Case: TestH1H1HBadScheme\r\nHost: example.com\r\n\r\n"); err != nil {
|
||||
t.Fatalf("Error io.WriteString() = %v", err)
|
||||
}
|
||||
resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error http.ReadResponse() = %v", err)
|
||||
}
|
||||
if got, want := resp.StatusCode, 400; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request
|
||||
// without Host header field
|
||||
func TestH1H1HTTP10(t *testing.T) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
@ -44,12 +45,15 @@ func TestH2H1AddXff(t *testing.T) {
|
|||
})
|
||||
defer st.Close()
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddXff",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddXff2 tests that server appends X-Forwarded-For header
|
||||
|
@ -64,7 +68,7 @@ func TestH2H1AddXff2(t *testing.T) {
|
|||
})
|
||||
defer st.Close()
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddXff2",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
|
@ -73,6 +77,9 @@ func TestH2H1AddXff2(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for
|
||||
|
@ -85,8 +92,8 @@ func TestH2H1StripXff(t *testing.T) {
|
|||
})
|
||||
defer st.Close()
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
name: "TestH2H1StripXff1",
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1StripXff",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
},
|
||||
|
@ -94,6 +101,9 @@ func TestH2H1StripXff(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and
|
||||
|
@ -112,7 +122,7 @@ func TestH2H1StripAddXff(t *testing.T) {
|
|||
})
|
||||
defer st.Close()
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1StripAddXff",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
|
@ -121,6 +131,177 @@ func TestH2H1StripAddXff(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedObfuscated tests that server generates
|
||||
// Forwarded header field with obfuscated "by" and "for" parameters.
|
||||
func TestH2H1AddForwardedObfuscated(t *testing.T) {
|
||||
st := newServerTester([]string{"--add-forwarded=by,for,host,proto"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
pattern := fmt.Sprintf(`by=_[^;]+;for=_[^;]+;host="127\.0\.0\.1:%v";proto=http`, serverPort)
|
||||
validFwd := regexp.MustCompile(pattern)
|
||||
got := r.Header.Get("Forwarded")
|
||||
|
||||
if !validFwd.MatchString(got) {
|
||||
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedObfuscated",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedByIP tests that server generates Forwarded header
|
||||
// field with IP address in "by" parameter.
|
||||
func TestH2H1AddForwardedByIP(t *testing.T) {
|
||||
st := newServerTester([]string{"--add-forwarded=by,for", "--forwarded-by=ip"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
pattern := fmt.Sprintf(`by="127\.0\.0\.1:%v";for=_[^;]+`, serverPort)
|
||||
validFwd := regexp.MustCompile(pattern)
|
||||
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
|
||||
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedByIP",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedForIP tests that server generates Forwarded header
|
||||
// field with IP address in "for" parameters.
|
||||
func TestH2H1AddForwardedForIP(t *testing.T) {
|
||||
st := newServerTester([]string{"--add-forwarded=by,for,host,proto", "--forwarded-by=_alpha", "--forwarded-for=ip"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
want := fmt.Sprintf(`by=_alpha;for=127.0.0.1;host="127.0.0.1:%v";proto=http`, serverPort)
|
||||
if got := r.Header.Get("Forwarded"); got != want {
|
||||
t.Errorf("Forwarded = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedForIP",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedMerge tests that server generates Forwarded
|
||||
// header field with IP address in "by" and "for" parameters. The
|
||||
// generated values must be appended to the existing value.
|
||||
func TestH2H1AddForwardedMerge(t *testing.T) {
|
||||
st := newServerTester([]string{"--add-forwarded=proto"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("Forwarded"), `host=foo, proto=http`; got != want {
|
||||
t.Errorf("Forwarded = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedMerge",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedStrip tests that server generates Forwarded
|
||||
// header field with IP address in "by" and "for" parameters. The
|
||||
// generated values must not include the existing value.
|
||||
func TestH2H1AddForwardedStrip(t *testing.T) {
|
||||
st := newServerTester([]string{"--strip-incoming-forwarded", "--add-forwarded=proto"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("Forwarded"), `proto=http`; got != want {
|
||||
t.Errorf("Forwarded = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedStrip",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1StripForwarded tests that server strips incoming Forwarded
|
||||
// header field.
|
||||
func TestH2H1StripForwarded(t *testing.T) {
|
||||
st := newServerTester([]string{"--strip-incoming-forwarded"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, found := r.Header["Forwarded"]; found {
|
||||
t.Errorf("Forwarded = %v; want nothing", got)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1StripForwarded",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AddForwardedStatic tests that server generates Forwarded
|
||||
// header field with the given static obfuscated string for "by"
|
||||
// parameter.
|
||||
func TestH2H1AddForwardedStatic(t *testing.T) {
|
||||
st := newServerTester([]string{"--add-forwarded=by,for", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
pattern := `by=_alpha;for=_[^;]+`
|
||||
validFwd := regexp.MustCompile(pattern)
|
||||
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
|
||||
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1AddForwardedStatic",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1GenerateVia tests that server generates Via header field to and
|
||||
|
@ -329,7 +510,7 @@ func TestH2H1ChunkedRequestBody(t *testing.T) {
|
|||
})
|
||||
defer st.Close()
|
||||
|
||||
_, err := st.http2(requestParam{
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1ChunkedRequestBody",
|
||||
method: "POST",
|
||||
body: []byte("foo"),
|
||||
|
@ -337,6 +518,9 @@ func TestH2H1ChunkedRequestBody(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1MultipleRequestCL tests that server rejects request with
|
||||
|
@ -425,6 +609,46 @@ func TestH2H1InvalidMethod(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH2H1BadAuthority tests that server rejects request including
|
||||
// bad characters in :authority header field.
|
||||
func TestH2H1BadAuthority(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1BadAuthority",
|
||||
authority: `foo\bar`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
|
||||
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1BadScheme tests that server rejects request including
|
||||
// bad characters in :scheme header field.
|
||||
func TestH2H1BadScheme(t *testing.T) {
|
||||
st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H1BadScheme",
|
||||
scheme: "http*",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
|
||||
t.Errorf("res.errCode: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2
|
||||
// request is assembled into 1 when forwarding to HTTP/1 backend link.
|
||||
func TestH2H1AssembleCookies(t *testing.T) {
|
||||
|
@ -1348,6 +1572,208 @@ func TestH2H2TLSXfp(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestH2H2AddXff tests that server generates X-Forwarded-For header
|
||||
// field when forwarding request to backend.
|
||||
func TestH2H2AddXff(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
xff := r.Header.Get("X-Forwarded-For")
|
||||
want := "127.0.0.1"
|
||||
if xff != want {
|
||||
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2AddXff",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2AddXff2 tests that server appends X-Forwarded-For header
|
||||
// field to existing one when forwarding request to backend.
|
||||
func TestH2H2AddXff2(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
xff := r.Header.Get("X-Forwarded-For")
|
||||
want := "host, 127.0.0.1"
|
||||
if xff != want {
|
||||
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2AddXff2",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2StripXff tests that --strip-incoming-x-forwarded-for
|
||||
// option.
|
||||
func TestH2H2StripXff(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if xff, found := r.Header["X-Forwarded-For"]; found {
|
||||
t.Errorf("X-Forwarded-For = %v; want nothing", xff)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2StripXff",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2StripAddXff tests that --strip-incoming-x-forwarded-for and
|
||||
// --add-x-forwarded-for options.
|
||||
func TestH2H2StripAddXff(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-x-forwarded-for", "--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
xff := r.Header.Get("X-Forwarded-For")
|
||||
want := "127.0.0.1"
|
||||
if xff != want {
|
||||
t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2StripAddXff",
|
||||
header: []hpack.HeaderField{
|
||||
pair("x-forwarded-for", "host"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2AddForwarded tests that server generates Forwarded header
|
||||
// field using static obfuscated "by" parameter.
|
||||
func TestH2H2AddForwarded(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--add-forwarded=by,for,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
pattern := fmt.Sprintf(`by=_alpha;for=_[^;]+;host="127\.0\.0\.1:%v";proto=https`, serverPort)
|
||||
validFwd := regexp.MustCompile(pattern)
|
||||
if got := r.Header.Get("Forwarded"); !validFwd.MatchString(got) {
|
||||
t.Errorf("Forwarded = %v; want pattern %v", got, pattern)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2AddForwarded",
|
||||
scheme: "https",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2AddForwardedMerge tests that server generates Forwarded
|
||||
// header field using static obfuscated "by" parameter, and
|
||||
// existing Forwarded header field.
|
||||
func TestH2H2AddForwardedMerge(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--add-forwarded=by,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
want := fmt.Sprintf(`host=foo, by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
|
||||
if got := r.Header.Get("Forwarded"); got != want {
|
||||
t.Errorf("Forwarded = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2AddForwardedMerge",
|
||||
scheme: "https",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2AddForwardedStrip tests that server generates Forwarded
|
||||
// header field using static obfuscated "by" parameter, and
|
||||
// existing Forwarded header field stripped.
|
||||
func TestH2H2AddForwardedStrip(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-forwarded", "--add-forwarded=by,host,proto", "--forwarded-by=_alpha"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
want := fmt.Sprintf(`by=_alpha;host="127.0.0.1:%v";proto=https`, serverPort)
|
||||
if got := r.Header.Get("Forwarded"); got != want {
|
||||
t.Errorf("Forwarded = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2AddForwardedStrip",
|
||||
scheme: "https",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2StripForwarded tests that server strips incoming Forwarded
|
||||
// header field.
|
||||
func TestH2H2StripForwarded(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--http2-bridge", "--strip-incoming-forwarded"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, found := r.Header["Forwarded"]; found {
|
||||
t.Errorf("Forwarded = %v; want nothing", got)
|
||||
}
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.http2(requestParam{
|
||||
name: "TestH2H2StripForwarded",
|
||||
scheme: "https",
|
||||
header: []hpack.HeaderField{
|
||||
pair("forwarded", "host=foo"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.http2() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 200; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestH2H2ReqPhaseReturn tests mruby request phase hook returns
|
||||
// custom response.
|
||||
func TestH2H2ReqPhaseReturn(t *testing.T) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package nghttp2
|
||||
|
||||
import (
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"github.com/tatsuhiro-t/spdy"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
@ -230,6 +230,46 @@ func TestS3H1InvalidMethod(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestS3H1BadHost tests that server rejects request including bad
|
||||
// character in :host header field.
|
||||
func TestS3H1BadHost(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1BadHost",
|
||||
authority: `foo\bar`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 400; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1BadScheme tests that server rejects request including bad
|
||||
// character in :scheme header field.
|
||||
func TestS3H1BadScheme(t *testing.T) {
|
||||
st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Errorf("server should not forward this request")
|
||||
})
|
||||
defer st.Close()
|
||||
|
||||
res, err := st.spdy(requestParam{
|
||||
name: "TestS3H1BadScheme",
|
||||
scheme: `http*`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Error st.spdy() = %v", err)
|
||||
}
|
||||
if got, want := res.status, 400; got != want {
|
||||
t.Errorf("status: %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestS3H1ReqPhaseSetHeader tests mruby request phase hook
|
||||
// modifies request header fields.
|
||||
func TestS3H1ReqPhaseSetHeader(t *testing.T) {
|
||||
|
|
|
@ -15,7 +15,7 @@ THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))
|
|||
USE_CYTHON := 1
|
||||
#USE_CYTHON := 0
|
||||
|
||||
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV], //g')
|
||||
_VERSION := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -r -e 's/(-DEV)?], //g')
|
||||
_VERSION := $(subst ., ,$(_VERSION))
|
||||
VER_MAJOR := $(word 1,$(_VERSION))
|
||||
VER_MINOR := $(word 2,$(_VERSION))
|
||||
|
|
|
@ -752,7 +752,7 @@ typedef enum {
|
|||
/**
|
||||
* Indicates that END_STREAM flag must not be set even if
|
||||
* NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send
|
||||
* trailer header fields with `nghttp2_submit_request()` or
|
||||
* trailer fields with `nghttp2_submit_request()` or
|
||||
* `nghttp2_submit_response()`.
|
||||
*/
|
||||
NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02,
|
||||
|
@ -788,13 +788,13 @@ typedef enum {
|
|||
* :enum:`NGHTTP2_FLAG_END_STREAM` set, and
|
||||
* :enum:`NGHTTP2_DATA_FLAG_EOF` flag is set to |*data_flags|, DATA
|
||||
* frame will have END_STREAM flag set. Usually, this is expected
|
||||
* behaviour and all are fine. One exception is send trailer header
|
||||
* fields. You cannot send trailers after sending frame with
|
||||
* END_STREAM set. To avoid this problem, one can set
|
||||
* behaviour and all are fine. One exception is send trailer fields.
|
||||
* You cannot send trailer fields after sending frame with END_STREAM
|
||||
* set. To avoid this problem, one can set
|
||||
* :enum:`NGHTTP2_DATA_FLAG_NO_END_STREAM` along with
|
||||
* :enum:`NGHTTP2_DATA_FLAG_EOF` to signal the library not to set
|
||||
* END_STREAM in DATA frame. Then application can use
|
||||
* `nghttp2_submit_trailer()` to send trailers.
|
||||
* `nghttp2_submit_trailer()` to send trailer fields.
|
||||
* `nghttp2_submit_trailer()` can be called inside this callback.
|
||||
*
|
||||
* If the application wants to postpone DATA frames (e.g.,
|
||||
|
@ -853,9 +853,9 @@ typedef struct {
|
|||
*
|
||||
* The category of HEADERS, which indicates the role of the frame. In
|
||||
* HTTP/2 spec, request, response, push response and other arbitrary
|
||||
* headers (e.g., trailers) are all called just HEADERS. To give the
|
||||
* application the role of incoming HEADERS frame, we define several
|
||||
* categories.
|
||||
* headers (e.g., trailer fields) are all called just HEADERS. To
|
||||
* give the application the role of incoming HEADERS frame, we define
|
||||
* several categories.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
|
@ -1503,7 +1503,7 @@ typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session,
|
|||
* NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side,
|
||||
* ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST``
|
||||
* containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing
|
||||
* trailer headers and never get PUSH_PROMISE in this callback.
|
||||
* trailer fields and never get PUSH_PROMISE in this callback.
|
||||
*
|
||||
* For the client applications, ``frame->hd.type`` is either
|
||||
* ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of
|
||||
|
@ -1515,7 +1515,7 @@ typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session,
|
|||
* non-final response code and finally client gets exactly one HEADERS
|
||||
* frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS``
|
||||
* containing final response headers (non-1xx status code). The
|
||||
* trailer headers also has ``frame->headers.cat ==
|
||||
* trailer fields also has ``frame->headers.cat ==
|
||||
* NGHTTP2_HCAT_HEADERS`` which does not contain any status code.
|
||||
*
|
||||
* Returning :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close
|
||||
|
@ -3390,7 +3390,7 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
|
|||
/**
|
||||
* @function
|
||||
*
|
||||
* Submits trailer HEADERS against the stream |stream_id|.
|
||||
* Submits trailer fields HEADERS against the stream |stream_id|.
|
||||
*
|
||||
* The |nva| is an array of name/value pair :type:`nghttp2_nv` with
|
||||
* |nvlen| elements. The application is responsible not to include
|
||||
|
@ -3409,26 +3409,26 @@ nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
|
|||
* :type:`nghttp2_on_frame_send_callback` or
|
||||
* :type:`nghttp2_on_frame_not_send_callback` is called.
|
||||
*
|
||||
* For server, trailer must be followed by response HEADERS or
|
||||
* response DATA. The library does not check that response HEADERS
|
||||
* has already sent and if `nghttp2_submit_trailer()` is called before
|
||||
* any response HEADERS submission (usually by
|
||||
* `nghttp2_submit_response()`), the content of |nva| will be sent as
|
||||
* response headers, which will result in error.
|
||||
* For server, trailer fields must follow response HEADERS or response
|
||||
* DATA with END_STREAM flag set. The library does not enforce this
|
||||
* requirement, and applications should do this for themselves. If
|
||||
* `nghttp2_submit_trailer()` is called before any response HEADERS
|
||||
* submission (usually by `nghttp2_submit_response()`), the content of
|
||||
* |nva| will be sent as response headers, which will result in error.
|
||||
*
|
||||
* This function has the same effect with `nghttp2_submit_headers()`,
|
||||
* with flags = :enum:`NGHTTP2_FLAG_END_HEADERS` and both pri_spec and
|
||||
* stream_user_data to NULL.
|
||||
*
|
||||
* To submit trailer after `nghttp2_submit_response()` is called, the
|
||||
* application has to specify :type:`nghttp2_data_provider` to
|
||||
* `nghttp2_submit_response()`. In side
|
||||
* :type:`nghttp2_data_source_read_callback`, when setting
|
||||
* To submit trailer fields after `nghttp2_submit_response()` is
|
||||
* called, the application has to specify
|
||||
* :type:`nghttp2_data_provider` to `nghttp2_submit_response()`. In
|
||||
* side :type:`nghttp2_data_source_read_callback`, when setting
|
||||
* :enum:`NGHTTP2_DATA_FLAG_EOF`, also set
|
||||
* :enum:`NGHTTP2_DATA_FLAG_NO_END_STREAM`. After that, the
|
||||
* application can send trailer using `nghttp2_submit_trailer()`.
|
||||
* `nghttp2_submit_trailer()` can be used inside
|
||||
* :type:`nghttp2_data_source_read_callback`.
|
||||
* application can send trailer fields using
|
||||
* `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used
|
||||
* inside :type:`nghttp2_data_source_read_callback`.
|
||||
*
|
||||
* This function returns 0 if it succeeds and |stream_id| is -1.
|
||||
* Otherwise, this function returns 0 if it succeeds, or one of the
|
||||
|
|
|
@ -284,9 +284,101 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_nv *nv,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Generated by genauthroitychartbl.py */
|
||||
static char VALID_AUTHORITY_CHARS[] = {
|
||||
0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */, 0 /* EOT */,
|
||||
0 /* ENQ */, 0 /* ACK */, 0 /* BEL */, 0 /* BS */, 0 /* HT */,
|
||||
0 /* LF */, 0 /* VT */, 0 /* FF */, 0 /* CR */, 0 /* SO */,
|
||||
0 /* SI */, 0 /* DLE */, 0 /* DC1 */, 0 /* DC2 */, 0 /* DC3 */,
|
||||
0 /* DC4 */, 0 /* NAK */, 0 /* SYN */, 0 /* ETB */, 0 /* CAN */,
|
||||
0 /* EM */, 0 /* SUB */, 0 /* ESC */, 0 /* FS */, 0 /* GS */,
|
||||
0 /* RS */, 0 /* US */, 0 /* SPC */, 1 /* ! */, 0 /* " */,
|
||||
0 /* # */, 1 /* $ */, 1 /* % */, 1 /* & */, 1 /* ' */,
|
||||
1 /* ( */, 1 /* ) */, 1 /* * */, 1 /* + */, 1 /* , */,
|
||||
1 /* - */, 1 /* . */, 0 /* / */, 1 /* 0 */, 1 /* 1 */,
|
||||
1 /* 2 */, 1 /* 3 */, 1 /* 4 */, 1 /* 5 */, 1 /* 6 */,
|
||||
1 /* 7 */, 1 /* 8 */, 1 /* 9 */, 1 /* : */, 1 /* ; */,
|
||||
0 /* < */, 1 /* = */, 0 /* > */, 0 /* ? */, 1 /* @ */,
|
||||
1 /* A */, 1 /* B */, 1 /* C */, 1 /* D */, 1 /* E */,
|
||||
1 /* F */, 1 /* G */, 1 /* H */, 1 /* I */, 1 /* J */,
|
||||
1 /* K */, 1 /* L */, 1 /* M */, 1 /* N */, 1 /* O */,
|
||||
1 /* P */, 1 /* Q */, 1 /* R */, 1 /* S */, 1 /* T */,
|
||||
1 /* U */, 1 /* V */, 1 /* W */, 1 /* X */, 1 /* Y */,
|
||||
1 /* Z */, 1 /* [ */, 0 /* \ */, 1 /* ] */, 0 /* ^ */,
|
||||
1 /* _ */, 0 /* ` */, 1 /* a */, 1 /* b */, 1 /* c */,
|
||||
1 /* d */, 1 /* e */, 1 /* f */, 1 /* g */, 1 /* h */,
|
||||
1 /* i */, 1 /* j */, 1 /* k */, 1 /* l */, 1 /* m */,
|
||||
1 /* n */, 1 /* o */, 1 /* p */, 1 /* q */, 1 /* r */,
|
||||
1 /* s */, 1 /* t */, 1 /* u */, 1 /* v */, 1 /* w */,
|
||||
1 /* x */, 1 /* y */, 1 /* z */, 0 /* { */, 0 /* | */,
|
||||
0 /* } */, 1 /* ~ */, 0 /* DEL */, 0 /* 0x80 */, 0 /* 0x81 */,
|
||||
0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */,
|
||||
0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */,
|
||||
0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */,
|
||||
0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */,
|
||||
0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */,
|
||||
0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */,
|
||||
0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */,
|
||||
0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */,
|
||||
0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */,
|
||||
0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */,
|
||||
0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */,
|
||||
0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */,
|
||||
0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */,
|
||||
0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */,
|
||||
0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */,
|
||||
0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */,
|
||||
0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */,
|
||||
0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */,
|
||||
0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */,
|
||||
0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */,
|
||||
0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */,
|
||||
0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */,
|
||||
0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */,
|
||||
0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */,
|
||||
0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */,
|
||||
0 /* 0xff */
|
||||
};
|
||||
|
||||
static int check_authority(const uint8_t *value, size_t len) {
|
||||
const uint8_t *last;
|
||||
for (last = value + len; value != last; ++value) {
|
||||
if (!VALID_AUTHORITY_CHARS[*value]) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int check_scheme(const uint8_t *value, size_t len) {
|
||||
const uint8_t *last;
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(('A' <= *value && *value <= 'Z') || ('a' <= *value && *value <= 'z'))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
last = value + len;
|
||||
++value;
|
||||
|
||||
for (; value != last; ++value) {
|
||||
if (!(('A' <= *value && *value <= 'Z') ||
|
||||
('a' <= *value && *value <= 'z') ||
|
||||
('0' <= *value && *value <= '9') || *value == '+' || *value == '-' ||
|
||||
*value == '.')) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
|
||||
nghttp2_frame *frame, nghttp2_nv *nv, int token,
|
||||
int trailer) {
|
||||
int rv;
|
||||
|
||||
/* We are strict for pseudo header field. One bad character should
|
||||
lead to fail. OTOH, we should be a bit forgiving for regular
|
||||
headers, since existing public internet has so much illegal
|
||||
|
@ -313,7 +405,15 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
|
|||
return NGHTTP2_ERR_IGN_HTTP_HEADER;
|
||||
}
|
||||
|
||||
if (!nghttp2_check_header_value(nv->value, nv->valuelen)) {
|
||||
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);
|
||||
} else {
|
||||
rv = nghttp2_check_header_value(nv->value, nv->valuelen);
|
||||
}
|
||||
|
||||
if (rv == 0) {
|
||||
assert(nv->namelen > 0);
|
||||
if (nv->name[0] == ':') {
|
||||
return NGHTTP2_ERR_HTTP_HEADER;
|
||||
|
|
|
@ -130,7 +130,7 @@ static int session_detect_idle_stream(nghttp2_session *session,
|
|||
int32_t stream_id) {
|
||||
/* Assume that stream object with stream_id does not exist */
|
||||
if (nghttp2_session_is_my_stream_id(session, stream_id)) {
|
||||
if (session->sent_stream_id < stream_id) {
|
||||
if (session->last_sent_stream_id < stream_id) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -1296,7 +1296,9 @@ static int session_allow_incoming_new_stream(nghttp2_session *session) {
|
|||
* This function returns nonzero if session is closing.
|
||||
*/
|
||||
static int session_is_closing(nghttp2_session *session) {
|
||||
return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0;
|
||||
return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0 ||
|
||||
(nghttp2_session_want_read(session) == 0 &&
|
||||
nghttp2_session_want_write(session) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1327,8 +1329,8 @@ static int session_predicate_for_stream_send(nghttp2_session *session,
|
|||
|
||||
int nghttp2_session_check_request_allowed(nghttp2_session *session) {
|
||||
return !session->server && session->next_stream_id <= INT32_MAX &&
|
||||
(session->goaway_flags &
|
||||
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) == 0;
|
||||
(session->goaway_flags & NGHTTP2_GOAWAY_RECV) == 0 &&
|
||||
!session_is_closing(session);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1350,10 +1352,11 @@ static int session_predicate_request_headers_send(nghttp2_session *session,
|
|||
if (item->aux_data.headers.canceled) {
|
||||
return NGHTTP2_ERR_STREAM_CLOSING;
|
||||
}
|
||||
/* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND) or
|
||||
GOAWAY was received from peer, new request is not allowed. */
|
||||
if (session->goaway_flags &
|
||||
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) {
|
||||
/* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND),
|
||||
GOAWAY was received from peer, or session is about to close, new
|
||||
request is not allowed. */
|
||||
if ((session->goaway_flags & NGHTTP2_GOAWAY_RECV) ||
|
||||
session_is_closing(session)) {
|
||||
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
|
||||
}
|
||||
return 0;
|
||||
|
@ -1912,8 +1915,8 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
nghttp2_bufs_len(&session->aob.framebufs)));
|
||||
|
||||
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
||||
assert(session->sent_stream_id < frame->hd.stream_id);
|
||||
session->sent_stream_id = frame->hd.stream_id;
|
||||
assert(session->last_sent_stream_id < frame->hd.stream_id);
|
||||
session->last_sent_stream_id = frame->hd.stream_id;
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -1943,6 +1946,13 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
|
||||
assert(session->obq_flood_counter_ > 0);
|
||||
--session->obq_flood_counter_;
|
||||
/* When session is about to close, don't send SETTINGS ACK.
|
||||
We are required to send SETTINGS without ACK though; for
|
||||
example, we have to send SETTINGS as a part of connection
|
||||
preface. */
|
||||
if (session_is_closing(session)) {
|
||||
return NGHTTP2_ERR_SESSION_CLOSING;
|
||||
}
|
||||
}
|
||||
|
||||
rv = nghttp2_frame_pack_settings(&session->aob.framebufs,
|
||||
|
@ -1985,9 +1995,9 @@ static int session_prep_frame(nghttp2_session *session,
|
|||
return rv;
|
||||
}
|
||||
|
||||
assert(session->sent_stream_id + 2 <=
|
||||
assert(session->last_sent_stream_id + 2 <=
|
||||
frame->push_promise.promised_stream_id);
|
||||
session->sent_stream_id = frame->push_promise.promised_stream_id;
|
||||
session->last_sent_stream_id = frame->push_promise.promised_stream_id;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -3870,8 +3880,8 @@ static int update_remote_initial_window_size_func(nghttp2_map_entry *entry,
|
|||
rv = nghttp2_stream_update_remote_initial_window_size(
|
||||
stream, arg->new_window_size, arg->old_window_size);
|
||||
if (rv != 0) {
|
||||
return nghttp2_session_terminate_session(arg->session,
|
||||
NGHTTP2_FLOW_CONTROL_ERROR);
|
||||
return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
|
||||
NGHTTP2_FLOW_CONTROL_ERROR);
|
||||
}
|
||||
|
||||
/* If window size gets positive, push deferred DATA frame to
|
||||
|
@ -3922,8 +3932,8 @@ static int update_local_initial_window_size_func(nghttp2_map_entry *entry,
|
|||
rv = nghttp2_stream_update_local_initial_window_size(
|
||||
stream, arg->new_window_size, arg->old_window_size);
|
||||
if (rv != 0) {
|
||||
return nghttp2_session_terminate_session(arg->session,
|
||||
NGHTTP2_FLOW_CONTROL_ERROR);
|
||||
return nghttp2_session_add_rst_stream(arg->session, stream->stream_id,
|
||||
NGHTTP2_FLOW_CONTROL_ERROR);
|
||||
}
|
||||
if (!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) &&
|
||||
stream->window_update_queued == 0 &&
|
||||
|
@ -6726,6 +6736,7 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
|
|||
session->last_proc_stream_id = 1;
|
||||
} else {
|
||||
nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
|
||||
session->last_sent_stream_id = 1;
|
||||
session->next_stream_id += 2;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -250,7 +250,7 @@ struct nghttp2_session {
|
|||
/* The last stream ID this session initiated. For client session,
|
||||
this is the last stream ID it has sent. For server session, it
|
||||
is the last promised stream ID sent in PUSH_PROMISE. */
|
||||
int32_t sent_stream_id;
|
||||
int32_t last_sent_stream_id;
|
||||
/* The largest stream ID received so far */
|
||||
int32_t last_recv_stream_id;
|
||||
/* The largest stream ID which has been processed in some way. This
|
||||
|
|
|
@ -55,10 +55,10 @@
|
|||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 3
|
||||
#serial 4
|
||||
|
||||
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
|
||||
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
|
||||
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
|
||||
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
|
||||
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
|
||||
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
|
||||
|
@ -67,7 +67,7 @@ AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
|
|||
[AS_VAR_SET(CACHEVAR,[yes])],
|
||||
[AS_VAR_SET(CACHEVAR,[no])])
|
||||
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
|
||||
AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
|
||||
AS_VAR_IF(CACHEVAR,yes,
|
||||
[m4_default([$2], :)],
|
||||
[m4_default([$3], :)])
|
||||
AS_VAR_POPDEF([CACHEVAR])dnl
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
git submodule update --init
|
||||
./configure --with-mruby --with-neverbleed --enable-asio-lib
|
||||
make -j3 distcheck DISTCHECK_CONFIGURE_FLAGS="--with-mruby --with-neverbleed --enable-asio-lib --enable-werror"
|
|
@ -101,12 +101,26 @@ template <typename Array> void append_nv(Stream *stream, const Array &nva) {
|
|||
} // namespace
|
||||
|
||||
Config::Config()
|
||||
: mime_types_file("/etc/mime.types"), stream_read_timeout(1_min),
|
||||
stream_write_timeout(1_min), data_ptr(nullptr), padding(0), num_worker(1),
|
||||
max_concurrent_streams(100), header_table_size(-1), port(0),
|
||||
verbose(false), daemon(false), verify_client(false), no_tls(false),
|
||||
error_gzip(false), early_response(false), hexdump(false),
|
||||
echo_upload(false), no_content_length(false) {}
|
||||
: mime_types_file("/etc/mime.types"),
|
||||
stream_read_timeout(1_min),
|
||||
stream_write_timeout(1_min),
|
||||
data_ptr(nullptr),
|
||||
padding(0),
|
||||
num_worker(1),
|
||||
max_concurrent_streams(100),
|
||||
header_table_size(-1),
|
||||
window_bits(-1),
|
||||
connection_window_bits(-1),
|
||||
port(0),
|
||||
verbose(false),
|
||||
daemon(false),
|
||||
verify_client(false),
|
||||
no_tls(false),
|
||||
error_gzip(false),
|
||||
early_response(false),
|
||||
hexdump(false),
|
||||
echo_upload(false),
|
||||
no_content_length(false) {}
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
|
@ -225,8 +239,13 @@ class Sessions {
|
|||
public:
|
||||
Sessions(HttpServer *sv, struct ev_loop *loop, const Config *config,
|
||||
SSL_CTX *ssl_ctx)
|
||||
: sv_(sv), loop_(loop), config_(config), ssl_ctx_(ssl_ctx),
|
||||
callbacks_(nullptr), next_session_id_(1), tstamp_cached_(ev_now(loop)),
|
||||
: sv_(sv),
|
||||
loop_(loop),
|
||||
config_(config),
|
||||
ssl_ctx_(ssl_ctx),
|
||||
callbacks_(nullptr),
|
||||
next_session_id_(1),
|
||||
tstamp_cached_(ev_now(loop)),
|
||||
cached_date_(util::http_date(tstamp_cached_)) {
|
||||
nghttp2_session_callbacks_new(&callbacks_);
|
||||
|
||||
|
@ -424,8 +443,12 @@ void release_fd_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
} // namespace
|
||||
|
||||
Stream::Stream(Http2Handler *handler, int32_t stream_id)
|
||||
: handler(handler), file_ent(nullptr), body_length(0), body_offset(0),
|
||||
stream_id(stream_id), echo_upload(false) {
|
||||
: handler(handler),
|
||||
file_ent(nullptr),
|
||||
body_length(0),
|
||||
body_offset(0),
|
||||
stream_id(stream_id),
|
||||
echo_upload(false) {
|
||||
auto config = handler->get_config();
|
||||
ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
|
||||
ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
|
||||
|
@ -496,8 +519,13 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
|
||||
Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl,
|
||||
int64_t session_id)
|
||||
: session_id_(session_id), session_(nullptr), sessions_(sessions),
|
||||
ssl_(ssl), data_pending_(nullptr), data_pendinglen_(0), fd_(fd) {
|
||||
: session_id_(session_id),
|
||||
session_(nullptr),
|
||||
sessions_(sessions),
|
||||
ssl_(ssl),
|
||||
data_pending_(nullptr),
|
||||
data_pendinglen_(0),
|
||||
fd_(fd) {
|
||||
ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.);
|
||||
ev_io_init(&wev_, writecb, fd, EV_WRITE);
|
||||
ev_io_init(&rev_, readcb, fd, EV_READ);
|
||||
|
@ -819,11 +847,28 @@ int Http2Handler::connection_made() {
|
|||
entry[niv].value = config->header_table_size;
|
||||
++niv;
|
||||
}
|
||||
|
||||
if (config->window_bits != -1) {
|
||||
entry[niv].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
entry[niv].value = (1 << config->window_bits) - 1;
|
||||
++niv;
|
||||
}
|
||||
|
||||
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
|
||||
if (r != 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (config->connection_window_bits != -1) {
|
||||
r = nghttp2_submit_window_update(
|
||||
session_, NGHTTP2_FLAG_NONE, 0,
|
||||
(1 << config->connection_window_bits) - 1 -
|
||||
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE);
|
||||
if (r != 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
ev_timer_start(sessions_->get_loop(), &settings_timerev_);
|
||||
|
||||
if (ssl_ && !nghttp2::ssl::check_http2_requirement(ssl_)) {
|
||||
|
@ -864,6 +909,21 @@ int Http2Handler::verify_npn_result() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string make_trailer_header_value(const Headers &trailer) {
|
||||
if (trailer.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
auto trailer_names = trailer[0].name;
|
||||
for (size_t i = 1; i < trailer.size(); ++i) {
|
||||
trailer_names += ", ";
|
||||
trailer_names += trailer[i].name;
|
||||
}
|
||||
return trailer_names;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Http2Handler::submit_file_response(const std::string &status,
|
||||
Stream *stream, time_t last_modified,
|
||||
off_t file_length,
|
||||
|
@ -888,14 +948,8 @@ int Http2Handler::submit_file_response(const std::string &status,
|
|||
if (content_type) {
|
||||
nva[nvlen++] = http2::make_nv_ls("content-type", *content_type);
|
||||
}
|
||||
auto &trailer = get_config()->trailer;
|
||||
std::string trailer_names;
|
||||
if (!trailer.empty()) {
|
||||
trailer_names = trailer[0].name;
|
||||
for (size_t i = 1; i < trailer.size(); ++i) {
|
||||
trailer_names += ", ";
|
||||
trailer_names += trailer[i].name;
|
||||
}
|
||||
auto trailer_names = make_trailer_header_value(get_config()->trailer);
|
||||
if (!trailer_names.empty()) {
|
||||
nva[nvlen++] = http2::make_nv_ls("trailer", trailer_names);
|
||||
}
|
||||
return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
|
||||
|
@ -906,10 +960,19 @@ int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
|
|||
const Headers &headers,
|
||||
nghttp2_data_provider *data_prd) {
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
nva.reserve(3 + headers.size());
|
||||
nva.reserve(4 + headers.size());
|
||||
nva.push_back(http2::make_nv_ls(":status", status));
|
||||
nva.push_back(http2::make_nv_ll("server", NGHTTPD_SERVER));
|
||||
nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date()));
|
||||
|
||||
std::string trailer_names;
|
||||
if (data_prd) {
|
||||
trailer_names = make_trailer_header_value(get_config()->trailer);
|
||||
if (!trailer_names.empty()) {
|
||||
nva.push_back(http2::make_nv_ls("trailer", trailer_names));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &nv : headers) {
|
||||
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
|
||||
}
|
||||
|
@ -920,11 +983,21 @@ int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
|
|||
|
||||
int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
|
||||
nghttp2_data_provider *data_prd) {
|
||||
auto nva =
|
||||
make_array(http2::make_nv_ls(":status", status),
|
||||
http2::make_nv_ll("server", NGHTTPD_SERVER),
|
||||
http2::make_nv_ls("date", sessions_->get_cached_date()));
|
||||
return nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
|
||||
auto nva = make_array(http2::make_nv_ls(":status", status),
|
||||
http2::make_nv_ll("server", NGHTTPD_SERVER),
|
||||
http2::make_nv_ls("date", sessions_->get_cached_date()),
|
||||
http2::make_nv_ll("", ""));
|
||||
size_t nvlen = 3;
|
||||
|
||||
std::string trailer_names;
|
||||
if (data_prd) {
|
||||
trailer_names = make_trailer_header_value(get_config()->trailer);
|
||||
if (!trailer_names.empty()) {
|
||||
nva[nvlen++] = http2::make_nv_ls("trailer", trailer_names);
|
||||
}
|
||||
}
|
||||
|
||||
return nghttp2_submit_response(session_, stream_id, nva.data(), nvlen,
|
||||
data_prd);
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,8 @@ struct Config {
|
|||
size_t num_worker;
|
||||
size_t max_concurrent_streams;
|
||||
ssize_t header_table_size;
|
||||
int window_bits;
|
||||
int connection_window_bits;
|
||||
uint16_t port;
|
||||
bool verbose;
|
||||
bool daemon;
|
||||
|
@ -87,9 +89,16 @@ struct FileEntry {
|
|||
FileEntry(std::string path, int64_t length, int64_t mtime, int fd,
|
||||
const std::string *content_type, ev_tstamp last_valid,
|
||||
bool stale = false)
|
||||
: path(std::move(path)), length(length), mtime(mtime),
|
||||
last_valid(last_valid), content_type(content_type), dlnext(nullptr),
|
||||
dlprev(nullptr), fd(fd), usecount(1), stale(stale) {}
|
||||
: path(std::move(path)),
|
||||
length(length),
|
||||
mtime(mtime),
|
||||
last_valid(last_valid),
|
||||
content_type(content_type),
|
||||
dlnext(nullptr),
|
||||
dlprev(nullptr),
|
||||
fd(fd),
|
||||
usecount(1),
|
||||
stale(stale) {}
|
||||
std::string path;
|
||||
std::multimap<std::string, std::unique_ptr<FileEntry>>::iterator it;
|
||||
int64_t length;
|
||||
|
|
|
@ -27,9 +27,9 @@ check_PROGRAMS =
|
|||
TESTS =
|
||||
|
||||
AM_CFLAGS = $(WARNCFLAGS)
|
||||
AM_CXXFLAGS = $(WARNCXXFLAGS) $(CXX1XCXXFLAGS)
|
||||
AM_CPPFLAGS = \
|
||||
-DPKGDATADIR='"$(pkgdatadir)"' \
|
||||
-Wall \
|
||||
-I$(top_srcdir)/lib/includes \
|
||||
-I$(top_builddir)/lib/includes \
|
||||
-I$(top_srcdir)/lib \
|
||||
|
@ -169,12 +169,15 @@ 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_http_test.cc shrpx_http_test.h \
|
||||
http2_test.cc http2_test.h \
|
||||
util_test.cc util_test.h \
|
||||
nghttp2_gzip_test.c nghttp2_gzip_test.h \
|
||||
nghttp2_gzip.c nghttp2_gzip.h \
|
||||
buffer_test.cc buffer_test.h \
|
||||
memchunk_test.cc memchunk_test.h
|
||||
memchunk_test.cc memchunk_test.h \
|
||||
template_test.cc template_test.h \
|
||||
base64_test.cc base64_test.h
|
||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
||||
|
|
|
@ -39,15 +39,33 @@ using boost::asio::ip::tcp;
|
|||
|
||||
session::session(boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service)
|
||||
: impl_(std::make_shared<session_tcp_impl>(io_service, host, service)) {
|
||||
: impl_(std::make_shared<session_tcp_impl>(
|
||||
io_service, host, service, boost::posix_time::seconds(60))) {
|
||||
impl_->start_resolve(host, service);
|
||||
}
|
||||
|
||||
session::session(boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout)
|
||||
: impl_(std::make_shared<session_tcp_impl>(io_service, host, service,
|
||||
connect_timeout)) {
|
||||
impl_->start_resolve(host, service);
|
||||
}
|
||||
|
||||
session::session(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
||||
const std::string &service)
|
||||
: impl_(std::make_shared<session_tls_impl>(
|
||||
io_service, tls_ctx, host, service, boost::posix_time::seconds(60))) {
|
||||
impl_->start_resolve(host, service);
|
||||
}
|
||||
|
||||
session::session(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout)
|
||||
: impl_(std::make_shared<session_tls_impl>(io_service, tls_ctx, host,
|
||||
service)) {
|
||||
service, connect_timeout)) {
|
||||
impl_->start_resolve(host, service);
|
||||
}
|
||||
|
||||
|
@ -97,10 +115,6 @@ const request *session::submit(boost::system::error_code &ec,
|
|||
return impl_->submit(ec, method, uri, std::move(cb), std::move(h));
|
||||
}
|
||||
|
||||
void session::connect_timeout(const boost::posix_time::time_duration &t) {
|
||||
impl_->connect_timeout(t);
|
||||
}
|
||||
|
||||
void session::read_timeout(const boost::posix_time::time_duration &t) {
|
||||
impl_->read_timeout(t);
|
||||
}
|
||||
|
|
|
@ -38,12 +38,21 @@ namespace nghttp2 {
|
|||
namespace asio_http2 {
|
||||
namespace client {
|
||||
|
||||
session_impl::session_impl(boost::asio::io_service &io_service)
|
||||
: wblen_(0), io_service_(io_service), resolver_(io_service),
|
||||
deadline_(io_service), connect_timeout_(boost::posix_time::seconds(60)),
|
||||
read_timeout_(boost::posix_time::seconds(60)), session_(nullptr),
|
||||
data_pending_(nullptr), data_pendinglen_(0), writing_(false),
|
||||
inside_callback_(false), stopped_(false) {}
|
||||
session_impl::session_impl(
|
||||
boost::asio::io_service &io_service,
|
||||
const boost::posix_time::time_duration &connect_timeout)
|
||||
: wblen_(0),
|
||||
io_service_(io_service),
|
||||
resolver_(io_service),
|
||||
deadline_(io_service),
|
||||
connect_timeout_(connect_timeout),
|
||||
read_timeout_(boost::posix_time::seconds(60)),
|
||||
session_(nullptr),
|
||||
data_pending_(nullptr),
|
||||
data_pendinglen_(0),
|
||||
writing_(false),
|
||||
inside_callback_(false),
|
||||
stopped_(false) {}
|
||||
|
||||
session_impl::~session_impl() {
|
||||
// finish up all active stream
|
||||
|
@ -698,9 +707,7 @@ void session_impl::stop() {
|
|||
stopped_ = true;
|
||||
}
|
||||
|
||||
void session_impl::connect_timeout(const boost::posix_time::time_duration &t) {
|
||||
connect_timeout_ = t;
|
||||
}
|
||||
bool session_impl::stopped() const { return stopped_; }
|
||||
|
||||
void session_impl::read_timeout(const boost::posix_time::time_duration &t) {
|
||||
read_timeout_ = t;
|
||||
|
|
|
@ -43,7 +43,8 @@ using boost::asio::ip::tcp;
|
|||
|
||||
class session_impl : public std::enable_shared_from_this<session_impl> {
|
||||
public:
|
||||
session_impl(boost::asio::io_service &io_service);
|
||||
session_impl(boost::asio::io_service &io_service,
|
||||
const boost::posix_time::time_duration &connect_timeout);
|
||||
virtual ~session_impl();
|
||||
|
||||
void start_resolve(const std::string &host, const std::string &service);
|
||||
|
@ -91,10 +92,10 @@ public:
|
|||
void do_read();
|
||||
void do_write();
|
||||
|
||||
void connect_timeout(const boost::posix_time::time_duration &t);
|
||||
void read_timeout(const boost::posix_time::time_duration &t);
|
||||
|
||||
void stop();
|
||||
bool stopped() const;
|
||||
|
||||
protected:
|
||||
boost::array<uint8_t, 8_k> rb_;
|
||||
|
|
|
@ -28,10 +28,11 @@ namespace nghttp2 {
|
|||
namespace asio_http2 {
|
||||
namespace client {
|
||||
|
||||
session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service,
|
||||
const std::string &host,
|
||||
const std::string &service)
|
||||
: session_impl(io_service), socket_(io_service) {}
|
||||
session_tcp_impl::session_tcp_impl(
|
||||
boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout)
|
||||
: session_impl(io_service, connect_timeout), socket_(io_service) {}
|
||||
|
||||
session_tcp_impl::~session_tcp_impl() {}
|
||||
|
||||
|
@ -39,6 +40,10 @@ void session_tcp_impl::start_connect(tcp::resolver::iterator endpoint_it) {
|
|||
boost::asio::async_connect(socket_, endpoint_it,
|
||||
[this](const boost::system::error_code &ec,
|
||||
tcp::resolver::iterator endpoint_it) {
|
||||
if (stopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
not_connected(ec);
|
||||
return;
|
||||
|
|
|
@ -38,7 +38,8 @@ using boost::asio::ip::tcp;
|
|||
class session_tcp_impl : public session_impl {
|
||||
public:
|
||||
session_tcp_impl(boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service);
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout);
|
||||
virtual ~session_tcp_impl();
|
||||
|
||||
virtual void start_connect(tcp::resolver::iterator endpoint_it);
|
||||
|
|
|
@ -29,11 +29,11 @@ namespace nghttp2 {
|
|||
namespace asio_http2 {
|
||||
namespace client {
|
||||
|
||||
session_tls_impl::session_tls_impl(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_ctx,
|
||||
const std::string &host,
|
||||
const std::string &service)
|
||||
: session_impl(io_service), socket_(io_service, tls_ctx) {
|
||||
session_tls_impl::session_tls_impl(
|
||||
boost::asio::io_service &io_service, boost::asio::ssl::context &tls_ctx,
|
||||
const std::string &host, const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout)
|
||||
: session_impl(io_service, connect_timeout), socket_(io_service, tls_ctx) {
|
||||
// this callback setting is no effect is
|
||||
// ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is
|
||||
// not used, which is what we want.
|
||||
|
@ -46,6 +46,10 @@ void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
|
|||
boost::asio::async_connect(
|
||||
socket(), endpoint_it, [this](const boost::system::error_code &ec,
|
||||
tcp::resolver::iterator endpoint_it) {
|
||||
if (stopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
not_connected(ec);
|
||||
return;
|
||||
|
@ -54,6 +58,10 @@ void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) {
|
|||
socket_.async_handshake(
|
||||
boost::asio::ssl::stream_base::client,
|
||||
[this, endpoint_it](const boost::system::error_code &ec) {
|
||||
if (stopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
not_connected(ec);
|
||||
return;
|
||||
|
|
|
@ -41,7 +41,8 @@ class session_tls_impl : public session_impl {
|
|||
public:
|
||||
session_tls_impl(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_ctx, const std::string &host,
|
||||
const std::string &service);
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout);
|
||||
virtual ~session_tls_impl();
|
||||
|
||||
virtual void start_connect(tcp::resolver::iterator endpoint_it);
|
||||
|
|
|
@ -175,7 +175,12 @@ void server::start_accept(tcp::acceptor &acceptor, serve_mux &mux) {
|
|||
});
|
||||
}
|
||||
|
||||
void server::stop() { io_service_pool_.stop(); }
|
||||
void server::stop() {
|
||||
io_service_pool_.stop();
|
||||
for (auto &acceptor : acceptors_) {
|
||||
acceptor.close();
|
||||
}
|
||||
}
|
||||
|
||||
void server::join() { io_service_pool_.join(); }
|
||||
|
||||
|
|
|
@ -69,10 +69,13 @@ public:
|
|||
const boost::posix_time::time_duration &tls_handshake_timeout,
|
||||
const boost::posix_time::time_duration &read_timeout,
|
||||
SocketArgs &&... args)
|
||||
: socket_(std::forward<SocketArgs>(args)...), mux_(mux),
|
||||
: socket_(std::forward<SocketArgs>(args)...),
|
||||
mux_(mux),
|
||||
deadline_(socket_.get_io_service()),
|
||||
tls_handshake_timeout_(tls_handshake_timeout),
|
||||
read_timeout_(read_timeout), writing_(false), stopped_(false) {}
|
||||
read_timeout_(read_timeout),
|
||||
writing_(false),
|
||||
stopped_(false) {}
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void start() {
|
||||
|
|
|
@ -230,8 +230,14 @@ int on_frame_not_send_callback(nghttp2_session *session,
|
|||
http2_handler::http2_handler(boost::asio::io_service &io_service,
|
||||
boost::asio::ip::tcp::endpoint ep,
|
||||
connection_write writefun, serve_mux &mux)
|
||||
: writefun_(writefun), mux_(mux), io_service_(io_service), remote_ep_(ep),
|
||||
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false),
|
||||
: writefun_(writefun),
|
||||
mux_(mux),
|
||||
io_service_(io_service),
|
||||
remote_ep_(ep),
|
||||
session_(nullptr),
|
||||
buf_(nullptr),
|
||||
buflen_(0),
|
||||
inside_callback_(false),
|
||||
tstamp_cached_(time(nullptr)),
|
||||
formatted_date_(util::http_date(tstamp_cached_)) {}
|
||||
|
||||
|
|
|
@ -38,7 +38,8 @@ namespace asio_http2 {
|
|||
namespace server {
|
||||
|
||||
http2_impl::http2_impl()
|
||||
: num_threads_(1), backlog_(-1),
|
||||
: num_threads_(1),
|
||||
backlog_(-1),
|
||||
tls_handshake_timeout_(boost::posix_time::seconds(60)),
|
||||
read_timeout_(boost::posix_time::seconds(60)) {}
|
||||
|
||||
|
|
|
@ -36,8 +36,11 @@ namespace asio_http2 {
|
|||
namespace server {
|
||||
|
||||
response_impl::response_impl()
|
||||
: strm_(nullptr), generator_cb_(deferred_generator()), status_code_(200),
|
||||
state_(response_state::INITIAL), pushed_(false),
|
||||
: strm_(nullptr),
|
||||
generator_cb_(deferred_generator()),
|
||||
status_code_(200),
|
||||
state_(response_state::INITIAL),
|
||||
pushed_(false),
|
||||
push_promise_sent_(false) {}
|
||||
|
||||
unsigned int response_impl::status_code() const { return status_code_; }
|
||||
|
|
141
src/base64.h
141
src/base64.h
|
@ -33,9 +33,8 @@ namespace nghttp2 {
|
|||
|
||||
namespace base64 {
|
||||
|
||||
template <typename InputIterator>
|
||||
std::string encode(InputIterator first, InputIterator last) {
|
||||
static const char CHAR_TABLE[] = {
|
||||
template <typename InputIt> std::string encode(InputIt first, InputIt last) {
|
||||
static constexpr char CHAR_TABLE[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
|
||||
|
@ -48,39 +47,38 @@ std::string encode(InputIterator first, InputIterator last) {
|
|||
return res;
|
||||
}
|
||||
size_t r = len % 3;
|
||||
InputIterator j = last - r;
|
||||
char temp[4];
|
||||
res.resize((len + 2) / 3 * 4);
|
||||
auto j = last - r;
|
||||
auto p = std::begin(res);
|
||||
while (first != j) {
|
||||
int n = static_cast<unsigned char>(*first++) << 16;
|
||||
n += static_cast<unsigned char>(*first++) << 8;
|
||||
n += static_cast<unsigned char>(*first++);
|
||||
temp[0] = CHAR_TABLE[n >> 18];
|
||||
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
|
||||
temp[3] = CHAR_TABLE[n & 0x3fu];
|
||||
res.append(temp, sizeof(temp));
|
||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||
n += static_cast<uint8_t>(*first++) << 8;
|
||||
n += static_cast<uint8_t>(*first++);
|
||||
*p++ = CHAR_TABLE[n >> 18];
|
||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
|
||||
*p++ = CHAR_TABLE[n & 0x3fu];
|
||||
}
|
||||
|
||||
if (r == 2) {
|
||||
int n = static_cast<unsigned char>(*first++) << 16;
|
||||
n += static_cast<unsigned char>(*first++) << 8;
|
||||
temp[0] = CHAR_TABLE[n >> 18];
|
||||
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
|
||||
temp[3] = '=';
|
||||
res.append(temp, sizeof(temp));
|
||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||
n += static_cast<uint8_t>(*first++) << 8;
|
||||
*p++ = CHAR_TABLE[n >> 18];
|
||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
*p++ = CHAR_TABLE[(n >> 6) & 0x3fu];
|
||||
*p++ = '=';
|
||||
} else if (r == 1) {
|
||||
int n = static_cast<unsigned char>(*first++) << 16;
|
||||
temp[0] = CHAR_TABLE[n >> 18];
|
||||
temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
temp[2] = '=';
|
||||
temp[3] = '=';
|
||||
res.append(temp, sizeof(temp));
|
||||
uint32_t n = static_cast<uint8_t>(*first++) << 16;
|
||||
*p++ = CHAR_TABLE[n >> 18];
|
||||
*p++ = CHAR_TABLE[(n >> 12) & 0x3fu];
|
||||
*p++ = '=';
|
||||
*p++ = '=';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
InputIterator getNext(InputIterator first, InputIterator last, const int *tbl) {
|
||||
template <typename InputIt>
|
||||
InputIt next_decode_input(InputIt first, InputIt last, const int *tbl) {
|
||||
for (; first != last; ++first) {
|
||||
if (tbl[static_cast<size_t>(*first)] != -1 || *first == '=') {
|
||||
break;
|
||||
|
@ -89,9 +87,8 @@ InputIterator getNext(InputIterator first, InputIterator last, const int *tbl) {
|
|||
return first;
|
||||
}
|
||||
|
||||
template <typename InputIterator>
|
||||
std::string decode(InputIterator first, InputIterator last) {
|
||||
static const int INDEX_TABLE[] = {
|
||||
template <typename InputIt> std::string decode(InputIt first, InputIt last) {
|
||||
static constexpr int INDEX_TABLE[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
|
||||
|
@ -107,59 +104,47 @@ std::string decode(InputIterator first, InputIterator last) {
|
|||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1};
|
||||
std::string res;
|
||||
InputIterator k[4];
|
||||
int eq = 0;
|
||||
for (; first != last;) {
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
k[i - 1] = getNext(first, last, INDEX_TABLE);
|
||||
if (k[i - 1] == last) {
|
||||
// If i == 1, input may look like this: "TWFu\n" (i.e.,
|
||||
// garbage at the end)
|
||||
if (i != 1) {
|
||||
res.clear();
|
||||
}
|
||||
return res;
|
||||
} else if (*k[i - 1] == '=' && eq == 0) {
|
||||
eq = i;
|
||||
}
|
||||
first = k[i - 1] + 1;
|
||||
}
|
||||
if (eq) {
|
||||
break;
|
||||
}
|
||||
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
|
||||
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
|
||||
(INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6) +
|
||||
INDEX_TABLE[static_cast<unsigned char>(*k[3])];
|
||||
res += n >> 16;
|
||||
res += n >> 8 & 0xffu;
|
||||
res += n & 0xffu;
|
||||
auto len = last - first;
|
||||
if (len % 4 != 0) {
|
||||
return "";
|
||||
}
|
||||
if (eq) {
|
||||
if (eq <= 2) {
|
||||
res.clear();
|
||||
return res;
|
||||
} else {
|
||||
for (int i = eq; i <= 4; ++i) {
|
||||
if (*k[i - 1] != '=') {
|
||||
res.clear();
|
||||
std::string res;
|
||||
res.resize(len / 4 * 3);
|
||||
|
||||
auto p = std::begin(res);
|
||||
for (; first != last;) {
|
||||
uint32_t n = 0;
|
||||
for (int i = 1; i <= 4; ++i, ++first) {
|
||||
auto idx = INDEX_TABLE[static_cast<size_t>(*first)];
|
||||
if (idx == -1) {
|
||||
if (i <= 2) {
|
||||
return "";
|
||||
}
|
||||
if (i == 3) {
|
||||
if (*first == '=' && *(first + 1) == '=' && first + 2 == last) {
|
||||
*p++ = n >> 16;
|
||||
res.resize(p - std::begin(res));
|
||||
return res;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
if (*first == '=' && first + 1 == last) {
|
||||
*p++ = n >> 16;
|
||||
*p++ = n >> 8 & 0xffu;
|
||||
res.resize(p - std::begin(res));
|
||||
return res;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
if (eq == 3) {
|
||||
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
|
||||
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12);
|
||||
res += n >> 16;
|
||||
} else if (eq == 4) {
|
||||
int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
|
||||
(INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
|
||||
(INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6);
|
||||
res += n >> 16;
|
||||
res += n >> 8 & 0xffu;
|
||||
}
|
||||
|
||||
n += idx << (24 - i * 6);
|
||||
}
|
||||
|
||||
*p++ = n >> 16;
|
||||
*p++ = n >> 8 & 0xffu;
|
||||
*p++ = n & 0xffu;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 "base64_test.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
void test_base64_encode(void) {
|
||||
{
|
||||
std::string in = "\xff";
|
||||
auto out = base64::encode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("/w==" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "\xff\xfe";
|
||||
auto out = base64::encode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("//4=" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "\xff\xfe\xfd";
|
||||
auto out = base64::encode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("//79" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "\xff\xfe\xfd\xfc";
|
||||
auto out = base64::encode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("//79/A==" == out);
|
||||
}
|
||||
}
|
||||
|
||||
void test_base64_decode(void) {
|
||||
{
|
||||
std::string in = "/w==";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("\xff" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "//4=";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("\xff\xfe" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "//79";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("\xff\xfe\xfd" == out);
|
||||
}
|
||||
{
|
||||
std::string in = "//79/A==";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("\xff\xfe\xfd\xfc" == out);
|
||||
}
|
||||
{
|
||||
// we check the number of valid input must be multiples of 4
|
||||
std::string in = "//79=";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("" == out);
|
||||
}
|
||||
{
|
||||
// ending invalid character at the boundary of multiples of 4 is
|
||||
// bad
|
||||
std::string in = "bmdodHRw\n";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("" == out);
|
||||
}
|
||||
{
|
||||
// after seeing '=', subsequent input must be also '='.
|
||||
std::string in = "//79/A=A";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("" == out);
|
||||
}
|
||||
{
|
||||
// additional '=' at the end is bad
|
||||
std::string in = "//79/A======";
|
||||
auto out = base64::decode(std::begin(in), std::end(in));
|
||||
CU_ASSERT("" == out);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nghttp2
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 BASE64_TEST_H
|
||||
#define BASE64_TEST_H
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif // HAVE_CONFIG_H
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
void test_base64_encode(void);
|
||||
void test_base64_decode(void);
|
||||
|
||||
} // namespace nghttp2
|
||||
|
||||
#endif // BASE64_TEST_H
|
145
src/h2load.cc
145
src/h2load.cc
|
@ -79,14 +79,31 @@ bool recorded(const std::chrono::steady_clock::time_point &t) {
|
|||
} // namespace
|
||||
|
||||
Config::Config()
|
||||
: data_length(-1), addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
|
||||
max_concurrent_streams(-1), window_bits(30), connection_window_bits(30),
|
||||
rate(0), rate_period(1.0), conn_active_timeout(0.),
|
||||
conn_inactivity_timeout(0.), no_tls_proto(PROTO_HTTP2), data_fd(-1),
|
||||
port(0), default_port(0), verbose(false), timing_script(false) {}
|
||||
: data_length(-1),
|
||||
addrs(nullptr),
|
||||
nreqs(1),
|
||||
nclients(1),
|
||||
nthreads(1),
|
||||
max_concurrent_streams(-1),
|
||||
window_bits(30),
|
||||
connection_window_bits(30),
|
||||
rate(0),
|
||||
rate_period(1.0),
|
||||
conn_active_timeout(0.),
|
||||
conn_inactivity_timeout(0.),
|
||||
no_tls_proto(PROTO_HTTP2),
|
||||
data_fd(-1),
|
||||
port(0),
|
||||
default_port(0),
|
||||
verbose(false),
|
||||
timing_script(false) {}
|
||||
|
||||
Config::~Config() {
|
||||
freeaddrinfo(addrs);
|
||||
if (base_uri_unix) {
|
||||
delete addrs;
|
||||
} else {
|
||||
freeaddrinfo(addrs);
|
||||
}
|
||||
|
||||
if (data_fd != -1) {
|
||||
close(data_fd);
|
||||
|
@ -102,9 +119,18 @@ constexpr size_t MAX_SAMPLES = 1000000;
|
|||
} // namespace
|
||||
|
||||
Stats::Stats(size_t req_todo, size_t nclients)
|
||||
: req_todo(req_todo), req_started(0), req_done(0), req_success(0),
|
||||
req_status_success(0), req_failed(0), req_error(0), req_timedout(0),
|
||||
bytes_total(0), bytes_head(0), bytes_head_decomp(0), bytes_body(0),
|
||||
: req_todo(req_todo),
|
||||
req_started(0),
|
||||
req_done(0),
|
||||
req_success(0),
|
||||
req_status_success(0),
|
||||
req_failed(0),
|
||||
req_error(0),
|
||||
req_timedout(0),
|
||||
bytes_total(0),
|
||||
bytes_head(0),
|
||||
bytes_head_decomp(0),
|
||||
bytes_body(0),
|
||||
status() {}
|
||||
|
||||
Stream::Stream() : req_stat{}, status_success(-1) {}
|
||||
|
@ -286,9 +312,18 @@ void client_request_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
} // namespace
|
||||
|
||||
Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
||||
: cstat{}, worker(worker), ssl(nullptr), next_addr(config.addrs),
|
||||
current_addr(nullptr), reqidx(0), state(CLIENT_IDLE), req_todo(req_todo),
|
||||
req_started(0), req_done(0), id(id), fd(-1),
|
||||
: cstat{},
|
||||
worker(worker),
|
||||
ssl(nullptr),
|
||||
next_addr(config.addrs),
|
||||
current_addr(nullptr),
|
||||
reqidx(0),
|
||||
state(CLIENT_IDLE),
|
||||
req_todo(req_todo),
|
||||
req_started(0),
|
||||
req_done(0),
|
||||
id(id),
|
||||
fd(-1),
|
||||
new_connection_requested(false) {
|
||||
ev_io_init(&wev, writecb, 0, EV_WRITE);
|
||||
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||
|
@ -1090,11 +1125,20 @@ void Client::try_new_connection() { new_connection_requested = true; }
|
|||
|
||||
Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
|
||||
size_t rate, size_t max_samples, Config *config)
|
||||
: stats(req_todo, nclients), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx),
|
||||
config(config), id(id), tls_info_report_done(false),
|
||||
app_info_report_done(false), nconns_made(0), nclients(nclients),
|
||||
nreqs_per_client(req_todo / nclients), nreqs_rem(req_todo % nclients),
|
||||
rate(rate), max_samples(max_samples), next_client_id(0) {
|
||||
: stats(req_todo, nclients),
|
||||
loop(ev_loop_new(0)),
|
||||
ssl_ctx(ssl_ctx),
|
||||
config(config),
|
||||
id(id),
|
||||
tls_info_report_done(false),
|
||||
app_info_report_done(false),
|
||||
nconns_made(0),
|
||||
nclients(nclients),
|
||||
nreqs_per_client(req_todo / nclients),
|
||||
nreqs_rem(req_todo % nclients),
|
||||
rate(rate),
|
||||
max_samples(max_samples),
|
||||
next_client_id(0) {
|
||||
if (!config->is_rate_mode()) {
|
||||
progress_interval = std::max(static_cast<size_t>(1), req_todo / 10);
|
||||
} else {
|
||||
|
@ -1301,6 +1345,18 @@ process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
|
|||
|
||||
namespace {
|
||||
void resolve_host() {
|
||||
if (config.base_uri_unix) {
|
||||
auto res = make_unique<addrinfo>();
|
||||
res->ai_family = config.unix_addr.sun_family;
|
||||
res->ai_socktype = SOCK_STREAM;
|
||||
res->ai_addrlen = sizeof(config.unix_addr);
|
||||
res->ai_addr =
|
||||
static_cast<struct sockaddr *>(static_cast<void *>(&config.unix_addr));
|
||||
|
||||
config.addrs = res.release();
|
||||
return;
|
||||
};
|
||||
|
||||
int rv;
|
||||
addrinfo hints{}, *res;
|
||||
|
||||
|
@ -1357,6 +1413,10 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr char UNIX_PATH_PREFIX[] = "unix:";
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
bool parse_base_uri(std::string base_uri) {
|
||||
http_parser_url u{};
|
||||
|
@ -1636,11 +1696,16 @@ Options:
|
|||
contained in other URIs, if present, are ignored.
|
||||
Definition of a base URI overrides all scheme, host or
|
||||
port values.
|
||||
-B, --base-uri=<URI>
|
||||
-B, --base-uri=(<URI>|unix:<PATH>)
|
||||
Specify URI from which the scheme, host and port will be
|
||||
used for all requests. The base URI overrides all
|
||||
values defined either at the command line or inside
|
||||
input files.
|
||||
input files. If argument starts with "unix:", then the
|
||||
rest of the argument will be treated as UNIX domain
|
||||
socket path. The connection is made through that path
|
||||
instead of TCP. In this case, scheme is inferred from
|
||||
the first URI appeared in the command line or inside
|
||||
input files as usual.
|
||||
--npn-list=<LIST>
|
||||
Comma delimited list of ALPN protocol identifier sorted
|
||||
in the order of preference. That means most desirable
|
||||
|
@ -1819,13 +1884,40 @@ int main(int argc, char **argv) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
case 'B': {
|
||||
config.base_uri = "";
|
||||
config.base_uri_unix = false;
|
||||
|
||||
if (util::istarts_with_l(optarg, UNIX_PATH_PREFIX)) {
|
||||
// UNIX domain socket path
|
||||
sockaddr_un un;
|
||||
|
||||
auto path = optarg + str_size(UNIX_PATH_PREFIX);
|
||||
auto pathlen = strlen(optarg) - str_size(UNIX_PATH_PREFIX);
|
||||
|
||||
if (pathlen == 0 || pathlen + 1 > sizeof(un.sun_path)) {
|
||||
std::cerr << "--base-uri: invalid UNIX domain socket path: " << optarg
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
config.base_uri_unix = true;
|
||||
|
||||
auto &unix_addr = config.unix_addr;
|
||||
std::copy_n(path, pathlen + 1, unix_addr.sun_path);
|
||||
unix_addr.sun_family = AF_UNIX;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parse_base_uri(optarg)) {
|
||||
std::cerr << "invalid base URI: " << optarg << std::endl;
|
||||
std::cerr << "--base-uri: invalid base URI: " << optarg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
config.base_uri = optarg;
|
||||
break;
|
||||
}
|
||||
case 'v':
|
||||
config.verbose = true;
|
||||
break;
|
||||
|
@ -2321,7 +2413,7 @@ int main(int argc, char **argv) {
|
|||
|
||||
std::cout << std::fixed << std::setprecision(2) << R"(
|
||||
finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
|
||||
<< util::utos_with_funit(bps) << R"(B/s
|
||||
<< util::utos_funit(bps) << R"(B/s
|
||||
requests: )" << stats.req_todo << " total, " << stats.req_started
|
||||
<< " started, " << stats.req_done << " done, "
|
||||
<< stats.req_status_success << " succeeded, " << stats.req_failed
|
||||
|
@ -2329,9 +2421,12 @@ requests: )" << stats.req_todo << " total, " << stats.req_started
|
|||
<< stats.req_timedout << R"( timeout
|
||||
status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
|
||||
<< stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
|
||||
traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
|
||||
<< " bytes headers (space savings " << header_space_savings * 100
|
||||
<< "%), " << stats.bytes_body << R"( bytes data
|
||||
traffic: )" << util::utos_funit(stats.bytes_total) << "B (" << stats.bytes_total
|
||||
<< ") total, " << util::utos_funit(stats.bytes_head) << "B ("
|
||||
<< stats.bytes_head << ") headers (space savings "
|
||||
<< header_space_savings * 100 << "%), "
|
||||
<< util::utos_funit(stats.bytes_body) << "B (" << stats.bytes_body
|
||||
<< R"() data
|
||||
min max mean sd +/- sd
|
||||
time for request: )" << std::setw(10) << util::format_duration(ts.request.min)
|
||||
<< " " << std::setw(10) << util::format_duration(ts.request.max)
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#ifdef HAVE_NETDB_H
|
||||
#include <netdb.h>
|
||||
#endif // HAVE_NETDB_H
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
@ -100,6 +101,11 @@ struct Config {
|
|||
bool verbose;
|
||||
bool timing_script;
|
||||
std::string base_uri;
|
||||
// true if UNIX domain socket is used. In this case, base_uri is
|
||||
// not used in usual way.
|
||||
bool base_uri_unix;
|
||||
// used when UNIX domain socket is used (base_uri_unix is true).
|
||||
sockaddr_un unix_addr;
|
||||
// list of supported NPN/ALPN protocol strings in the order of
|
||||
// preference.
|
||||
std::vector<std::string> npn_list;
|
||||
|
|
|
@ -41,7 +41,10 @@ using namespace nghttp2;
|
|||
namespace h2load {
|
||||
|
||||
Http1Session::Http1Session(Client *client)
|
||||
: stream_req_counter_(1), stream_resp_counter_(1), client_(client), htp_(),
|
||||
: stream_req_counter_(1),
|
||||
stream_resp_counter_(1),
|
||||
client_(client),
|
||||
htp_(),
|
||||
complete_(false) {
|
||||
http_parser_init(&htp_, HTTP_RESPONSE);
|
||||
htp_.data = this;
|
||||
|
|
20
src/http2.cc
20
src/http2.cc
|
@ -348,6 +348,7 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
|
|||
switch (kv.token) {
|
||||
case HD_COOKIE:
|
||||
case HD_CONNECTION:
|
||||
case HD_FORWARDED:
|
||||
case HD_HOST:
|
||||
case HD_HTTP2_SETTINGS:
|
||||
case HD_KEEP_ALIVE:
|
||||
|
@ -385,6 +386,7 @@ void build_http1_headers_from_headers(DefaultMemchunks *buf,
|
|||
switch (kv.token) {
|
||||
case HD_CONNECTION:
|
||||
case HD_COOKIE:
|
||||
case HD_FORWARDED:
|
||||
case HD_HOST:
|
||||
case HD_HTTP2_SETTINGS:
|
||||
case HD_KEEP_ALIVE:
|
||||
|
@ -638,6 +640,15 @@ int lookup_token(const uint8_t *name, size_t namelen) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case 9:
|
||||
switch (name[8]) {
|
||||
case 'd':
|
||||
if (util::streq_l("forwarde", name, 8)) {
|
||||
return HD_FORWARDED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
switch (name[9]) {
|
||||
case 'e':
|
||||
|
@ -662,6 +673,15 @@ int lookup_token(const uint8_t *name, size_t namelen) {
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
switch (name[11]) {
|
||||
case 'e':
|
||||
if (util::streq_l("content-typ", name, 11)) {
|
||||
return HD_CONTENT_TYPE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
switch (name[12]) {
|
||||
case 'l':
|
||||
|
|
14
src/http2.h
14
src/http2.h
|
@ -45,7 +45,9 @@ namespace nghttp2 {
|
|||
struct Header {
|
||||
Header(std::string name, std::string value, bool no_index = false,
|
||||
int16_t token = -1)
|
||||
: name(std::move(name)), value(std::move(value)), token(token),
|
||||
: name(std::move(name)),
|
||||
value(std::move(value)),
|
||||
token(token),
|
||||
no_index(no_index) {}
|
||||
|
||||
Header() : token(-1), no_index(false) {}
|
||||
|
@ -151,6 +153,12 @@ nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const std::string &value) {
|
|||
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
nghttp2_nv make_nv_ls_nocopy(const char(&name)[N], const StringRef &value) {
|
||||
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
|
||||
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
|
||||
}
|
||||
|
||||
// Appends headers in |headers| to |nv|. |headers| must be indexed
|
||||
// before this call (its element's token field is assigned). Certain
|
||||
// headers, including disallowed headers in HTTP/2 spec and headers
|
||||
|
@ -218,6 +226,8 @@ int parse_http_status_code(const std::string &src);
|
|||
|
||||
// Header fields to be indexed, except HD_MAXIDX which is convenient
|
||||
// member to get maximum value.
|
||||
//
|
||||
// generated by genheaderfunc.py
|
||||
enum {
|
||||
HD__AUTHORITY,
|
||||
HD__HOST,
|
||||
|
@ -231,9 +241,11 @@ enum {
|
|||
HD_CACHE_CONTROL,
|
||||
HD_CONNECTION,
|
||||
HD_CONTENT_LENGTH,
|
||||
HD_CONTENT_TYPE,
|
||||
HD_COOKIE,
|
||||
HD_DATE,
|
||||
HD_EXPECT,
|
||||
HD_FORWARDED,
|
||||
HD_HOST,
|
||||
HD_HTTP2_SETTINGS,
|
||||
HD_IF_MODIFIED_SINCE,
|
||||
|
|
|
@ -123,15 +123,33 @@ class session_impl;
|
|||
class session {
|
||||
public:
|
||||
// Starts HTTP/2 session by connecting to |host| and |service|
|
||||
// (e.g., "80") using clear text TCP connection.
|
||||
// (e.g., "80") using clear text TCP connection with connect timeout
|
||||
// 60 seconds.
|
||||
session(boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service);
|
||||
|
||||
// Starts HTTP/2 session by connecting to |host| and |service|
|
||||
// (e.g., "443") using encrypted SSL/TLS connection.
|
||||
// (e.g., "80") using clear text TCP connection with given connect
|
||||
// timeout.
|
||||
session(boost::asio::io_service &io_service, const std::string &host,
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout);
|
||||
|
||||
// Starts HTTP/2 session by connecting to |host| and |service|
|
||||
// (e.g., "443") using encrypted SSL/TLS connection with connect
|
||||
// timeout 60 seconds.
|
||||
session(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_context, const std::string &host,
|
||||
const std::string &service);
|
||||
|
||||
// Starts HTTP/2 session by connecting to |host| and |service|
|
||||
// (e.g., "443") using encrypted SSL/TLS connection with given
|
||||
// connect timeout.
|
||||
session(boost::asio::io_service &io_service,
|
||||
boost::asio::ssl::context &tls_context, const std::string &host,
|
||||
const std::string &service,
|
||||
const boost::posix_time::time_duration &connect_timeout);
|
||||
|
||||
~session();
|
||||
|
||||
session(session &&other) noexcept;
|
||||
|
@ -144,9 +162,6 @@ public:
|
|||
// and session is terminated.
|
||||
void on_error(error_cb cb) const;
|
||||
|
||||
// Sets connect timeout, which defaults to 60 seconds.
|
||||
void connect_timeout(const boost::posix_time::time_duration &t);
|
||||
|
||||
// Sets read timeout, which defaults to 60 seconds.
|
||||
void read_timeout(const boost::posix_time::time_duration &t);
|
||||
|
||||
|
|
|
@ -32,13 +32,23 @@ namespace nghttp2 {
|
|||
namespace util {
|
||||
|
||||
EvbufferBuffer::EvbufferBuffer()
|
||||
: evbuffer_(nullptr), bucket_(nullptr), buf_(nullptr), bufmax_(0),
|
||||
buflen_(0), limit_(0), writelen_(0) {}
|
||||
: evbuffer_(nullptr),
|
||||
bucket_(nullptr),
|
||||
buf_(nullptr),
|
||||
bufmax_(0),
|
||||
buflen_(0),
|
||||
limit_(0),
|
||||
writelen_(0) {}
|
||||
|
||||
EvbufferBuffer::EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
|
||||
ssize_t limit)
|
||||
: evbuffer_(evbuffer), bucket_(limit == -1 ? nullptr : evbuffer_new()),
|
||||
buf_(buf), bufmax_(bufmax), buflen_(0), limit_(limit), writelen_(0) {}
|
||||
: evbuffer_(evbuffer),
|
||||
bucket_(limit == -1 ? nullptr : evbuffer_new()),
|
||||
buf_(buf),
|
||||
bufmax_(bufmax),
|
||||
buflen_(0),
|
||||
limit_(limit),
|
||||
writelen_(0) {}
|
||||
|
||||
void EvbufferBuffer::reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
|
||||
ssize_t limit) {
|
||||
|
|
|
@ -41,9 +41,19 @@
|
|||
|
||||
namespace nghttp2 {
|
||||
|
||||
#define DEFAULT_WR_IOVCNT 16
|
||||
|
||||
#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT
|
||||
#define MAX_WR_IOVCNT IOV_MAX
|
||||
#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
|
||||
#define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT
|
||||
#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
|
||||
|
||||
template <size_t N> struct Memchunk {
|
||||
Memchunk(std::unique_ptr<Memchunk> next_chunk)
|
||||
: pos(std::begin(buf)), last(pos), knext(std::move(next_chunk)),
|
||||
: pos(std::begin(buf)),
|
||||
last(pos),
|
||||
knext(std::move(next_chunk)),
|
||||
next(nullptr) {}
|
||||
size_t len() const { return last - pos; }
|
||||
size_t left() const { return std::end(buf) - last; }
|
||||
|
@ -89,14 +99,16 @@ template <typename Memchunk> struct Memchunks {
|
|||
Memchunks(Pool<Memchunk> *pool)
|
||||
: pool(pool), head(nullptr), tail(nullptr), len(0) {}
|
||||
Memchunks(const Memchunks &) = delete;
|
||||
Memchunks(Memchunks &&other)
|
||||
: pool(other.pool), head(other.head), tail(other.tail), len(other.len) {
|
||||
Memchunks(Memchunks &&other) noexcept : pool(other.pool),
|
||||
head(other.head),
|
||||
tail(other.tail),
|
||||
len(other.len) {
|
||||
// keep other.pool
|
||||
other.head = other.tail = nullptr;
|
||||
other.len = 0;
|
||||
}
|
||||
Memchunks &operator=(const Memchunks &) = delete;
|
||||
Memchunks &operator=(Memchunks &&other) {
|
||||
Memchunks &operator=(Memchunks &&other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
@ -165,6 +177,7 @@ template <typename Memchunk> struct Memchunks {
|
|||
return append(s, N - 1);
|
||||
}
|
||||
size_t append(const std::string &s) { return append(s.c_str(), s.size()); }
|
||||
size_t append(const StringRef &s) { return append(s.c_str(), s.size()); }
|
||||
size_t remove(void *dest, size_t count) {
|
||||
if (!tail || count == 0) {
|
||||
return 0;
|
||||
|
@ -196,6 +209,36 @@ template <typename Memchunk> struct Memchunks {
|
|||
|
||||
return first - static_cast<uint8_t *>(dest);
|
||||
}
|
||||
size_t remove(Memchunks &dest, size_t count) {
|
||||
if (!tail || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto left = count;
|
||||
auto m = head;
|
||||
|
||||
while (m) {
|
||||
auto next = m->next;
|
||||
auto n = std::min(left, m->len());
|
||||
|
||||
assert(m->len());
|
||||
dest.append(m->pos, n);
|
||||
m->pos += n;
|
||||
len -= n;
|
||||
left -= n;
|
||||
if (m->len() > 0) {
|
||||
break;
|
||||
}
|
||||
pool->recycle(m);
|
||||
m = next;
|
||||
}
|
||||
head = m;
|
||||
if (head == nullptr) {
|
||||
tail = nullptr;
|
||||
}
|
||||
|
||||
return count - left;
|
||||
}
|
||||
size_t drain(size_t count) {
|
||||
auto ndata = count;
|
||||
auto m = head;
|
||||
|
@ -249,17 +292,24 @@ template <typename Memchunk> struct Memchunks {
|
|||
// Wrapper around Memchunks to offer "peeking" functionality.
|
||||
template <typename Memchunk> struct PeekMemchunks {
|
||||
PeekMemchunks(Pool<Memchunk> *pool)
|
||||
: memchunks(pool), cur(nullptr), cur_pos(nullptr), cur_last(nullptr),
|
||||
len(0), peeking(true) {}
|
||||
: memchunks(pool),
|
||||
cur(nullptr),
|
||||
cur_pos(nullptr),
|
||||
cur_last(nullptr),
|
||||
len(0),
|
||||
peeking(true) {}
|
||||
PeekMemchunks(const PeekMemchunks &) = delete;
|
||||
PeekMemchunks(PeekMemchunks &&other)
|
||||
: memchunks(std::move(other.memchunks)), cur(other.cur),
|
||||
cur_pos(other.cur_pos), cur_last(other.cur_last), len(other.len),
|
||||
PeekMemchunks(PeekMemchunks &&other) noexcept
|
||||
: memchunks(std::move(other.memchunks)),
|
||||
cur(other.cur),
|
||||
cur_pos(other.cur_pos),
|
||||
cur_last(other.cur_last),
|
||||
len(other.len),
|
||||
peeking(other.peeking) {
|
||||
other.reset();
|
||||
}
|
||||
PeekMemchunks &operator=(const PeekMemchunks &) = delete;
|
||||
PeekMemchunks &operator=(PeekMemchunks &&other) {
|
||||
PeekMemchunks &operator=(PeekMemchunks &&other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
@ -368,14 +418,6 @@ using MemchunkPool = Pool<Memchunk16K>;
|
|||
using DefaultMemchunks = Memchunks<Memchunk16K>;
|
||||
using DefaultPeekMemchunks = PeekMemchunks<Memchunk16K>;
|
||||
|
||||
#define DEFAULT_WR_IOVCNT 16
|
||||
|
||||
#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT
|
||||
#define MAX_WR_IOVCNT IOV_MAX
|
||||
#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
|
||||
#define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT
|
||||
#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
|
||||
|
||||
inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) {
|
||||
if (max == 0) {
|
||||
return 0;
|
||||
|
|
|
@ -94,13 +94,26 @@ constexpr auto anchors = std::array<Anchor, 5>{{
|
|||
|
||||
Config::Config()
|
||||
: header_table_size(-1),
|
||||
min_header_table_size(std::numeric_limits<uint32_t>::max()), padding(0),
|
||||
max_concurrent_streams(100), peer_max_concurrent_streams(100),
|
||||
weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1), timeout(0.), window_bits(-1),
|
||||
connection_window_bits(-1), verbose(0), null_out(false),
|
||||
remote_name(false), get_assets(false), stat(false), upgrade(false),
|
||||
continuation(false), no_content_length(false), no_dep(false),
|
||||
hexdump(false), no_push(false) {
|
||||
min_header_table_size(std::numeric_limits<uint32_t>::max()),
|
||||
padding(0),
|
||||
max_concurrent_streams(100),
|
||||
peer_max_concurrent_streams(100),
|
||||
weight(NGHTTP2_DEFAULT_WEIGHT),
|
||||
multiply(1),
|
||||
timeout(0.),
|
||||
window_bits(-1),
|
||||
connection_window_bits(-1),
|
||||
verbose(0),
|
||||
null_out(false),
|
||||
remote_name(false),
|
||||
get_assets(false),
|
||||
stat(false),
|
||||
upgrade(false),
|
||||
continuation(false),
|
||||
no_content_length(false),
|
||||
no_dep(false),
|
||||
hexdump(false),
|
||||
no_push(false) {
|
||||
nghttp2_option_new(&http2_option);
|
||||
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
|
||||
peer_max_concurrent_streams);
|
||||
|
@ -133,9 +146,18 @@ std::string strip_fragment(const char *raw_uri) {
|
|||
Request::Request(const std::string &uri, const http_parser_url &u,
|
||||
const nghttp2_data_provider *data_prd, int64_t data_length,
|
||||
const nghttp2_priority_spec &pri_spec, int level)
|
||||
: uri(uri), u(u), pri_spec(pri_spec), data_length(data_length),
|
||||
data_offset(0), response_len(0), inflater(nullptr), html_parser(nullptr),
|
||||
data_prd(data_prd), stream_id(-1), status(0), level(level),
|
||||
: uri(uri),
|
||||
u(u),
|
||||
pri_spec(pri_spec),
|
||||
data_length(data_length),
|
||||
data_offset(0),
|
||||
response_len(0),
|
||||
inflater(nullptr),
|
||||
html_parser(nullptr),
|
||||
data_prd(data_prd),
|
||||
stream_id(-1),
|
||||
status(0),
|
||||
level(level),
|
||||
expect_final_response(false) {
|
||||
http2::init_hdidx(res_hdidx);
|
||||
http2::init_hdidx(req_hdidx);
|
||||
|
@ -467,10 +489,20 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
|
||||
HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
|
||||
struct ev_loop *loop, SSL_CTX *ssl_ctx)
|
||||
: session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
|
||||
ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
|
||||
complete(0), success(0), settings_payloadlen(0), state(ClientState::IDLE),
|
||||
upgrade_response_status_code(0), fd(-1),
|
||||
: session(nullptr),
|
||||
callbacks(callbacks),
|
||||
loop(loop),
|
||||
ssl_ctx(ssl_ctx),
|
||||
ssl(nullptr),
|
||||
addrs(nullptr),
|
||||
next_addr(nullptr),
|
||||
cur_addr(nullptr),
|
||||
complete(0),
|
||||
success(0),
|
||||
settings_payloadlen(0),
|
||||
state(ClientState::IDLE),
|
||||
upgrade_response_status_code(0),
|
||||
fd(-1),
|
||||
upgrade_response_complete(false) {
|
||||
ev_io_init(&wev, writecb, 0, EV_WRITE);
|
||||
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||
|
@ -1974,7 +2006,7 @@ id responseEnd requestStart process code size request path)" << std::endl;
|
|||
<< ("+" + util::format_duration(request_start)) << " "
|
||||
<< std::setw(8) << util::format_duration(total) << " "
|
||||
<< std::setw(4) << req->status << " " << std::setw(4)
|
||||
<< util::utos_with_unit(req->response_len) << " "
|
||||
<< util::utos_unit(req->response_len) << " "
|
||||
<< req->make_reqpath() << std::endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,6 +143,11 @@ Options:
|
|||
Default: 1
|
||||
-e, --error-gzip
|
||||
Make error response gzipped.
|
||||
-w, --window-bits=<N>
|
||||
Sets the stream level initial window size to 2**<N>-1.
|
||||
-W, --connection-window-bits=<N>
|
||||
Sets the connection level initial window size to
|
||||
2**<N>-1.
|
||||
--dh-param-file=<PATH>
|
||||
Path to file that contains DH parameters in PEM format.
|
||||
Without this option, DHE cipher suites are not
|
||||
|
@ -202,6 +207,8 @@ int main(int argc, char **argv) {
|
|||
{"max-concurrent-streams", required_argument, nullptr, 'm'},
|
||||
{"workers", required_argument, nullptr, 'n'},
|
||||
{"error-gzip", no_argument, nullptr, 'e'},
|
||||
{"window-bits", required_argument, nullptr, 'w'},
|
||||
{"connection-window-bits", required_argument, nullptr, 'W'},
|
||||
{"no-tls", no_argument, &flag, 1},
|
||||
{"color", no_argument, &flag, 2},
|
||||
{"version", no_argument, &flag, 3},
|
||||
|
@ -214,7 +221,7 @@ int main(int argc, char **argv) {
|
|||
{"no-content-length", no_argument, &flag, 10},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options,
|
||||
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
|
||||
&option_index);
|
||||
char *end;
|
||||
if (c == -1) {
|
||||
|
@ -281,6 +288,26 @@ int main(int argc, char **argv) {
|
|||
std::cerr << "-p: Bad option value: " << optarg << std::endl;
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
case 'W': {
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
auto n = strtoul(optarg, &endptr, 10);
|
||||
if (errno != 0 || *endptr != '\0' || n >= 31) {
|
||||
std::cerr << "-" << static_cast<char>(c)
|
||||
<< ": specify the integer in the range [0, 30], inclusive"
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (c == 'w') {
|
||||
config.window_bits = n;
|
||||
} else {
|
||||
config.connection_window_bits = n;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
util::show_candidates(argv[optind - 1], long_options);
|
||||
exit(EXIT_FAILURE);
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
#include "nghttp2_gzip_test.h"
|
||||
#include "buffer_test.h"
|
||||
#include "memchunk_test.h"
|
||||
#include "template_test.h"
|
||||
#include "shrpx_http_test.h"
|
||||
#include "base64_test.h"
|
||||
#include "shrpx_config.h"
|
||||
#include "ssl.h"
|
||||
|
||||
|
@ -69,6 +72,8 @@ int main(int argc, char *argv[]) {
|
|||
shrpx::test_shrpx_ssl_create_lookup_tree) ||
|
||||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
|
||||
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
|
||||
!CU_add_test(pSuite, "ssl_tls_hostname_match",
|
||||
shrpx::test_shrpx_ssl_tls_hostname_match) ||
|
||||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
|
||||
!CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
|
||||
!CU_add_test(pSuite, "http2_copy_headers_to_nva",
|
||||
|
@ -101,14 +106,10 @@ 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_index_request_headers",
|
||||
shrpx::test_downstream_index_request_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_index_response_headers",
|
||||
shrpx::test_downstream_index_response_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_get_request_header",
|
||||
shrpx::test_downstream_get_request_header) ||
|
||||
!CU_add_test(pSuite, "downstream_get_response_header",
|
||||
shrpx::test_downstream_get_response_header) ||
|
||||
!CU_add_test(pSuite, "downstream_field_store_index_headers",
|
||||
shrpx::test_downstream_field_store_index_headers) ||
|
||||
!CU_add_test(pSuite, "downstream_field_store_header",
|
||||
shrpx::test_downstream_field_store_header) ||
|
||||
!CU_add_test(pSuite, "downstream_crumble_request_cookie",
|
||||
shrpx::test_downstream_crumble_request_cookie) ||
|
||||
!CU_add_test(pSuite, "downstream_assemble_request_cookie",
|
||||
|
@ -125,6 +126,8 @@ int main(int argc, char *argv[]) {
|
|||
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, "http_create_forwarded",
|
||||
shrpx::test_shrpx_http_create_forwarded) ||
|
||||
!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",
|
||||
|
@ -144,10 +147,8 @@ 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_with_unit",
|
||||
shrpx::test_util_utos_with_unit) ||
|
||||
!CU_add_test(pSuite, "util_utos_with_funit",
|
||||
shrpx::test_util_utos_with_funit) ||
|
||||
!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",
|
||||
shrpx::test_util_parse_uint_with_unit) ||
|
||||
!CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) ||
|
||||
|
@ -182,7 +183,13 @@ int main(int argc, char *argv[]) {
|
|||
!CU_add_test(pSuite, "peek_memchunk_disable_peek_no_drain",
|
||||
nghttp2::test_peek_memchunks_disable_peek_no_drain) ||
|
||||
!CU_add_test(pSuite, "peek_memchunk_reset",
|
||||
nghttp2::test_peek_memchunks_reset)) {
|
||||
nghttp2::test_peek_memchunks_reset) ||
|
||||
!CU_add_test(pSuite, "template_immutable_string",
|
||||
nghttp2::test_template_immutable_string) ||
|
||||
!CU_add_test(pSuite, "template_string_ref",
|
||||
nghttp2::test_template_string_ref) ||
|
||||
!CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) ||
|
||||
!CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
|
1679
src/shrpx.cc
1679
src/shrpx.cc
File diff suppressed because it is too large
Load Diff
|
@ -45,16 +45,16 @@ void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
AcceptHandler::AcceptHandler(int fd, ConnectionHandler *h)
|
||||
: conn_hnr_(h), fd_(fd) {
|
||||
ev_io_init(&wev_, acceptcb, fd_, EV_READ);
|
||||
AcceptHandler::AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h)
|
||||
: conn_hnr_(h), faddr_(faddr) {
|
||||
ev_io_init(&wev_, acceptcb, faddr_->fd, EV_READ);
|
||||
wev_.data = this;
|
||||
ev_io_start(conn_hnr_->get_loop(), &wev_);
|
||||
}
|
||||
|
||||
AcceptHandler::~AcceptHandler() {
|
||||
ev_io_stop(conn_hnr_->get_loop(), &wev_);
|
||||
close(fd_);
|
||||
close(faddr_->fd);
|
||||
}
|
||||
|
||||
void AcceptHandler::accept_connection() {
|
||||
|
@ -63,10 +63,10 @@ void AcceptHandler::accept_connection() {
|
|||
socklen_t addrlen = sizeof(sockaddr);
|
||||
|
||||
#ifdef HAVE_ACCEPT4
|
||||
auto cfd =
|
||||
accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
#else // !HAVE_ACCEPT4
|
||||
auto cfd = accept(fd_, &sockaddr.sa, &addrlen);
|
||||
auto cfd = accept4(faddr_->fd, &sockaddr.sa, &addrlen,
|
||||
SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
#else // !HAVE_ACCEPT4
|
||||
auto cfd = accept(faddr_->fd, &sockaddr.sa, &addrlen);
|
||||
#endif // !HAVE_ACCEPT4
|
||||
|
||||
if (cfd == -1) {
|
||||
|
@ -87,7 +87,7 @@ void AcceptHandler::accept_connection() {
|
|||
case ENFILE:
|
||||
LOG(WARN) << "acceptor: running out file descriptor; disable acceptor "
|
||||
"temporarily";
|
||||
conn_hnr_->disable_acceptor_temporary(30.);
|
||||
conn_hnr_->sleep_acceptor(get_config()->conn.listener.timeout.sleep);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ void AcceptHandler::accept_connection() {
|
|||
|
||||
util::make_socket_nodelay(cfd);
|
||||
|
||||
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen);
|
||||
conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen, faddr_);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,6 @@ void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); }
|
|||
|
||||
void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); }
|
||||
|
||||
int AcceptHandler::get_fd() const { return fd_; }
|
||||
int AcceptHandler::get_fd() const { return faddr_->fd; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -32,10 +32,11 @@
|
|||
namespace shrpx {
|
||||
|
||||
class ConnectionHandler;
|
||||
struct UpstreamAddr;
|
||||
|
||||
class AcceptHandler {
|
||||
public:
|
||||
AcceptHandler(int fd, ConnectionHandler *h);
|
||||
AcceptHandler(const UpstreamAddr *faddr, ConnectionHandler *h);
|
||||
~AcceptHandler();
|
||||
void accept_connection();
|
||||
void enable();
|
||||
|
@ -45,7 +46,7 @@ public:
|
|||
private:
|
||||
ev_io wev_;
|
||||
ConnectionHandler *conn_hnr_;
|
||||
int fd_;
|
||||
const UpstreamAddr *faddr_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -27,6 +27,13 @@
|
|||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif // HAVE_UNISTD_H
|
||||
#ifdef HAVE_SYS_SOCKET_H
|
||||
#include <sys/socket.h>
|
||||
#endif // HAVE_SYS_SOCKET_H
|
||||
#ifdef HAVE_NETDB_H
|
||||
#include <netdb.h>
|
||||
#endif // HAVE_NETDB_H
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#include "shrpx_upstream.h"
|
||||
|
@ -120,6 +127,10 @@ int ClientHandler::read_clear() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!ev_is_active(&conn_.rev)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto nread = conn_.read_clear(rb_.last, rb_.wleft());
|
||||
|
||||
if (nread == 0) {
|
||||
|
@ -213,6 +224,10 @@ int ClientHandler::read_tls() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!ev_is_active(&conn_.rev)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto nread = conn_.read_tls(rb_.last, rb_.wleft());
|
||||
|
||||
if (nread == 0) {
|
||||
|
@ -362,20 +377,24 @@ int ClientHandler::upstream_http1_connhd_read() {
|
|||
}
|
||||
|
||||
ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
||||
const char *ipaddr, const char *port)
|
||||
const char *ipaddr, const char *port, int family,
|
||||
const UpstreamAddr *faddr)
|
||||
: conn_(worker->get_loop(), fd, ssl, worker->get_mcpool(),
|
||||
get_config()->upstream_write_timeout,
|
||||
get_config()->upstream_read_timeout, get_config()->write_rate,
|
||||
get_config()->write_burst, get_config()->read_rate,
|
||||
get_config()->read_burst, writecb, readcb, timeoutcb, this,
|
||||
get_config()->tls_dyn_rec_warmup_threshold,
|
||||
get_config()->tls_dyn_rec_idle_timeout),
|
||||
get_config()->conn.upstream.timeout.write,
|
||||
get_config()->conn.upstream.timeout.read,
|
||||
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()->downstream_proto == PROTO_HTTP2
|
||||
get_config()->conn.downstream.proto == PROTO_HTTP2
|
||||
? make_unique<std::vector<ssize_t>>(
|
||||
get_config()->downstream_addr_groups.size(), -1)
|
||||
get_config()->conn.downstream.addr_groups.size(), -1)
|
||||
: nullptr),
|
||||
ipaddr_(ipaddr), port_(port), worker_(worker),
|
||||
ipaddr_(ipaddr),
|
||||
port_(port),
|
||||
faddr_(faddr),
|
||||
worker_(worker),
|
||||
left_connhd_len_(NGHTTP2_CLIENT_MAGIC_LEN),
|
||||
should_close_after_write_(false) {
|
||||
|
||||
|
@ -388,7 +407,7 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
|||
conn_.rlimit.startw();
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
if (get_config()->accept_proxy_protocol) {
|
||||
if (get_config()->conn.upstream.accept_proxy_protocol) {
|
||||
read_ = &ClientHandler::read_clear;
|
||||
write_ = &ClientHandler::noop;
|
||||
on_read_ = &ClientHandler::proxy_protocol_read;
|
||||
|
@ -396,6 +415,23 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
|
|||
} else {
|
||||
setup_upstream_io_callback();
|
||||
}
|
||||
|
||||
auto &fwdconf = get_config()->http.forwarded;
|
||||
|
||||
if (fwdconf.params & FORWARDED_FOR) {
|
||||
if (fwdconf.for_node_type == FORWARDED_NODE_OBFUSCATED) {
|
||||
forwarded_for_ = "_";
|
||||
forwarded_for_ += util::random_alpha_digit(worker_->get_randgen(),
|
||||
SHRPX_OBFUSCATED_NODE_LENGTH);
|
||||
} else if (family == AF_INET6) {
|
||||
forwarded_for_ = "[";
|
||||
forwarded_for_ += ipaddr_;
|
||||
forwarded_for_ += ']';
|
||||
} else {
|
||||
// family == AF_INET or family == AF_UNIX
|
||||
forwarded_for_ = ipaddr_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientHandler::setup_upstream_io_callback() {
|
||||
|
@ -503,7 +539,8 @@ int ClientHandler::validate_next_proto() {
|
|||
CLOG(INFO, this) << "The negotiated next protocol: " << proto;
|
||||
}
|
||||
|
||||
if (!ssl::in_proto_list(get_config()->npn_list, next_proto, next_proto_len)) {
|
||||
if (!ssl::in_proto_list(get_config()->tls.npn_list, next_proto,
|
||||
next_proto_len)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this) << "The negotiated protocol is not supported";
|
||||
}
|
||||
|
@ -626,33 +663,34 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
|
|||
std::unique_ptr<DownstreamConnection>
|
||||
ClientHandler::get_downstream_connection(Downstream *downstream) {
|
||||
size_t group;
|
||||
auto &groups = get_config()->downstream_addr_groups;
|
||||
auto catch_all = get_config()->downstream_addr_group_catch_all;
|
||||
auto &downstreamconf = get_config()->conn.downstream;
|
||||
auto &groups = downstreamconf.addr_groups;
|
||||
auto catch_all = downstreamconf.addr_group_catch_all;
|
||||
|
||||
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.
|
||||
if (groups.size() == 1) {
|
||||
group = 0;
|
||||
} else if (downstream->get_request_method() == HTTP_CONNECT) {
|
||||
} 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;
|
||||
} else {
|
||||
auto &router = get_config()->router;
|
||||
if (!downstream->get_request_http2_authority().empty()) {
|
||||
group = match_downstream_addr_group(
|
||||
router, downstream->get_request_http2_authority(),
|
||||
downstream->get_request_path(), groups, catch_all);
|
||||
if (!req.authority.empty()) {
|
||||
group = match_downstream_addr_group(router, req.authority, req.path,
|
||||
groups, catch_all);
|
||||
} else {
|
||||
auto h = downstream->get_request_header(http2::HD_HOST);
|
||||
auto h = req.fs.header(http2::HD_HOST);
|
||||
if (h) {
|
||||
group = match_downstream_addr_group(router, h->value,
|
||||
downstream->get_request_path(),
|
||||
groups, catch_all);
|
||||
group = match_downstream_addr_group(router, h->value, req.path, groups,
|
||||
catch_all);
|
||||
} else {
|
||||
group = match_downstream_addr_group(
|
||||
router, "", downstream->get_request_path(), groups, catch_all);
|
||||
group = match_downstream_addr_group(router, "", req.path, groups,
|
||||
catch_all);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -672,7 +710,7 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
|
||||
auto dconn_pool = worker_->get_dconn_pool();
|
||||
|
||||
if (get_config()->downstream_proto == PROTO_HTTP2) {
|
||||
if (downstreamconf.proto == PROTO_HTTP2) {
|
||||
Http2Session *http2session;
|
||||
auto &pinned = (*pinned_http2sessions_)[group];
|
||||
if (pinned == -1) {
|
||||
|
@ -684,8 +722,8 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
|
|||
}
|
||||
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
|
||||
} else {
|
||||
dconn =
|
||||
make_unique<HttpDownstreamConnection>(dconn_pool, group, conn_.loop);
|
||||
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, group,
|
||||
conn_.loop, worker_);
|
||||
}
|
||||
dconn->set_client_handler(this);
|
||||
return dconn;
|
||||
|
@ -735,17 +773,6 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
|||
"Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
|
||||
"\r\n";
|
||||
|
||||
auto required_size = str_size(res) + input->rleft();
|
||||
|
||||
if (output->wleft() < required_size) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
CLOG(INFO, this)
|
||||
<< "HTTP Upgrade failed because of insufficient buffer space: need "
|
||||
<< required_size << ", available " << output->wleft();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (upstream->upgrade_upstream(http) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -757,11 +784,8 @@ int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
|
|||
on_read_ = &ClientHandler::upstream_http2_connhd_read;
|
||||
write_ = &ClientHandler::write_clear;
|
||||
|
||||
auto nread =
|
||||
downstream->get_response_buf()->remove(output->last, output->wleft());
|
||||
output->write(nread);
|
||||
|
||||
output->write(res, str_size(res));
|
||||
input->remove(*output, input->rleft());
|
||||
output->append(res, str_size(res));
|
||||
upstream_ = std::move(upstream);
|
||||
|
||||
signal_write();
|
||||
|
@ -783,28 +807,27 @@ void ClientHandler::start_immediate_shutdown() {
|
|||
}
|
||||
|
||||
namespace {
|
||||
// Construct absolute request URI from |downstream|, mainly to log
|
||||
// Construct absolute request URI from |Request|, mainly to log
|
||||
// request URI for proxy request (HTTP/2 proxy or client proxy). This
|
||||
// is mostly same routine found in
|
||||
// HttpDownstreamConnection::push_request_headers(), but vastly
|
||||
// simplified since we only care about absolute URI.
|
||||
std::string construct_absolute_request_uri(Downstream *downstream) {
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
if (authority.empty()) {
|
||||
return downstream->get_request_path();
|
||||
std::string construct_absolute_request_uri(const Request &req) {
|
||||
if (req.authority.empty()) {
|
||||
return req.path;
|
||||
}
|
||||
|
||||
std::string uri;
|
||||
auto &scheme = downstream->get_request_http2_scheme();
|
||||
if (scheme.empty()) {
|
||||
if (req.scheme.empty()) {
|
||||
// We may have to log the request which lacks scheme (e.g.,
|
||||
// http/1.1 with origin form).
|
||||
uri += "http://";
|
||||
} else {
|
||||
uri += scheme;
|
||||
uri += req.scheme;
|
||||
uri += "://";
|
||||
}
|
||||
uri += authority;
|
||||
uri += downstream->get_request_path();
|
||||
uri += req.authority;
|
||||
uri += req.path;
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
@ -812,34 +835,34 @@ std::string construct_absolute_request_uri(Downstream *downstream) {
|
|||
|
||||
void ClientHandler::write_accesslog(Downstream *downstream) {
|
||||
nghttp2::ssl::TLSSessionInfo tls_info;
|
||||
const auto &req = downstream->request();
|
||||
const auto &resp = downstream->response();
|
||||
|
||||
upstream_accesslog(
|
||||
get_config()->accesslog_format,
|
||||
get_config()->logging.access.format,
|
||||
LogSpec{
|
||||
downstream, ipaddr_.c_str(),
|
||||
http2::to_method_string(downstream->get_request_method()),
|
||||
downstream, StringRef(ipaddr_), http2::to_method_string(req.method),
|
||||
|
||||
downstream->get_request_method() == HTTP_CONNECT
|
||||
? downstream->get_request_http2_authority().c_str()
|
||||
req.method == HTTP_CONNECT
|
||||
? StringRef(req.authority)
|
||||
: (get_config()->http2_proxy || get_config()->client_proxy)
|
||||
? construct_absolute_request_uri(downstream).c_str()
|
||||
: downstream->get_request_path().empty()
|
||||
? downstream->get_request_method() == HTTP_OPTIONS
|
||||
? "*"
|
||||
: "-"
|
||||
: downstream->get_request_path().c_str(),
|
||||
? StringRef(construct_absolute_request_uri(req))
|
||||
: req.path.empty()
|
||||
? req.method == HTTP_OPTIONS
|
||||
? StringRef::from_lit("*")
|
||||
: StringRef::from_lit("-")
|
||||
: StringRef(req.path),
|
||||
|
||||
alpn_.c_str(),
|
||||
StringRef(alpn_),
|
||||
nghttp2::ssl::get_tls_session_info(&tls_info, conn_.tls.ssl),
|
||||
|
||||
std::chrono::system_clock::now(), // time_now
|
||||
downstream->get_request_start_time(), // request_start_time
|
||||
std::chrono::high_resolution_clock::now(), // request_end_time
|
||||
|
||||
downstream->get_request_major(), downstream->get_request_minor(),
|
||||
downstream->get_response_http_status(),
|
||||
downstream->get_response_sent_bodylen(), port_.c_str(),
|
||||
get_config()->port, get_config()->pid,
|
||||
req.http_major, req.http_minor, resp.http_status,
|
||||
downstream->response_sent_body_length, StringRef(port_), faddr_->port,
|
||||
get_config()->pid,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -849,20 +872,20 @@ void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
|
|||
auto highres_now = std::chrono::high_resolution_clock::now();
|
||||
nghttp2::ssl::TLSSessionInfo tls_info;
|
||||
|
||||
upstream_accesslog(get_config()->accesslog_format,
|
||||
upstream_accesslog(get_config()->logging.access.format,
|
||||
LogSpec{
|
||||
nullptr, ipaddr_.c_str(),
|
||||
"-", // method
|
||||
"-", // path,
|
||||
alpn_.c_str(), nghttp2::ssl::get_tls_session_info(
|
||||
&tls_info, conn_.tls.ssl),
|
||||
nullptr, StringRef(ipaddr_),
|
||||
StringRef::from_lit("-"), // method
|
||||
StringRef::from_lit("-"), // path,
|
||||
StringRef(alpn_), nghttp2::ssl::get_tls_session_info(
|
||||
&tls_info, conn_.tls.ssl),
|
||||
time_now,
|
||||
highres_now, // request_start_time TODO is
|
||||
// there a better value?
|
||||
highres_now, // request_end_time
|
||||
major, minor, // major, minor
|
||||
status, body_bytes_sent, port_.c_str(),
|
||||
get_config()->port, get_config()->pid,
|
||||
status, body_bytes_sent, StringRef(port_),
|
||||
faddr_->port, get_config()->pid,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -952,7 +975,7 @@ int ClientHandler::proxy_protocol_read() {
|
|||
|
||||
--end;
|
||||
|
||||
constexpr const char HEADER[] = "PROXY ";
|
||||
constexpr char HEADER[] = "PROXY ";
|
||||
|
||||
if (static_cast<size_t>(end - rb_.pos) < str_size(HEADER)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -1103,4 +1126,18 @@ int ClientHandler::proxy_protocol_read() {
|
|||
return on_proxy_protocol_finish();
|
||||
}
|
||||
|
||||
StringRef ClientHandler::get_forwarded_by() {
|
||||
auto &fwdconf = get_config()->http.forwarded;
|
||||
|
||||
if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) {
|
||||
return StringRef(fwdconf.by_obfuscated);
|
||||
}
|
||||
|
||||
return StringRef(faddr_->hostport);
|
||||
}
|
||||
|
||||
const std::string &ClientHandler::get_forwarded_for() const {
|
||||
return forwarded_for_;
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -53,7 +53,7 @@ struct WorkerStat;
|
|||
class ClientHandler {
|
||||
public:
|
||||
ClientHandler(Worker *worker, int fd, SSL *ssl, const char *ipaddr,
|
||||
const char *port);
|
||||
const char *port, int family, const UpstreamAddr *faddr);
|
||||
~ClientHandler();
|
||||
|
||||
int noop();
|
||||
|
@ -134,17 +134,31 @@ public:
|
|||
|
||||
void setup_upstream_io_callback();
|
||||
|
||||
// Returns string suitable for use in "by" parameter of Forwarded
|
||||
// header field.
|
||||
StringRef get_forwarded_by();
|
||||
// Returns string suitable for use in "for" parameter of Forwarded
|
||||
// header field.
|
||||
const std::string &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_;
|
||||
std::string port_;
|
||||
// The ALPN identifier negotiated for this connection.
|
||||
std::string alpn_;
|
||||
// The client address used in "for" parameter of Forwarded header
|
||||
// field.
|
||||
std::string forwarded_for_;
|
||||
std::function<int(ClientHandler &)> read_, write_;
|
||||
std::function<int(ClientHandler &)> on_read_, on_write_;
|
||||
// Address of frontend listening socket
|
||||
const UpstreamAddr *faddr_;
|
||||
Worker *worker_;
|
||||
// The number of bytes of HTTP/2 client connection header to read
|
||||
size_t left_connhd_len_;
|
||||
|
|
|
@ -71,6 +71,8 @@ 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) {
|
||||
|
@ -79,8 +81,10 @@ TicketKeys::~TicketKeys() {
|
|||
}
|
||||
|
||||
DownstreamAddr::DownstreamAddr(const DownstreamAddr &other)
|
||||
: addr(other.addr), host(strcopy(other.host)),
|
||||
hostport(strcopy(other.hostport)), port(other.port),
|
||||
: addr(other.addr),
|
||||
host(other.host),
|
||||
hostport(other.hostport),
|
||||
port(other.port),
|
||||
host_unix(other.host_unix) {}
|
||||
|
||||
DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
|
||||
|
@ -89,8 +93,8 @@ DownstreamAddr &DownstreamAddr::operator=(const DownstreamAddr &other) {
|
|||
}
|
||||
|
||||
addr = other.addr;
|
||||
host = strcopy(other.host);
|
||||
hostport = strcopy(other.hostport);
|
||||
host = other.host;
|
||||
hostport = other.hostport;
|
||||
port = other.port;
|
||||
host_unix = other.host_unix;
|
||||
|
||||
|
@ -543,7 +547,8 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
|
|||
}
|
||||
|
||||
if (literal_start < var_start) {
|
||||
res.emplace_back(SHRPX_LOGF_LITERAL, strcopy(literal_start, var_start));
|
||||
res.emplace_back(SHRPX_LOGF_LITERAL,
|
||||
ImmutableString(literal_start, var_start));
|
||||
}
|
||||
|
||||
literal_start = p;
|
||||
|
@ -553,17 +558,18 @@ std::vector<LogFragment> parse_log_format(const char *optarg) {
|
|||
continue;
|
||||
}
|
||||
|
||||
res.emplace_back(type, strcopy(value, var_name + var_namelen));
|
||||
auto &v = res.back().value;
|
||||
for (size_t i = 0; v[i]; ++i) {
|
||||
if (v[i] == '_') {
|
||||
v[i] = '-';
|
||||
auto name = std::string(value, var_name + var_namelen);
|
||||
for (auto &c : name) {
|
||||
if (c == '_') {
|
||||
c = '-';
|
||||
}
|
||||
}
|
||||
|
||||
res.emplace_back(type, ImmutableString(name));
|
||||
}
|
||||
|
||||
if (literal_start != eop) {
|
||||
res.emplace_back(SHRPX_LOGF_LITERAL, strcopy(literal_start, eop));
|
||||
res.emplace_back(SHRPX_LOGF_LITERAL, ImmutableString(literal_start, eop));
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -593,6 +599,8 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
|||
// will append '/' to all patterns, so it becomes catch-all pattern.
|
||||
auto mapping = util::split_config_str_list(src, ':');
|
||||
assert(!mapping.empty());
|
||||
auto &addr_groups = mod_config()->conn.downstream.addr_groups;
|
||||
|
||||
for (const auto &raw_pattern : mapping) {
|
||||
auto done = false;
|
||||
std::string pattern;
|
||||
|
@ -607,7 +615,7 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
|||
util::inp_strlower(pattern);
|
||||
pattern += http2::normalize_path(slash, raw_pattern.second);
|
||||
}
|
||||
for (auto &g : mod_config()->downstream_addr_groups) {
|
||||
for (auto &g : addr_groups) {
|
||||
if (g.pattern.get() == pattern) {
|
||||
g.addrs.push_back(addr);
|
||||
done = true;
|
||||
|
@ -621,19 +629,45 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
|
|||
g.addrs.push_back(addr);
|
||||
|
||||
mod_config()->router.add_route(g.pattern.get(), strlen(g.pattern.get()),
|
||||
get_config()->downstream_addr_groups.size());
|
||||
addr_groups.size());
|
||||
|
||||
mod_config()->downstream_addr_groups.push_back(std::move(g));
|
||||
addr_groups.push_back(std::move(g));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int parse_forwarded_node_type(const std::string &optarg) {
|
||||
if (util::strieq(optarg, "obfuscated")) {
|
||||
return FORWARDED_NODE_OBFUSCATED;
|
||||
}
|
||||
|
||||
if (util::strieq(optarg, "ip")) {
|
||||
return FORWARDED_NODE_IP;
|
||||
}
|
||||
|
||||
if (optarg.size() < 2 || optarg[0] != '_') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (std::find_if_not(std::begin(optarg), std::end(optarg), [](char c) {
|
||||
return util::is_alpha(c) || util::is_digit(c) || c == '.' || c == '_' ||
|
||||
c == '-';
|
||||
}) != std::end(optarg)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return FORWARDED_NODE_OBFUSCATED;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// generated by gennghttpxfun.py
|
||||
enum {
|
||||
SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL,
|
||||
SHRPX_OPTID_ACCESSLOG_FILE,
|
||||
SHRPX_OPTID_ACCESSLOG_FORMAT,
|
||||
SHRPX_OPTID_ACCESSLOG_SYSLOG,
|
||||
SHRPX_OPTID_ADD_FORWARDED,
|
||||
SHRPX_OPTID_ADD_REQUEST_HEADER,
|
||||
SHRPX_OPTID_ADD_RESPONSE_HEADER,
|
||||
SHRPX_OPTID_ADD_X_FORWARDED_FOR,
|
||||
|
@ -642,6 +676,7 @@ enum {
|
|||
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_WINDOW_BITS,
|
||||
|
@ -652,6 +687,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_SNI_FIELD,
|
||||
SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
|
||||
SHRPX_OPTID_BACKLOG,
|
||||
|
@ -669,6 +705,8 @@ enum {
|
|||
SHRPX_OPTID_ERRORLOG_SYSLOG,
|
||||
SHRPX_OPTID_FASTOPEN,
|
||||
SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE,
|
||||
SHRPX_OPTID_FORWARDED_BY,
|
||||
SHRPX_OPTID_FORWARDED_FOR,
|
||||
SHRPX_OPTID_FRONTEND,
|
||||
SHRPX_OPTID_FRONTEND_FRAME_DEBUG,
|
||||
SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
|
||||
|
@ -690,8 +728,11 @@ enum {
|
|||
SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT,
|
||||
SHRPX_OPTID_LOG_LEVEL,
|
||||
SHRPX_OPTID_MAX_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS,
|
||||
SHRPX_OPTID_MRUBY_FILE,
|
||||
SHRPX_OPTID_NO_HOST_REWRITE,
|
||||
SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST,
|
||||
SHRPX_OPTID_NO_LOCATION_REWRITE,
|
||||
SHRPX_OPTID_NO_OCSP,
|
||||
SHRPX_OPTID_NO_SERVER_PUSH,
|
||||
|
@ -704,9 +745,12 @@ enum {
|
|||
SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE,
|
||||
SHRPX_OPTID_READ_BURST,
|
||||
SHRPX_OPTID_READ_RATE,
|
||||
SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER,
|
||||
SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER,
|
||||
SHRPX_OPTID_RLIMIT_NOFILE,
|
||||
SHRPX_OPTID_STREAM_READ_TIMEOUT,
|
||||
SHRPX_OPTID_STREAM_WRITE_TIMEOUT,
|
||||
SHRPX_OPTID_STRIP_INCOMING_FORWARDED,
|
||||
SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR,
|
||||
SHRPX_OPTID_SUBCERT,
|
||||
SHRPX_OPTID_SYSLOG_FACILITY,
|
||||
|
@ -915,11 +959,19 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
if (util::strieq_l("client-prox", name, 11)) {
|
||||
return SHRPX_OPTID_CLIENT_PROXY;
|
||||
}
|
||||
if (util::strieq_l("forwarded-b", name, 11)) {
|
||||
return SHRPX_OPTID_FORWARDED_BY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 13:
|
||||
switch (name[12]) {
|
||||
case 'd':
|
||||
if (util::strieq_l("add-forwarde", name, 12)) {
|
||||
return SHRPX_OPTID_ADD_FORWARDED;
|
||||
}
|
||||
break;
|
||||
case 'e':
|
||||
if (util::strieq_l("dh-param-fil", name, 12)) {
|
||||
return SHRPX_OPTID_DH_PARAM_FILE;
|
||||
|
@ -931,6 +983,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
return SHRPX_OPTID_RLIMIT_NOFILE;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("forwarded-fo", name, 12)) {
|
||||
return SHRPX_OPTID_FORWARDED_FOR;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (util::strieq_l("verify-clien", name, 12)) {
|
||||
return SHRPX_OPTID_VERIFY_CLIENT;
|
||||
|
@ -1022,6 +1079,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
}
|
||||
break;
|
||||
case 's':
|
||||
if (util::strieq_l("backend-http1-tl", name, 16)) {
|
||||
return SHRPX_OPTID_BACKEND_HTTP1_TLS;
|
||||
}
|
||||
if (util::strieq_l("max-header-field", name, 16)) {
|
||||
return SHRPX_OPTID_MAX_HEADER_FIELDS;
|
||||
}
|
||||
|
@ -1166,6 +1226,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
case 24:
|
||||
switch (name[23]) {
|
||||
case 'd':
|
||||
if (util::strieq_l("strip-incoming-forwarde", name, 23)) {
|
||||
return SHRPX_OPTID_STRIP_INCOMING_FORWARDED;
|
||||
}
|
||||
if (util::strieq_l("tls-ticket-key-memcache", name, 23)) {
|
||||
return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED;
|
||||
}
|
||||
|
@ -1196,6 +1259,9 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
if (util::strieq_l("backend-http2-window-bit", name, 24)) {
|
||||
return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS;
|
||||
}
|
||||
if (util::strieq_l("max-request-header-field", name, 24)) {
|
||||
return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -1205,11 +1271,17 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
|
||||
return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS;
|
||||
}
|
||||
if (util::strieq_l("max-response-header-field", name, 25)) {
|
||||
return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
|
||||
return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT;
|
||||
}
|
||||
if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) {
|
||||
return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -1220,6 +1292,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("request-header-field-buffe", name, 26)) {
|
||||
return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (util::strieq_l("worker-frontend-connection", name, 26)) {
|
||||
return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS;
|
||||
|
@ -1239,6 +1316,11 @@ int option_lookup_token(const char *name, size_t namelen) {
|
|||
return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
if (util::strieq_l("response-header-field-buffe", name, 27)) {
|
||||
return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER;
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (util::strieq_l("http2-max-concurrent-stream", name, 27)) {
|
||||
return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS;
|
||||
|
@ -1301,6 +1383,9 @@ 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)) {
|
||||
|
@ -1349,7 +1434,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
DownstreamAddr addr;
|
||||
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
|
||||
addr.host = strcopy(path, pat_delim);
|
||||
addr.host = ImmutableString(path, pat_delim);
|
||||
addr.host_unix = true;
|
||||
} else {
|
||||
if (split_host_port(host, sizeof(host), &port, optarg,
|
||||
|
@ -1357,7 +1442,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
addr.host = strcopy(host);
|
||||
addr.host = ImmutableString(host);
|
||||
addr.port = port;
|
||||
}
|
||||
|
||||
|
@ -1373,11 +1458,17 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_FRONTEND: {
|
||||
auto &listenerconf = mod_config()->conn.listener;
|
||||
|
||||
UpstreamAddr addr{};
|
||||
addr.fd = -1;
|
||||
|
||||
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
|
||||
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX);
|
||||
mod_config()->host = strcopy(path);
|
||||
mod_config()->port = 0;
|
||||
mod_config()->host_unix = true;
|
||||
addr.host = ImmutableString(path);
|
||||
addr.host_unix = true;
|
||||
|
||||
listenerconf.addrs.push_back(std::move(addr));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1387,9 +1478,26 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->host = strcopy(host);
|
||||
mod_config()->port = port;
|
||||
mod_config()->host_unix = false;
|
||||
addr.host = ImmutableString(host);
|
||||
addr.port = port;
|
||||
|
||||
if (util::numeric_host(host, AF_INET)) {
|
||||
addr.family = AF_INET;
|
||||
listenerconf.addrs.push_back(std::move(addr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (util::numeric_host(host, AF_INET6)) {
|
||||
addr.family = AF_INET6;
|
||||
listenerconf.addrs.push_back(std::move(addr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr.family = AF_INET;
|
||||
listenerconf.addrs.push_back(addr);
|
||||
|
||||
addr.family = AF_INET6;
|
||||
listenerconf.addrs.push_back(std::move(addr));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1401,7 +1509,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
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);
|
||||
return parse_uint(&mod_config()->http2.max_concurrent_streams, opt, optarg);
|
||||
case SHRPX_OPTID_LOG_LEVEL:
|
||||
if (Log::set_severity_level_by_name(optarg) == -1) {
|
||||
LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
|
||||
|
@ -1426,50 +1534,56 @@ int parse_config(const char *opt, const char *optarg,
|
|||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
|
||||
mod_config()->add_x_forwarded_for = util::strieq(optarg, "yes");
|
||||
mod_config()->http.xff.add = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR:
|
||||
mod_config()->strip_incoming_x_forwarded_for = util::strieq(optarg, "yes");
|
||||
mod_config()->http.xff.strip_incoming = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_NO_VIA:
|
||||
mod_config()->no_via = util::strieq(optarg, "yes");
|
||||
mod_config()->http.no_via = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT:
|
||||
return parse_duration(&mod_config()->http2_upstream_read_timeout, opt,
|
||||
return parse_duration(&mod_config()->conn.upstream.timeout.http2_read, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_FRONTEND_READ_TIMEOUT:
|
||||
return parse_duration(&mod_config()->upstream_read_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->conn.upstream.timeout.read, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->upstream_write_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->conn.upstream.timeout.write, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_BACKEND_READ_TIMEOUT:
|
||||
return parse_duration(&mod_config()->downstream_read_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->conn.downstream.timeout.read, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->downstream_write_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->conn.downstream.timeout.write, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_STREAM_READ_TIMEOUT:
|
||||
return parse_duration(&mod_config()->stream_read_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->http2.timeout.stream_read, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_STREAM_WRITE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->stream_write_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->http2.timeout.stream_write, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_ACCESSLOG_FILE:
|
||||
mod_config()->accesslog_file = strcopy(optarg);
|
||||
mod_config()->logging.access.file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ACCESSLOG_SYSLOG:
|
||||
mod_config()->accesslog_syslog = util::strieq(optarg, "yes");
|
||||
mod_config()->logging.access.syslog = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ACCESSLOG_FORMAT:
|
||||
mod_config()->accesslog_format = parse_log_format(optarg);
|
||||
mod_config()->logging.access.format = parse_log_format(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ERRORLOG_FILE:
|
||||
mod_config()->errorlog_file = strcopy(optarg);
|
||||
mod_config()->logging.error.file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ERRORLOG_SYSLOG:
|
||||
mod_config()->errorlog_syslog = util::strieq(optarg, "yes");
|
||||
mod_config()->logging.error.syslog = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FASTOPEN: {
|
||||
|
@ -1483,21 +1597,21 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->fastopen = n;
|
||||
mod_config()->conn.listener.fastopen = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->downstream_idle_read_timeout, opt,
|
||||
return parse_duration(&mod_config()->conn.downstream.timeout.idle_read, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS:
|
||||
case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: {
|
||||
size_t *resp;
|
||||
|
||||
if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) {
|
||||
resp = &mod_config()->http2_upstream_window_bits;
|
||||
resp = &mod_config()->http2.upstream.window_bits;
|
||||
} else {
|
||||
resp = &mod_config()->http2_downstream_window_bits;
|
||||
resp = &mod_config()->http2.downstream.window_bits;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
@ -1523,9 +1637,9 @@ int parse_config(const char *opt, const char *optarg,
|
|||
size_t *resp;
|
||||
|
||||
if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) {
|
||||
resp = &mod_config()->http2_upstream_connection_window_bits;
|
||||
resp = &mod_config()->http2.upstream.connection_window_bits;
|
||||
} else {
|
||||
resp = &mod_config()->http2_downstream_connection_window_bits;
|
||||
resp = &mod_config()->http2.downstream.connection_window_bits;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
@ -1547,15 +1661,15 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_FRONTEND_NO_TLS:
|
||||
mod_config()->upstream_no_tls = util::strieq(optarg, "yes");
|
||||
mod_config()->conn.upstream.no_tls = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_NO_TLS:
|
||||
mod_config()->downstream_no_tls = util::strieq(optarg, "yes");
|
||||
mod_config()->conn.downstream.no_tls = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
|
||||
mod_config()->backend_tls_sni_name = strcopy(optarg);
|
||||
mod_config()->tls.backend_sni_name = optarg;
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_PID_FILE:
|
||||
|
@ -1576,7 +1690,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_PRIVATE_KEY_FILE:
|
||||
mod_config()->private_key_file = strcopy(optarg);
|
||||
mod_config()->tls.private_key_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: {
|
||||
|
@ -1585,16 +1699,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()->private_key_passwd = strcopy(passwd);
|
||||
mod_config()->tls.private_key_passwd = strcopy(passwd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_CERTIFICATE_FILE:
|
||||
mod_config()->cert_file = strcopy(optarg);
|
||||
mod_config()->tls.cert_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_DH_PARAM_FILE:
|
||||
mod_config()->dh_param_file = strcopy(optarg);
|
||||
mod_config()->tls.dh_param_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_SUBCERT: {
|
||||
|
@ -1603,7 +1717,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
if (sp) {
|
||||
std::string keyfile(optarg, sp);
|
||||
// TODO Do we need private key for subcert?
|
||||
mod_config()->subcerts.emplace_back(keyfile, sp + 1);
|
||||
mod_config()->tls.subcerts.emplace_back(keyfile, sp + 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1614,7 +1728,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg;
|
||||
return -1;
|
||||
}
|
||||
mod_config()->syslog_facility = facility;
|
||||
mod_config()->logging.syslog_facility = facility;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1630,12 +1744,12 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->backlog = n;
|
||||
mod_config()->conn.listener.backlog = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_CIPHERS:
|
||||
mod_config()->ciphers = strcopy(optarg);
|
||||
mod_config()->tls.ciphers = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CLIENT:
|
||||
|
@ -1643,22 +1757,26 @@ int parse_config(const char *opt, const char *optarg,
|
|||
|
||||
return 0;
|
||||
case SHRPX_OPTID_INSECURE:
|
||||
mod_config()->insecure = util::strieq(optarg, "yes");
|
||||
mod_config()->tls.insecure = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CACERT:
|
||||
mod_config()->cacert = strcopy(optarg);
|
||||
mod_config()->tls.cacert = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_IPV4:
|
||||
mod_config()->backend_ipv4 = util::strieq(optarg, "yes");
|
||||
mod_config()->conn.downstream.ipv4 = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_IPV6:
|
||||
mod_config()->backend_ipv6 = util::strieq(optarg, "yes");
|
||||
mod_config()->conn.downstream.ipv6 = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: {
|
||||
auto &proxy = mod_config()->downstream_http_proxy;
|
||||
// Reset here so that multiple option occurrence does not merge
|
||||
// the results.
|
||||
proxy = {};
|
||||
// parse URI and get hostname, port and optionally userinfo.
|
||||
http_parser_url u{};
|
||||
int rv = http_parser_parse_url(optarg, strlen(optarg), 0, &u);
|
||||
|
@ -1669,19 +1787,17 @@ int parse_config(const char *opt, const char *optarg,
|
|||
// Surprisingly, u.field_set & UF_USERINFO is nonzero even if
|
||||
// userinfo component is empty string.
|
||||
if (!val.empty()) {
|
||||
val = util::percent_decode(std::begin(val), std::end(val));
|
||||
mod_config()->downstream_http_proxy_userinfo = strcopy(val);
|
||||
proxy.userinfo = util::percent_decode(std::begin(val), std::end(val));
|
||||
}
|
||||
}
|
||||
if (u.field_set & UF_HOST) {
|
||||
http2::copy_url_component(val, &u, UF_HOST, optarg);
|
||||
mod_config()->downstream_http_proxy_host = strcopy(val);
|
||||
http2::copy_url_component(proxy.host, &u, UF_HOST, optarg);
|
||||
} else {
|
||||
LOG(ERROR) << opt << ": no hostname specified";
|
||||
return -1;
|
||||
}
|
||||
if (u.field_set & UF_PORT) {
|
||||
mod_config()->downstream_http_proxy_port = u.port;
|
||||
proxy.port = u.port;
|
||||
} else {
|
||||
LOG(ERROR) << opt << ": no port specified";
|
||||
return -1;
|
||||
|
@ -1694,63 +1810,70 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_READ_RATE:
|
||||
return parse_uint_with_unit(&mod_config()->read_rate, opt, optarg);
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->conn.upstream.ratelimit.read.rate, opt, optarg);
|
||||
case SHRPX_OPTID_READ_BURST:
|
||||
return parse_uint_with_unit(&mod_config()->read_burst, opt, optarg);
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->conn.upstream.ratelimit.read.burst, opt, optarg);
|
||||
case SHRPX_OPTID_WRITE_RATE:
|
||||
return parse_uint_with_unit(&mod_config()->write_rate, opt, optarg);
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->conn.upstream.ratelimit.write.rate, opt, optarg);
|
||||
case SHRPX_OPTID_WRITE_BURST:
|
||||
return parse_uint_with_unit(&mod_config()->write_burst, opt, optarg);
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->conn.upstream.ratelimit.write.burst, opt, optarg);
|
||||
case SHRPX_OPTID_WORKER_READ_RATE:
|
||||
LOG(WARN) << opt << ": not implemented yet";
|
||||
return parse_uint_with_unit(&mod_config()->worker_read_rate, opt, optarg);
|
||||
return 0;
|
||||
case SHRPX_OPTID_WORKER_READ_BURST:
|
||||
LOG(WARN) << opt << ": not implemented yet";
|
||||
return parse_uint_with_unit(&mod_config()->worker_read_burst, opt, optarg);
|
||||
return 0;
|
||||
case SHRPX_OPTID_WORKER_WRITE_RATE:
|
||||
LOG(WARN) << opt << ": not implemented yet";
|
||||
return parse_uint_with_unit(&mod_config()->worker_write_rate, opt, optarg);
|
||||
return 0;
|
||||
case SHRPX_OPTID_WORKER_WRITE_BURST:
|
||||
LOG(WARN) << opt << ": not implemented yet";
|
||||
return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
|
||||
return 0;
|
||||
case SHRPX_OPTID_NPN_LIST:
|
||||
mod_config()->npn_list = util::parse_config_str_list(optarg);
|
||||
mod_config()->tls.npn_list = util::parse_config_str_list(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_TLS_PROTO_LIST:
|
||||
mod_config()->tls_proto_list = util::parse_config_str_list(optarg);
|
||||
mod_config()->tls.tls_proto_list = util::parse_config_str_list(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_VERIFY_CLIENT:
|
||||
mod_config()->verify_client = util::strieq(optarg, "yes");
|
||||
mod_config()->tls.client_verify.enabled = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_VERIFY_CLIENT_CACERT:
|
||||
mod_config()->verify_client_cacert = strcopy(optarg);
|
||||
mod_config()->tls.client_verify.cacert = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE:
|
||||
mod_config()->client_private_key_file = strcopy(optarg);
|
||||
mod_config()->tls.client.private_key_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_CLIENT_CERT_FILE:
|
||||
mod_config()->client_cert_file = strcopy(optarg);
|
||||
mod_config()->tls.client.cert_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER:
|
||||
mod_config()->http2_upstream_dump_request_header_file = strcopy(optarg);
|
||||
mod_config()->http2.upstream.debug.dump.request_header_file =
|
||||
strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER:
|
||||
mod_config()->http2_upstream_dump_response_header_file = strcopy(optarg);
|
||||
mod_config()->http2.upstream.debug.dump.response_header_file =
|
||||
strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING:
|
||||
mod_config()->http2_no_cookie_crumbling = util::strieq(optarg, "yes");
|
||||
mod_config()->http2.no_cookie_crumbling = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FRONTEND_FRAME_DEBUG:
|
||||
mod_config()->upstream_frame_debug = util::strieq(optarg, "yes");
|
||||
mod_config()->http2.upstream.debug.frame_debug =
|
||||
util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_PADDING:
|
||||
|
@ -1797,7 +1920,7 @@ int parse_config(const char *opt, const char *optarg,
|
|||
}
|
||||
}
|
||||
|
||||
mod_config()->altsvcs.push_back(std::move(altsvc));
|
||||
mod_config()->http.altsvcs.push_back(std::move(altsvc));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1809,16 +1932,17 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) {
|
||||
mod_config()->add_request_headers.push_back(std::move(p));
|
||||
mod_config()->http.add_request_headers.push_back(std::move(p));
|
||||
} else {
|
||||
mod_config()->add_response_headers.push_back(std::move(p));
|
||||
mod_config()->http.add_response_headers.push_back(std::move(p));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS:
|
||||
return parse_uint(&mod_config()->worker_frontend_connections, opt, optarg);
|
||||
return parse_uint(&mod_config()->conn.upstream.worker_connections, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_NO_LOCATION_REWRITE:
|
||||
mod_config()->no_location_rewrite = util::strieq(optarg, "yes");
|
||||
mod_config()->http.no_location_rewrite = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_NO_HOST_REWRITE:
|
||||
|
@ -1841,17 +1965,18 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->downstream_connections_per_host = n;
|
||||
mod_config()->conn.downstream.connections_per_host = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
|
||||
return parse_uint(&mod_config()->downstream_connections_per_frontend, opt,
|
||||
optarg);
|
||||
return parse_uint(&mod_config()->conn.downstream.connections_per_frontend,
|
||||
opt, optarg);
|
||||
case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->listener_disable_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->conn.listener.timeout.sleep, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_FILE:
|
||||
mod_config()->tls_ticket_key_files.push_back(optarg);
|
||||
mod_config()->tls.ticket.files.push_back(optarg);
|
||||
return 0;
|
||||
case SHRPX_OPTID_RLIMIT_NOFILE: {
|
||||
int n;
|
||||
|
@ -1884,36 +2009,50 @@ int parse_config(const char *opt, const char *optarg,
|
|||
}
|
||||
|
||||
if (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) {
|
||||
mod_config()->downstream_request_buffer_size = n;
|
||||
mod_config()->conn.downstream.request_buffer_size = n;
|
||||
} else {
|
||||
mod_config()->downstream_response_buffer_size = n;
|
||||
mod_config()->conn.downstream.response_buffer_size = n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case SHRPX_OPTID_NO_SERVER_PUSH:
|
||||
mod_config()->no_server_push = util::strieq(optarg, "yes");
|
||||
mod_config()->http2.no_server_push = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
|
||||
return parse_uint(&mod_config()->http2_downstream_connections_per_worker,
|
||||
return parse_uint(&mod_config()->http2.downstream.connections_per_worker,
|
||||
opt, optarg);
|
||||
case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
|
||||
mod_config()->fetch_ocsp_response_file = strcopy(optarg);
|
||||
mod_config()->tls.ocsp.fetch_ocsp_response_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_OCSP_UPDATE_INTERVAL:
|
||||
return parse_duration(&mod_config()->ocsp_update_interval, opt, optarg);
|
||||
return parse_duration(&mod_config()->tls.ocsp.update_interval, opt, optarg);
|
||||
case SHRPX_OPTID_NO_OCSP:
|
||||
mod_config()->no_ocsp = util::strieq(optarg, "yes");
|
||||
mod_config()->tls.ocsp.disabled = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_HEADER_FIELD_BUFFER:
|
||||
return parse_uint_with_unit(&mod_config()->header_field_buffer, opt,
|
||||
optarg);
|
||||
LOG(WARN) << opt
|
||||
<< ": deprecated. Use request-header-field-buffer instead.";
|
||||
// fall through
|
||||
case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER:
|
||||
return parse_uint_with_unit(&mod_config()->http.request_header_field_buffer,
|
||||
opt, optarg);
|
||||
case SHRPX_OPTID_MAX_HEADER_FIELDS:
|
||||
return parse_uint(&mod_config()->max_header_fields, opt, optarg);
|
||||
LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead.";
|
||||
// fall through
|
||||
case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS:
|
||||
return parse_uint(&mod_config()->http.max_request_header_fields, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER:
|
||||
return parse_uint_with_unit(
|
||||
&mod_config()->http.response_header_field_buffer, opt, optarg);
|
||||
case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS:
|
||||
return parse_uint(&mod_config()->http.max_response_header_fields, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_INCLUDE: {
|
||||
if (included_set.count(optarg)) {
|
||||
LOG(ERROR) << opt << ": " << optarg << " has already been included";
|
||||
|
@ -1932,19 +2071,19 @@ int parse_config(const char *opt, const char *optarg,
|
|||
}
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER:
|
||||
if (util::strieq(optarg, "aes-128-cbc")) {
|
||||
mod_config()->tls_ticket_key_cipher = EVP_aes_128_cbc();
|
||||
mod_config()->tls.ticket.cipher = EVP_aes_128_cbc();
|
||||
} else if (util::strieq(optarg, "aes-256-cbc")) {
|
||||
mod_config()->tls_ticket_key_cipher = EVP_aes_256_cbc();
|
||||
mod_config()->tls.ticket.cipher = EVP_aes_256_cbc();
|
||||
} else {
|
||||
LOG(ERROR) << opt
|
||||
<< ": unsupported cipher for ticket encryption: " << optarg;
|
||||
return -1;
|
||||
}
|
||||
mod_config()->tls_ticket_key_cipher_given = true;
|
||||
mod_config()->tls.ticket.cipher_given = true;
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_HOST_REWRITE:
|
||||
mod_config()->no_host_rewrite = !util::strieq(optarg, "yes");
|
||||
mod_config()->http.no_host_rewrite = !util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: {
|
||||
|
@ -1953,8 +2092,9 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->session_cache_memcached_host = strcopy(host);
|
||||
mod_config()->session_cache_memcached_port = port;
|
||||
auto &memcachedconf = mod_config()->tls.session_cache.memcached;
|
||||
memcachedconf.host = strcopy(host);
|
||||
memcachedconf.port = port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1964,13 +2104,14 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->tls_ticket_key_memcached_host = strcopy(host);
|
||||
mod_config()->tls_ticket_key_memcached_port = port;
|
||||
auto &memcachedconf = mod_config()->tls.ticket.memcached;
|
||||
memcachedconf.host = strcopy(host);
|
||||
memcachedconf.port = port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL:
|
||||
return parse_duration(&mod_config()->tls_ticket_key_memcached_interval, opt,
|
||||
return parse_duration(&mod_config()->tls.ticket.memcached.interval, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: {
|
||||
int n;
|
||||
|
@ -1983,11 +2124,11 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->tls_ticket_key_memcached_max_retry = n;
|
||||
mod_config()->tls.ticket.memcached.max_retry = n;
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL:
|
||||
return parse_uint(&mod_config()->tls_ticket_key_memcached_max_fail, opt,
|
||||
return parse_uint(&mod_config()->tls.ticket.memcached.max_fail, opt,
|
||||
optarg);
|
||||
case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: {
|
||||
size_t n;
|
||||
|
@ -1995,13 +2136,13 @@ int parse_config(const char *opt, const char *optarg,
|
|||
return -1;
|
||||
}
|
||||
|
||||
mod_config()->tls_dyn_rec_warmup_threshold = n;
|
||||
mod_config()->tls.dyn_rec.warmup_threshold = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT:
|
||||
return parse_duration(&mod_config()->tls_dyn_rec_idle_timeout, opt, optarg);
|
||||
return parse_duration(&mod_config()->tls.dyn_rec.idle_timeout, opt, optarg);
|
||||
|
||||
case SHRPX_OPTID_MRUBY_FILE:
|
||||
#ifdef HAVE_MRUBY
|
||||
|
@ -2012,9 +2153,82 @@ int parse_config(const char *opt, const char *optarg,
|
|||
#endif // !HAVE_MRUBY
|
||||
return 0;
|
||||
case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL:
|
||||
mod_config()->accept_proxy_protocol = util::strieq(optarg, "yes");
|
||||
mod_config()->conn.upstream.accept_proxy_protocol =
|
||||
util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_ADD_FORWARDED: {
|
||||
auto &fwdconf = mod_config()->http.forwarded;
|
||||
fwdconf.params = FORWARDED_NONE;
|
||||
for (const auto ¶m : util::parse_config_str_list(optarg)) {
|
||||
if (util::strieq(param, "by")) {
|
||||
fwdconf.params |= FORWARDED_BY;
|
||||
continue;
|
||||
}
|
||||
if (util::strieq(param, "for")) {
|
||||
fwdconf.params |= FORWARDED_FOR;
|
||||
continue;
|
||||
}
|
||||
if (util::strieq(param, "host")) {
|
||||
fwdconf.params |= FORWARDED_HOST;
|
||||
continue;
|
||||
}
|
||||
if (util::strieq(param, "proto")) {
|
||||
fwdconf.params |= FORWARDED_PROTO;
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG(ERROR) << opt << ": unknown parameter " << optarg;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_STRIP_INCOMING_FORWARDED:
|
||||
mod_config()->http.forwarded.strip_incoming = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_FORWARDED_BY:
|
||||
case SHRPX_OPTID_FORWARDED_FOR: {
|
||||
auto type = parse_forwarded_node_type(optarg);
|
||||
|
||||
if (type == -1 ||
|
||||
(optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) {
|
||||
LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string "
|
||||
<< optarg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto &fwdconf = mod_config()->http.forwarded;
|
||||
|
||||
switch (optid) {
|
||||
case SHRPX_OPTID_FORWARDED_BY:
|
||||
fwdconf.by_node_type = static_cast<shrpx_forwarded_node_type>(type);
|
||||
if (optarg[0] == '_') {
|
||||
fwdconf.by_obfuscated = optarg;
|
||||
} else {
|
||||
fwdconf.by_obfuscated = "";
|
||||
}
|
||||
break;
|
||||
case SHRPX_OPTID_FORWARDED_FOR:
|
||||
fwdconf.for_node_type = static_cast<shrpx_forwarded_node_type>(type);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
|
||||
mod_config()->tls.no_http2_cipher_black_list = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
case SHRPX_OPTID_BACKEND_HTTP1_TLS:
|
||||
mod_config()->conn.downstream.http1_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_CONF:
|
||||
LOG(WARN) << "conf: ignored";
|
||||
|
||||
|
|
|
@ -191,6 +191,26 @@ constexpr char SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD[] =
|
|||
"tls-dyn-rec-warmup-threshold";
|
||||
constexpr char SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT[] =
|
||||
"tls-dyn-rec-idle-timeout";
|
||||
constexpr char SHRPX_OPT_ADD_FORWARDED[] = "add-forwarded";
|
||||
constexpr char SHRPX_OPT_STRIP_INCOMING_FORWARDED[] =
|
||||
"strip-incoming-forwarded";
|
||||
constexpr static char SHRPX_OPT_FORWARDED_BY[] = "forwarded-by";
|
||||
constexpr char SHRPX_OPT_FORWARDED_FOR[] = "forwarded-for";
|
||||
constexpr char SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER[] =
|
||||
"request-header-field-buffer";
|
||||
constexpr char SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS[] =
|
||||
"max-request-header-fields";
|
||||
constexpr char SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER[] =
|
||||
"response-header-field-buffer";
|
||||
constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] =
|
||||
"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 size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
|
||||
|
||||
union sockaddr_union {
|
||||
sockaddr_storage storage;
|
||||
|
@ -207,6 +227,23 @@ struct Address {
|
|||
|
||||
enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP };
|
||||
|
||||
enum shrpx_forwarded_param {
|
||||
FORWARDED_NONE = 0,
|
||||
FORWARDED_BY = 0x1,
|
||||
FORWARDED_FOR = 0x2,
|
||||
FORWARDED_HOST = 0x4,
|
||||
FORWARDED_PROTO = 0x8,
|
||||
};
|
||||
|
||||
enum shrpx_forwarded_node_type {
|
||||
FORWARDED_NODE_OBFUSCATED,
|
||||
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) {}
|
||||
|
||||
|
@ -215,6 +252,24 @@ struct AltSvc {
|
|||
uint16_t port;
|
||||
};
|
||||
|
||||
struct UpstreamAddr {
|
||||
// The frontend address (e.g., FQDN, hostname, IP address). If
|
||||
// |host_unix| is true, this is UNIX domain socket path.
|
||||
ImmutableString host;
|
||||
// For TCP socket, this is <IP address>:<PORT>. For IPv6 address,
|
||||
// address is surrounded by square brackets. If socket is UNIX
|
||||
// domain socket, this is "localhost".
|
||||
ImmutableString hostport;
|
||||
// frontend port. 0 if |host_unix| is true.
|
||||
uint16_t port;
|
||||
// For TCP socket, this is either AF_INET or AF_INET6. For UNIX
|
||||
// domain socket, this is 0.
|
||||
int family;
|
||||
// true if |host| contains UNIX domain socket path.
|
||||
bool host_unix;
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct DownstreamAddr {
|
||||
DownstreamAddr() : addr{}, port(0), host_unix(false) {}
|
||||
DownstreamAddr(const DownstreamAddr &other);
|
||||
|
@ -225,8 +280,8 @@ struct DownstreamAddr {
|
|||
Address addr;
|
||||
// backend address. If |host_unix| is true, this is UNIX domain
|
||||
// socket path.
|
||||
std::unique_ptr<char[]> host;
|
||||
std::unique_ptr<char[]> hostport;
|
||||
ImmutableString host;
|
||||
ImmutableString hostport;
|
||||
// backend port. 0 if |host_unix| is true.
|
||||
uint16_t port;
|
||||
// true if |host| contains UNIX domain socket path.
|
||||
|
@ -263,168 +318,265 @@ struct TicketKeys {
|
|||
std::vector<TicketKey> keys;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
struct HttpProxy {
|
||||
Address addr;
|
||||
// host in http proxy URI
|
||||
std::string host;
|
||||
// userinfo in http proxy URI, not percent-encoded form
|
||||
std::string userinfo;
|
||||
// port in http proxy URI
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
struct TLSConfig {
|
||||
// RFC 5077 Session ticket related configurations
|
||||
struct {
|
||||
struct {
|
||||
Address addr;
|
||||
uint16_t port;
|
||||
std::unique_ptr<char[]> host;
|
||||
ev_tstamp interval;
|
||||
// Maximum number of retries when getting TLS ticket key from
|
||||
// mamcached, due to network error.
|
||||
size_t max_retry;
|
||||
// Maximum number of consecutive error from memcached, when this
|
||||
// limit reached, TLS ticket is disabled.
|
||||
size_t max_fail;
|
||||
} memcached;
|
||||
std::vector<std::string> files;
|
||||
const EVP_CIPHER *cipher;
|
||||
// true if --tls-ticket-key-cipher is used
|
||||
bool cipher_given;
|
||||
} ticket;
|
||||
|
||||
// Session cache related configurations
|
||||
struct {
|
||||
struct {
|
||||
Address addr;
|
||||
uint16_t port;
|
||||
std::unique_ptr<char[]> host;
|
||||
} memcached;
|
||||
} session_cache;
|
||||
|
||||
// Dynamic record sizing configurations
|
||||
struct {
|
||||
size_t warmup_threshold;
|
||||
ev_tstamp idle_timeout;
|
||||
} dyn_rec;
|
||||
|
||||
// OCSP realted configurations
|
||||
struct {
|
||||
ev_tstamp update_interval;
|
||||
std::unique_ptr<char[]> fetch_ocsp_response_file;
|
||||
bool disabled;
|
||||
} ocsp;
|
||||
|
||||
// Client verification configurations
|
||||
struct {
|
||||
// Path to file containing CA certificate solely used for client
|
||||
// certificate validation
|
||||
std::unique_ptr<char[]> 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;
|
||||
} client;
|
||||
|
||||
// The list of (private key file, certificate file) pair
|
||||
std::vector<std::pair<std::string, std::string>> subcerts;
|
||||
std::vector<AltSvc> altsvcs;
|
||||
std::vector<std::pair<std::string, std::string>> add_request_headers;
|
||||
std::vector<std::pair<std::string, std::string>> add_response_headers;
|
||||
std::vector<unsigned char> alpn_prefs;
|
||||
std::vector<LogFragment> accesslog_format;
|
||||
std::vector<DownstreamAddrGroup> downstream_addr_groups;
|
||||
std::vector<std::string> tls_ticket_key_files;
|
||||
// list of supported NPN/ALPN protocol strings in the order of
|
||||
// preference.
|
||||
std::vector<std::string> npn_list;
|
||||
// list of supported SSL/TLS protocol strings.
|
||||
std::vector<std::string> tls_proto_list;
|
||||
// binary form of http proxy host and port
|
||||
Address downstream_http_proxy_addr;
|
||||
Address session_cache_memcached_addr;
|
||||
Address tls_ticket_key_memcached_addr;
|
||||
Router router;
|
||||
std::chrono::seconds tls_session_timeout;
|
||||
ev_tstamp http2_upstream_read_timeout;
|
||||
ev_tstamp upstream_read_timeout;
|
||||
ev_tstamp upstream_write_timeout;
|
||||
ev_tstamp downstream_read_timeout;
|
||||
ev_tstamp downstream_write_timeout;
|
||||
ev_tstamp stream_read_timeout;
|
||||
ev_tstamp stream_write_timeout;
|
||||
ev_tstamp downstream_idle_read_timeout;
|
||||
ev_tstamp listener_disable_timeout;
|
||||
ev_tstamp ocsp_update_interval;
|
||||
ev_tstamp tls_ticket_key_memcached_interval;
|
||||
// address of frontend connection. This could be a path to UNIX
|
||||
// domain socket. In this case, |host_unix| must be true.
|
||||
std::unique_ptr<char[]> host;
|
||||
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[]> backend_tls_sni_name;
|
||||
std::unique_ptr<char[]> pid_file;
|
||||
std::unique_ptr<char[]> conf_path;
|
||||
std::unique_ptr<char[]> ciphers;
|
||||
std::unique_ptr<char[]> cacert;
|
||||
// userinfo in http proxy URI, not percent-encoded form
|
||||
std::unique_ptr<char[]> downstream_http_proxy_userinfo;
|
||||
// host in http proxy URI
|
||||
std::unique_ptr<char[]> downstream_http_proxy_host;
|
||||
std::unique_ptr<char[]> http2_upstream_dump_request_header_file;
|
||||
std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
|
||||
// // Rate limit configuration per connection
|
||||
// ev_token_bucket_cfg *rate_limit_cfg;
|
||||
// // Rate limit configuration per worker (thread)
|
||||
// ev_token_bucket_cfg *worker_rate_limit_cfg;
|
||||
// Path to file containing CA certificate solely used for client
|
||||
// certificate validation
|
||||
std::unique_ptr<char[]> verify_client_cacert;
|
||||
std::unique_ptr<char[]> client_private_key_file;
|
||||
std::unique_ptr<char[]> client_cert_file;
|
||||
std::unique_ptr<char[]> accesslog_file;
|
||||
std::unique_ptr<char[]> errorlog_file;
|
||||
std::unique_ptr<char[]> fetch_ocsp_response_file;
|
||||
bool insecure;
|
||||
bool no_http2_cipher_black_list;
|
||||
};
|
||||
|
||||
struct HttpConfig {
|
||||
struct {
|
||||
// obfuscated value used in "by" parameter of Forwarded header
|
||||
// field. This is only used when user defined static obfuscated
|
||||
// string is provided.
|
||||
std::string by_obfuscated;
|
||||
// bitwise-OR of one or more of shrpx_forwarded_param values.
|
||||
uint32_t params;
|
||||
// type of value recorded in "by" parameter of Forwarded header
|
||||
// field.
|
||||
shrpx_forwarded_node_type by_node_type;
|
||||
// type of value recorded in "for" parameter of Forwarded header
|
||||
// field.
|
||||
shrpx_forwarded_node_type for_node_type;
|
||||
bool strip_incoming;
|
||||
} forwarded;
|
||||
struct {
|
||||
bool add;
|
||||
bool strip_incoming;
|
||||
} xff;
|
||||
std::vector<AltSvc> altsvcs;
|
||||
std::vector<std::pair<std::string, std::string>> add_request_headers;
|
||||
std::vector<std::pair<std::string, std::string>> add_response_headers;
|
||||
StringRef server_name;
|
||||
size_t request_header_field_buffer;
|
||||
size_t max_request_header_fields;
|
||||
size_t response_header_field_buffer;
|
||||
size_t max_response_header_fields;
|
||||
bool no_via;
|
||||
bool no_location_rewrite;
|
||||
bool no_host_rewrite;
|
||||
};
|
||||
|
||||
struct Http2Config {
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
std::unique_ptr<char[]> request_header_file;
|
||||
std::unique_ptr<char[]> response_header_file;
|
||||
FILE *request_header;
|
||||
FILE *response_header;
|
||||
} dump;
|
||||
bool frame_debug;
|
||||
} debug;
|
||||
nghttp2_option *option;
|
||||
nghttp2_session_callbacks *callbacks;
|
||||
size_t window_bits;
|
||||
size_t connection_window_bits;
|
||||
} upstream;
|
||||
struct {
|
||||
nghttp2_option *option;
|
||||
nghttp2_session_callbacks *callbacks;
|
||||
size_t window_bits;
|
||||
size_t connection_window_bits;
|
||||
size_t connections_per_worker;
|
||||
} downstream;
|
||||
struct {
|
||||
ev_tstamp stream_read;
|
||||
ev_tstamp stream_write;
|
||||
} timeout;
|
||||
size_t max_concurrent_streams;
|
||||
bool no_cookie_crumbling;
|
||||
bool no_server_push;
|
||||
};
|
||||
|
||||
struct LoggingConfig {
|
||||
struct {
|
||||
std::vector<LogFragment> format;
|
||||
std::unique_ptr<char[]> file;
|
||||
// Send accesslog to syslog, ignoring accesslog_file.
|
||||
bool syslog;
|
||||
} access;
|
||||
struct {
|
||||
std::unique_ptr<char[]> file;
|
||||
// Send errorlog to syslog, ignoring errorlog_file.
|
||||
bool syslog;
|
||||
} error;
|
||||
int syslog_facility;
|
||||
};
|
||||
|
||||
struct RateLimitConfig {
|
||||
size_t rate;
|
||||
size_t burst;
|
||||
};
|
||||
|
||||
struct ConnectionConfig {
|
||||
struct {
|
||||
struct {
|
||||
ev_tstamp sleep;
|
||||
} timeout;
|
||||
// address of frontend acceptors
|
||||
std::vector<UpstreamAddr> addrs;
|
||||
int backlog;
|
||||
// TCP fastopen. If this is positive, it is passed to
|
||||
// setsockopt() along with TCP_FASTOPEN.
|
||||
int fastopen;
|
||||
} listener;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
ev_tstamp http2_read;
|
||||
ev_tstamp read;
|
||||
ev_tstamp write;
|
||||
} timeout;
|
||||
struct {
|
||||
RateLimitConfig read;
|
||||
RateLimitConfig write;
|
||||
} ratelimit;
|
||||
size_t worker_connections;
|
||||
bool no_tls;
|
||||
bool accept_proxy_protocol;
|
||||
} upstream;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
ev_tstamp read;
|
||||
ev_tstamp write;
|
||||
ev_tstamp idle_read;
|
||||
} timeout;
|
||||
std::vector<DownstreamAddrGroup> 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;
|
||||
bool no_tls;
|
||||
bool http1_tls;
|
||||
// true if IPv4 only; ipv4 and ipv6 are mutually exclusive; and
|
||||
// (ipv4 && ipv6) must be false.
|
||||
bool ipv4;
|
||||
// true if IPv6 only
|
||||
bool ipv6;
|
||||
} downstream;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
Router router;
|
||||
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[]> session_cache_memcached_host;
|
||||
std::unique_ptr<char[]> tls_ticket_key_memcached_host;
|
||||
std::unique_ptr<char[]> mruby_file;
|
||||
FILE *http2_upstream_dump_request_header;
|
||||
FILE *http2_upstream_dump_response_header;
|
||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||
nghttp2_session_callbacks *http2_downstream_callbacks;
|
||||
nghttp2_option *http2_option;
|
||||
nghttp2_option *http2_client_option;
|
||||
const EVP_CIPHER *tls_ticket_key_cipher;
|
||||
const char *server_name;
|
||||
char **original_argv;
|
||||
char **argv;
|
||||
char *cwd;
|
||||
size_t num_worker;
|
||||
size_t http2_max_concurrent_streams;
|
||||
size_t http2_upstream_window_bits;
|
||||
size_t http2_downstream_window_bits;
|
||||
size_t http2_upstream_connection_window_bits;
|
||||
size_t http2_downstream_connection_window_bits;
|
||||
size_t http2_downstream_connections_per_worker;
|
||||
size_t downstream_connections_per_host;
|
||||
size_t downstream_connections_per_frontend;
|
||||
size_t read_rate;
|
||||
size_t read_burst;
|
||||
size_t write_rate;
|
||||
size_t write_burst;
|
||||
size_t worker_read_rate;
|
||||
size_t worker_read_burst;
|
||||
size_t worker_write_rate;
|
||||
size_t worker_write_burst;
|
||||
size_t padding;
|
||||
size_t worker_frontend_connections;
|
||||
size_t rlimit_nofile;
|
||||
size_t downstream_request_buffer_size;
|
||||
size_t downstream_response_buffer_size;
|
||||
size_t header_field_buffer;
|
||||
size_t max_header_fields;
|
||||
// The index of catch-all group in downstream_addr_groups.
|
||||
size_t downstream_addr_group_catch_all;
|
||||
// Maximum number of retries when getting TLS ticket key from
|
||||
// mamcached, due to network error.
|
||||
size_t tls_ticket_key_memcached_max_retry;
|
||||
// Maximum number of consecutive error from memcached, when this
|
||||
// limit reached, TLS ticket is disabled.
|
||||
size_t tls_ticket_key_memcached_max_fail;
|
||||
// Bit mask to disable SSL/TLS protocol versions. This will be
|
||||
// passed to SSL_CTX_set_options().
|
||||
long int tls_proto_mask;
|
||||
// downstream protocol; this will be determined by given options.
|
||||
shrpx_proto downstream_proto;
|
||||
int syslog_facility;
|
||||
int backlog;
|
||||
int argc;
|
||||
int fastopen;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
// frontend listening port. 0 if frontend listens on UNIX domain
|
||||
// socket, in this case |host_unix| must be true.
|
||||
uint16_t port;
|
||||
// port in http proxy URI
|
||||
uint16_t downstream_http_proxy_port;
|
||||
uint16_t session_cache_memcached_port;
|
||||
uint16_t tls_ticket_key_memcached_port;
|
||||
bool verbose;
|
||||
bool daemon;
|
||||
bool verify_client;
|
||||
bool http2_proxy;
|
||||
bool http2_bridge;
|
||||
bool client_proxy;
|
||||
bool add_x_forwarded_for;
|
||||
bool strip_incoming_x_forwarded_for;
|
||||
bool no_via;
|
||||
bool upstream_no_tls;
|
||||
bool downstream_no_tls;
|
||||
// Send accesslog to syslog, ignoring accesslog_file.
|
||||
bool accesslog_syslog;
|
||||
// Send errorlog to syslog, ignoring errorlog_file.
|
||||
bool errorlog_syslog;
|
||||
bool client;
|
||||
// true if --client or --client-proxy are enabled.
|
||||
bool client_mode;
|
||||
bool insecure;
|
||||
bool backend_ipv4;
|
||||
bool backend_ipv6;
|
||||
bool http2_no_cookie_crumbling;
|
||||
bool upstream_frame_debug;
|
||||
bool no_location_rewrite;
|
||||
bool no_host_rewrite;
|
||||
bool no_server_push;
|
||||
// true if host contains UNIX domain socket path
|
||||
bool host_unix;
|
||||
bool no_ocsp;
|
||||
// true if --tls-ticket-key-cipher is used
|
||||
bool tls_ticket_key_cipher_given;
|
||||
bool accept_proxy_protocol;
|
||||
size_t tls_dyn_rec_warmup_threshold;
|
||||
ev_tstamp tls_dyn_rec_idle_timeout;
|
||||
};
|
||||
|
||||
const Config *get_config();
|
||||
|
|
|
@ -76,82 +76,82 @@ void test_shrpx_config_parse_log_format(void) {
|
|||
CU_ASSERT(SHRPX_LOGF_REMOTE_ADDR == res[0].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[1].type);
|
||||
CU_ASSERT(0 == strcmp(" - $remote_user [", res[1].value.get()));
|
||||
CU_ASSERT(" - $remote_user [" == res[1].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_TIME_LOCAL == res[2].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[3].type);
|
||||
CU_ASSERT(0 == strcmp("] \"", res[3].value.get()));
|
||||
CU_ASSERT("] \"" == res[3].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_REQUEST == res[4].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[5].type);
|
||||
CU_ASSERT(0 == strcmp("\" ", res[5].value.get()));
|
||||
CU_ASSERT("\" " == res[5].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_STATUS == res[6].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[7].type);
|
||||
CU_ASSERT(0 == strcmp(" ", res[7].value.get()));
|
||||
CU_ASSERT(" " == res[7].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_BODY_BYTES_SENT == res[8].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[9].type);
|
||||
CU_ASSERT(0 == strcmp(" \"", res[9].value.get()));
|
||||
CU_ASSERT(" \"" == res[9].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_HTTP == res[10].type);
|
||||
CU_ASSERT(0 == strcmp("referer", res[10].value.get()));
|
||||
CU_ASSERT("referer" == res[10].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[11].type);
|
||||
CU_ASSERT(0 == strcmp("\" ", res[11].value.get()));
|
||||
CU_ASSERT("\" " == res[11].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_AUTHORITY == res[12].type);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[13].type);
|
||||
CU_ASSERT(0 == strcmp(" \"", res[13].value.get()));
|
||||
CU_ASSERT(" \"" == res[13].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_HTTP == res[14].type);
|
||||
CU_ASSERT(0 == strcmp("user-agent", res[14].value.get()));
|
||||
CU_ASSERT("user-agent" == res[14].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[15].type);
|
||||
CU_ASSERT(0 == strcmp("\"", res[15].value.get()));
|
||||
CU_ASSERT("\"" == res[15].value);
|
||||
|
||||
res = parse_log_format("$");
|
||||
|
||||
CU_ASSERT(1 == res.size());
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
|
||||
CU_ASSERT(0 == strcmp("$", res[0].value.get()));
|
||||
CU_ASSERT("$" == res[0].value);
|
||||
|
||||
res = parse_log_format("${");
|
||||
|
||||
CU_ASSERT(1 == res.size());
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
|
||||
CU_ASSERT(0 == strcmp("${", res[0].value.get()));
|
||||
CU_ASSERT("${" == res[0].value);
|
||||
|
||||
res = parse_log_format("${a");
|
||||
|
||||
CU_ASSERT(1 == res.size());
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
|
||||
CU_ASSERT(0 == strcmp("${a", res[0].value.get()));
|
||||
CU_ASSERT("${a" == res[0].value);
|
||||
|
||||
res = parse_log_format("${a ");
|
||||
|
||||
CU_ASSERT(1 == res.size());
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
|
||||
CU_ASSERT(0 == strcmp("${a ", res[0].value.get()));
|
||||
CU_ASSERT("${a " == res[0].value);
|
||||
|
||||
res = parse_log_format("$$remote_addr");
|
||||
|
||||
CU_ASSERT(2 == res.size());
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_LITERAL == res[0].type);
|
||||
CU_ASSERT(0 == strcmp("$", res[0].value.get()));
|
||||
CU_ASSERT("$" == res[0].value);
|
||||
|
||||
CU_ASSERT(SHRPX_LOGF_REMOTE_ADDR == res[1].type);
|
||||
CU_ASSERT(nullptr == res[1].value.get());
|
||||
CU_ASSERT("" == res[1].value);
|
||||
}
|
||||
|
||||
void test_shrpx_config_read_tls_ticket_key_file(void) {
|
||||
|
|
|
@ -42,15 +42,21 @@ using namespace nghttp2;
|
|||
namespace shrpx {
|
||||
Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
|
||||
MemchunkPool *mcpool, ev_tstamp write_timeout,
|
||||
ev_tstamp read_timeout, size_t write_rate,
|
||||
size_t write_burst, size_t read_rate, size_t read_burst,
|
||||
IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data,
|
||||
ev_tstamp read_timeout,
|
||||
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)
|
||||
: tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool)},
|
||||
wlimit(loop, &wev, write_rate, write_burst),
|
||||
rlimit(loop, &rev, read_rate, read_burst, this), writecb(writecb),
|
||||
readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd),
|
||||
wlimit(loop, &wev, write_limit.rate, write_limit.burst),
|
||||
rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
|
||||
writecb(writecb),
|
||||
readcb(readcb),
|
||||
timeoutcb(timeoutcb),
|
||||
loop(loop),
|
||||
data(data),
|
||||
fd(fd),
|
||||
tls_dyn_rec_warmup_threshold(tls_dyn_rec_warmup_threshold),
|
||||
tls_dyn_rec_idle_timeout(tls_dyn_rec_idle_timeout) {
|
||||
|
||||
|
@ -482,10 +488,17 @@ int Connection::check_http2_requirement() {
|
|||
!util::check_h2_is_selected(next_proto, next_proto_len)) {
|
||||
return 0;
|
||||
}
|
||||
if (!nghttp2::ssl::check_http2_requirement(tls.ssl)) {
|
||||
if (!nghttp2::ssl::check_http2_tls_version(tls.ssl)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "TLSv1.2 and/or black listed cipher suite was negotiated. "
|
||||
"HTTP/2 must not be used.";
|
||||
LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used.";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!get_config()->tls.no_http2_cipher_black_list &&
|
||||
nghttp2::ssl::check_http2_cipher_black_list(tls.ssl)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite "
|
||||
"black list. HTTP/2 must not be used.";
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -73,10 +73,10 @@ using TimerCb = EVCb<ev_timer>;
|
|||
|
||||
struct Connection {
|
||||
Connection(struct ev_loop *loop, int fd, SSL *ssl, MemchunkPool *mcpool,
|
||||
ev_tstamp write_timeout, ev_tstamp read_timeout, size_t write_rate,
|
||||
size_t write_burst, size_t read_rate, size_t read_burst,
|
||||
IOCb writecb, IOCb readcb, TimerCb timeoutcb, void *data,
|
||||
size_t tls_dyn_rec_warmup_threshold,
|
||||
ev_tstamp write_timeout, ev_tstamp read_timeout,
|
||||
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);
|
||||
~Connection();
|
||||
|
||||
|
|
|
@ -102,10 +102,17 @@ void thread_join_async_cb(struct ev_loop *loop, ev_async *w, int revent) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::random_device rd;
|
||||
} // namespace
|
||||
|
||||
ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
|
||||
: single_worker_(nullptr), loop_(loop),
|
||||
: gen_(rd()),
|
||||
single_worker_(nullptr),
|
||||
loop_(loop),
|
||||
tls_ticket_key_memcached_get_retry_count_(0),
|
||||
tls_ticket_key_memcached_fail_count_(0), worker_round_robin_cnt_(0),
|
||||
tls_ticket_key_memcached_fail_count_(0),
|
||||
worker_round_robin_cnt_(0),
|
||||
graceful_shutdown_(false) {
|
||||
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||
disable_acceptor_timer_.data = this;
|
||||
|
@ -290,19 +297,20 @@ void ConnectionHandler::graceful_shutdown_worker() {
|
|||
#endif // NOTHREADS
|
||||
}
|
||||
|
||||
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
||||
int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen,
|
||||
const UpstreamAddr *faddr) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Accepted connection. fd=" << fd;
|
||||
}
|
||||
|
||||
if (get_config()->num_worker == 1) {
|
||||
|
||||
auto &upstreamconf = get_config()->conn.upstream;
|
||||
if (single_worker_->get_worker_stat()->num_connections >=
|
||||
get_config()->worker_frontend_connections) {
|
||||
upstreamconf.worker_connections) {
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LLOG(INFO, this) << "Too many connections >="
|
||||
<< get_config()->worker_frontend_connections;
|
||||
<< upstreamconf.worker_connections;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
@ -310,7 +318,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
|||
}
|
||||
|
||||
auto client =
|
||||
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen);
|
||||
ssl::accept_connection(single_worker_.get(), fd, addr, addrlen, faddr);
|
||||
if (!client) {
|
||||
LLOG(ERROR, this) << "ClientHandler creation failed";
|
||||
|
||||
|
@ -331,6 +339,7 @@ int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
|
|||
wev.client_fd = fd;
|
||||
memcpy(&wev.client_addr, addr, addrlen);
|
||||
wev.client_addrlen = addrlen;
|
||||
wev.faddr = faddr;
|
||||
|
||||
workers_[idx]->send(wev);
|
||||
|
||||
|
@ -345,43 +354,23 @@ Worker *ConnectionHandler::get_single_worker() const {
|
|||
return single_worker_.get();
|
||||
}
|
||||
|
||||
void ConnectionHandler::set_acceptor(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptor_ = std::move(h);
|
||||
}
|
||||
|
||||
AcceptHandler *ConnectionHandler::get_acceptor() const {
|
||||
return acceptor_.get();
|
||||
}
|
||||
|
||||
void ConnectionHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptor6_ = std::move(h);
|
||||
}
|
||||
|
||||
AcceptHandler *ConnectionHandler::get_acceptor6() const {
|
||||
return acceptor6_.get();
|
||||
void ConnectionHandler::add_acceptor(std::unique_ptr<AcceptHandler> h) {
|
||||
acceptors_.push_back(std::move(h));
|
||||
}
|
||||
|
||||
void ConnectionHandler::enable_acceptor() {
|
||||
if (acceptor_) {
|
||||
acceptor_->enable();
|
||||
}
|
||||
|
||||
if (acceptor6_) {
|
||||
acceptor6_->enable();
|
||||
for (auto &a : acceptors_) {
|
||||
a->enable();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionHandler::disable_acceptor() {
|
||||
if (acceptor_) {
|
||||
acceptor_->disable();
|
||||
}
|
||||
|
||||
if (acceptor6_) {
|
||||
acceptor6_->disable();
|
||||
for (auto &a : acceptors_) {
|
||||
a->disable();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionHandler::disable_acceptor_temporary(ev_tstamp t) {
|
||||
void ConnectionHandler::sleep_acceptor(ev_tstamp t) {
|
||||
if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
|
||||
return;
|
||||
}
|
||||
|
@ -393,11 +382,8 @@ void ConnectionHandler::disable_acceptor_temporary(ev_tstamp t) {
|
|||
}
|
||||
|
||||
void ConnectionHandler::accept_pending_connection() {
|
||||
if (acceptor_) {
|
||||
acceptor_->accept_connection();
|
||||
}
|
||||
if (acceptor6_) {
|
||||
acceptor6_->accept_connection();
|
||||
for (auto &a : acceptors_) {
|
||||
a->accept_connection();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,7 +432,7 @@ int ConnectionHandler::start_ocsp_update(const char *cert_file) {
|
|||
assert(!ev_is_active(&ocsp_.chldev));
|
||||
|
||||
char *const argv[] = {
|
||||
const_cast<char *>(get_config()->fetch_ocsp_response_file.get()),
|
||||
const_cast<char *>(get_config()->tls.ocsp.fetch_ocsp_response_file.get()),
|
||||
const_cast<char *>(cert_file), nullptr};
|
||||
char *const envp[] = {nullptr};
|
||||
|
||||
|
@ -630,7 +616,7 @@ void ConnectionHandler::proceed_next_cert_ocsp() {
|
|||
if (ocsp_.next == all_ssl_ctx_.size()) {
|
||||
ocsp_.next = 0;
|
||||
// We have updated all ocsp response, and schedule next update.
|
||||
ev_timer_set(&ocsp_timer_, get_config()->ocsp_update_interval, 0.);
|
||||
ev_timer_set(&ocsp_timer_, get_config()->tls.ocsp.update_interval, 0.);
|
||||
ev_timer_start(loop_, &ocsp_timer_);
|
||||
return;
|
||||
}
|
||||
|
@ -667,13 +653,9 @@ ConnectionHandler::get_tls_ticket_key_memcached_dispatcher() const {
|
|||
return tls_ticket_key_memcached_dispatcher_.get();
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::random_device rd;
|
||||
} // namespace
|
||||
|
||||
void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
|
||||
if (++tls_ticket_key_memcached_get_retry_count_ >=
|
||||
get_config()->tls_ticket_key_memcached_max_retry) {
|
||||
get_config()->tls.ticket.memcached.max_retry) {
|
||||
LOG(WARN) << "Memcached: tls ticket get retry all failed "
|
||||
<< tls_ticket_key_memcached_get_retry_count_ << " times.";
|
||||
|
||||
|
@ -683,7 +665,7 @@ void ConnectionHandler::on_tls_ticket_key_network_error(ev_timer *w) {
|
|||
|
||||
auto dist = std::uniform_int_distribution<int>(
|
||||
1, std::min(60, 1 << tls_ticket_key_memcached_get_retry_count_));
|
||||
auto t = dist(rd);
|
||||
auto t = dist(gen_);
|
||||
|
||||
LOG(WARN)
|
||||
<< "Memcached: tls ticket get failed due to network error, retrying in "
|
||||
|
@ -697,7 +679,7 @@ void ConnectionHandler::on_tls_ticket_key_not_found(ev_timer *w) {
|
|||
tls_ticket_key_memcached_get_retry_count_ = 0;
|
||||
|
||||
if (++tls_ticket_key_memcached_fail_count_ >=
|
||||
get_config()->tls_ticket_key_memcached_max_fail) {
|
||||
get_config()->tls.ticket.memcached.max_fail) {
|
||||
LOG(WARN) << "Memcached: could not get tls ticket; disable tls ticket";
|
||||
|
||||
tls_ticket_key_memcached_fail_count_ = 0;
|
||||
|
@ -742,7 +724,7 @@ void ConnectionHandler::on_tls_ticket_key_get_success(
|
|||
|
||||
void ConnectionHandler::schedule_next_tls_ticket_key_memcached_get(
|
||||
ev_timer *w) {
|
||||
ev_timer_set(w, get_config()->tls_ticket_key_memcached_interval, 0.);
|
||||
ev_timer_set(w, get_config()->tls.ticket.memcached.interval, 0.);
|
||||
ev_timer_start(loop_, w);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // NOTHREADS
|
||||
|
@ -57,6 +58,7 @@ class Worker;
|
|||
struct WorkerStat;
|
||||
struct TicketKeys;
|
||||
class MemcachedDispatcher;
|
||||
struct UpstreamAddr;
|
||||
|
||||
struct OCSPUpdateContext {
|
||||
// ocsp response buffer
|
||||
|
@ -78,7 +80,8 @@ class ConnectionHandler {
|
|||
public:
|
||||
ConnectionHandler(struct ev_loop *loop);
|
||||
~ConnectionHandler();
|
||||
int handle_connection(int fd, sockaddr *addr, int addrlen);
|
||||
int handle_connection(int fd, sockaddr *addr, int addrlen,
|
||||
const UpstreamAddr *faddr);
|
||||
// Creates Worker object for single threaded configuration.
|
||||
int create_single_worker();
|
||||
// Creates |num| Worker objects for multi threaded configuration.
|
||||
|
@ -91,13 +94,10 @@ public:
|
|||
const std::shared_ptr<TicketKeys> &get_ticket_keys() const;
|
||||
struct ev_loop *get_loop() const;
|
||||
Worker *get_single_worker() const;
|
||||
void set_acceptor(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor() const;
|
||||
void set_acceptor6(std::unique_ptr<AcceptHandler> h);
|
||||
AcceptHandler *get_acceptor6() const;
|
||||
void add_acceptor(std::unique_ptr<AcceptHandler> h);
|
||||
void enable_acceptor();
|
||||
void disable_acceptor();
|
||||
void disable_acceptor_temporary(ev_tstamp t);
|
||||
void sleep_acceptor(ev_tstamp t);
|
||||
void accept_pending_connection();
|
||||
void graceful_shutdown_worker();
|
||||
void set_graceful_shutdown(bool f);
|
||||
|
@ -139,6 +139,7 @@ private:
|
|||
// Stores all SSL_CTX objects.
|
||||
std::vector<SSL_CTX *> all_ssl_ctx_;
|
||||
OCSPUpdateContext ocsp_;
|
||||
std::mt19937 gen_;
|
||||
// ev_loop for each worker
|
||||
std::vector<struct ev_loop *> worker_loops_;
|
||||
// Worker instances when multi threaded mode (-nN, N >= 2) is used.
|
||||
|
@ -152,10 +153,7 @@ private:
|
|||
// Worker object.
|
||||
std::shared_ptr<TicketKeys> ticket_keys_;
|
||||
struct ev_loop *loop_;
|
||||
// acceptor for IPv4 address or UNIX domain socket.
|
||||
std::unique_ptr<AcceptHandler> acceptor_;
|
||||
// acceptor for IPv6 address
|
||||
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||
std::vector<std::unique_ptr<AcceptHandler>> acceptors_;
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
std::unique_ptr<neverbleed_t> nb_;
|
||||
#endif // HAVE_NEVERBLEED
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -49,17 +49,174 @@ class Upstream;
|
|||
class DownstreamConnection;
|
||||
struct BlockedLink;
|
||||
|
||||
class FieldStore {
|
||||
public:
|
||||
FieldStore(size_t headers_initial_capacity)
|
||||
: content_length(-1),
|
||||
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_; }
|
||||
|
||||
Headers &headers() { return headers_; }
|
||||
|
||||
const void add_extra_buffer_size(size_t n) { buffer_size_ += n; }
|
||||
size_t buffer_size() const { return buffer_size_; }
|
||||
|
||||
size_t num_fields() const { return headers_.size() + trailers_.size(); }
|
||||
|
||||
// Returns pointer to the header field with the name |name|. If
|
||||
// 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);
|
||||
// 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;
|
||||
|
||||
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 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();
|
||||
|
||||
// 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 append_last_trailer_key(const char *data, size_t len);
|
||||
void append_last_trailer_value(const char *data, size_t len);
|
||||
|
||||
bool trailer_key_prev() const { return trailer_key_prev_; }
|
||||
|
||||
// content-length, -1 if it is unknown.
|
||||
int64_t content_length;
|
||||
|
||||
private:
|
||||
Headers 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_;
|
||||
// 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.
|
||||
size_t buffer_size_;
|
||||
bool header_key_prev_;
|
||||
bool trailer_key_prev_;
|
||||
};
|
||||
|
||||
struct Request {
|
||||
Request()
|
||||
: fs(16),
|
||||
recv_body_length(0),
|
||||
unconsumed_body_length(0),
|
||||
method(-1),
|
||||
http_major(1),
|
||||
http_minor(1),
|
||||
upgrade_request(false),
|
||||
http2_upgrade_seen(false),
|
||||
connection_close(false),
|
||||
http2_expect_body(false),
|
||||
no_authority(false) {}
|
||||
|
||||
void consume(size_t len) {
|
||||
assert(unconsumed_body_length >= len);
|
||||
unconsumed_body_length -= len;
|
||||
}
|
||||
|
||||
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;
|
||||
// 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;
|
||||
// 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;
|
||||
// the length of request body received so far
|
||||
int64_t recv_body_length;
|
||||
// The number of bytes not consumed by the application yet.
|
||||
size_t unconsumed_body_length;
|
||||
int method;
|
||||
// HTTP major and minor version
|
||||
int http_major, http_minor;
|
||||
// Returns true if the request is HTTP upgrade (HTTP Upgrade or
|
||||
// CONNECT method). Upgrade to HTTP/2 is excluded. For HTTP/2
|
||||
// Upgrade, check get_http2_upgrade_request().
|
||||
bool upgrade_request;
|
||||
// true if h2c is seen in Upgrade header field.
|
||||
bool http2_upgrade_seen;
|
||||
bool connection_close;
|
||||
// true if this is HTTP/2, and request body is expected. Note that
|
||||
// we don't take into account HTTP method here.
|
||||
bool http2_expect_body;
|
||||
// true if request does not have any information about authority.
|
||||
// This happens when: For HTTP/2 request, :authority is missing.
|
||||
// For HTTP/1 request, origin or asterisk form is used.
|
||||
bool no_authority;
|
||||
};
|
||||
|
||||
struct Response {
|
||||
Response()
|
||||
: fs(32),
|
||||
recv_body_length(0),
|
||||
unconsumed_body_length(0),
|
||||
http_status(0),
|
||||
http_major(1),
|
||||
http_minor(1),
|
||||
connection_close(false) {}
|
||||
|
||||
void consume(size_t len) {
|
||||
assert(unconsumed_body_length >= len);
|
||||
unconsumed_body_length -= len;
|
||||
}
|
||||
|
||||
FieldStore fs;
|
||||
// the length of response body received so far
|
||||
int64_t recv_body_length;
|
||||
// The number of bytes not consumed by the application yet. This is
|
||||
// mainly for HTTP/2 backend.
|
||||
size_t unconsumed_body_length;
|
||||
// HTTP status code
|
||||
unsigned int http_status;
|
||||
int http_major, http_minor;
|
||||
bool connection_close;
|
||||
};
|
||||
|
||||
class Downstream {
|
||||
public:
|
||||
Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id,
|
||||
int32_t priority);
|
||||
Downstream(Upstream *upstream, MemchunkPool *mcpool, int32_t stream_id);
|
||||
~Downstream();
|
||||
void reset_upstream(Upstream *upstream);
|
||||
Upstream *get_upstream() const;
|
||||
void set_stream_id(int32_t stream_id);
|
||||
int32_t get_stream_id() const;
|
||||
void set_priority(int32_t pri);
|
||||
int32_t get_priority() const;
|
||||
void set_assoc_stream_id(int32_t stream_id);
|
||||
int32_t get_assoc_stream_id() const;
|
||||
void pause_read(IOCtrlReason reason);
|
||||
int resume_read(IOCtrlReason reason, size_t consumed);
|
||||
void force_resume_read();
|
||||
|
@ -79,9 +236,6 @@ public:
|
|||
// Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
|
||||
// This should not depend on inspect_http1_response().
|
||||
void check_upgrade_fulfilled();
|
||||
// Returns true if the request is upgrade. Upgrade to HTTP/2 is
|
||||
// excluded. For HTTP/2 Upgrade, check get_http2_upgrade_request().
|
||||
bool get_upgrade_request() const;
|
||||
// Returns true if the upgrade is succeded as a result of the call
|
||||
// check_upgrade_fulfilled(). HTTP/2 Upgrade is excluded.
|
||||
bool get_upgraded() const;
|
||||
|
@ -94,99 +248,32 @@ public:
|
|||
bool get_http2_upgrade_request() const;
|
||||
// Returns the value of HTTP2-Settings request header field.
|
||||
const std::string &get_http2_settings() const;
|
||||
|
||||
// downstream request API
|
||||
const Headers &get_request_headers() const;
|
||||
Headers &get_request_headers();
|
||||
const Request &request() const { return req_; }
|
||||
Request &request() { return req_; }
|
||||
|
||||
// Count number of crumbled cookies
|
||||
size_t count_crumble_request_cookie();
|
||||
// Crumbles (split cookie by ";") in request_headers_ and adds them
|
||||
// in |nva|. Headers::no_index is inherited.
|
||||
void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
|
||||
void assemble_request_cookie();
|
||||
const std::string &get_assembled_request_cookie() const;
|
||||
// Lower the request header field names and indexes request headers.
|
||||
// If there is any invalid headers (e.g., multiple Content-Length
|
||||
// having different values), returns -1.
|
||||
int index_request_headers();
|
||||
// Returns pointer to the request header with the name |name|. If
|
||||
// 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 *get_request_header(int16_t token) const;
|
||||
// Returns pointer to the request header with the name |name|. If
|
||||
// no such header is found, returns nullptr.
|
||||
const Headers::value_type *get_request_header(const std::string &name) const;
|
||||
void add_request_header(std::string name, std::string value);
|
||||
void set_last_request_header_value(const char *data, size_t len);
|
||||
// Assembles request cookies. The opposite operation against
|
||||
// crumble_request_cookie().
|
||||
std::string assemble_request_cookie() const;
|
||||
|
||||
void add_request_header(std::string name, std::string value, int16_t token);
|
||||
void add_request_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, bool no_index,
|
||||
int16_t token);
|
||||
|
||||
bool get_request_header_key_prev() const;
|
||||
void append_last_request_header_key(const char *data, size_t len);
|
||||
void append_last_request_header_value(const char *data, size_t len);
|
||||
// Empties request headers.
|
||||
void clear_request_headers();
|
||||
|
||||
size_t get_request_headers_sum() const;
|
||||
|
||||
const Headers &get_request_trailers() const;
|
||||
void add_request_trailer(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, bool no_index,
|
||||
int16_t token);
|
||||
void add_request_trailer(std::string name, std::string value);
|
||||
void set_last_request_trailer_value(const char *data, size_t len);
|
||||
bool get_request_trailer_key_prev() const;
|
||||
void append_last_request_trailer_key(const char *data, size_t len);
|
||||
void append_last_request_trailer_value(const char *data, size_t len);
|
||||
|
||||
void set_request_method(int method);
|
||||
int get_request_method() const;
|
||||
void set_request_path(std::string path);
|
||||
void add_request_headers_sum(size_t amount);
|
||||
void
|
||||
set_request_start_time(std::chrono::high_resolution_clock::time_point time);
|
||||
const std::chrono::high_resolution_clock::time_point &
|
||||
get_request_start_time() const;
|
||||
void append_request_path(const char *data, size_t len);
|
||||
// Returns request path. For HTTP/1.1, this is request-target. For
|
||||
// HTTP/2, this is :path header field value. For CONNECT request,
|
||||
// this is empty.
|
||||
const std::string &get_request_path() const;
|
||||
// Returns HTTP/2 :scheme header field value.
|
||||
const std::string &get_request_http2_scheme() const;
|
||||
void set_request_http2_scheme(std::string scheme);
|
||||
// Returns :authority 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.
|
||||
const std::string &get_request_http2_authority() const;
|
||||
void set_request_http2_authority(std::string authority);
|
||||
void append_request_http2_authority(const char *data, size_t len);
|
||||
void set_request_major(int major);
|
||||
void set_request_minor(int minor);
|
||||
int get_request_major() const;
|
||||
int get_request_minor() const;
|
||||
int push_request_headers();
|
||||
bool get_chunked_request() const;
|
||||
void set_chunked_request(bool f);
|
||||
bool get_request_connection_close() const;
|
||||
void set_request_connection_close(bool f);
|
||||
bool get_request_http2_expect_body() const;
|
||||
void set_request_http2_expect_body(bool f);
|
||||
int push_upload_data_chunk(const uint8_t *data, size_t datalen);
|
||||
int end_upload_data();
|
||||
size_t get_request_datalen() const;
|
||||
void dec_request_datalen(size_t len);
|
||||
void reset_request_datalen();
|
||||
// Validates that received request body length and content-length
|
||||
// matches.
|
||||
bool validate_request_bodylen() const;
|
||||
int64_t get_request_content_length() const;
|
||||
void set_request_content_length(int64_t len);
|
||||
bool request_pseudo_header_allowed(int16_t token) const;
|
||||
bool validate_request_recv_body_length() const;
|
||||
void set_request_downstream_host(std::string host);
|
||||
bool expect_response_body() const;
|
||||
enum {
|
||||
|
@ -212,71 +299,24 @@ public:
|
|||
bool get_request_pending() const;
|
||||
// Returns true if request is ready to be submitted to downstream.
|
||||
bool request_submission_ready() const;
|
||||
|
||||
// downstream response API
|
||||
const Headers &get_response_headers() const;
|
||||
Headers &get_response_headers();
|
||||
// Lower the response header field names and indexes response
|
||||
// headers. If there are invalid headers (e.g., multiple
|
||||
// Content-Length with different values), returns -1.
|
||||
int index_response_headers();
|
||||
// Returns pointer to the response header with the name |name|. If
|
||||
// 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 response headers are indexed.
|
||||
const Headers::value_type *get_response_header(int16_t token) const;
|
||||
Headers::value_type *get_response_header(int16_t token);
|
||||
const Response &response() const { return resp_; }
|
||||
Response &response() { return resp_; }
|
||||
|
||||
// Rewrites the location response header field.
|
||||
void rewrite_location_response_header(const std::string &upstream_scheme);
|
||||
void add_response_header(std::string name, std::string value);
|
||||
void set_last_response_header_value(const char *data, size_t len);
|
||||
|
||||
void add_response_header(std::string name, std::string value, int16_t token);
|
||||
void add_response_header(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen, bool no_index,
|
||||
int16_t token);
|
||||
|
||||
bool get_response_header_key_prev() const;
|
||||
void append_last_response_header_key(const char *data, size_t len);
|
||||
void append_last_response_header_value(const char *data, size_t len);
|
||||
// Empties response headers.
|
||||
void clear_response_headers();
|
||||
|
||||
size_t get_response_headers_sum() const;
|
||||
|
||||
const Headers &get_response_trailers() const;
|
||||
void add_response_trailer(const uint8_t *name, size_t namelen,
|
||||
const uint8_t *value, size_t valuelen,
|
||||
bool no_index, int16_t token);
|
||||
void add_response_trailer(std::string name, std::string value);
|
||||
void set_last_response_trailer_value(const char *data, size_t len);
|
||||
bool get_response_trailer_key_prev() const;
|
||||
void append_last_response_trailer_key(const char *data, size_t len);
|
||||
void append_last_response_trailer_value(const char *data, size_t len);
|
||||
|
||||
unsigned int get_response_http_status() const;
|
||||
void set_response_http_status(unsigned int status);
|
||||
void set_response_major(int major);
|
||||
void set_response_minor(int minor);
|
||||
int get_response_major() const;
|
||||
int get_response_minor() const;
|
||||
int get_response_version() const;
|
||||
bool get_chunked_response() const;
|
||||
void set_chunked_response(bool f);
|
||||
bool get_response_connection_close() const;
|
||||
void set_response_connection_close(bool f);
|
||||
|
||||
void set_response_state(int state);
|
||||
int get_response_state() const;
|
||||
DefaultMemchunks *get_response_buf();
|
||||
bool response_buf_full();
|
||||
void add_response_bodylen(size_t amount);
|
||||
int64_t get_response_bodylen() const;
|
||||
void add_response_sent_bodylen(size_t amount);
|
||||
int64_t get_response_sent_bodylen() const;
|
||||
int64_t get_response_content_length() const;
|
||||
void set_response_content_length(int64_t len);
|
||||
// Validates that received response body length and content-length
|
||||
// matches.
|
||||
bool validate_response_bodylen() const;
|
||||
bool validate_response_recv_body_length() const;
|
||||
uint32_t get_response_rst_stream_error_code() const;
|
||||
void set_response_rst_stream_error_code(uint32_t error_code);
|
||||
// Inspects HTTP/1 response. This checks tranfer-encoding etc.
|
||||
|
@ -288,22 +328,11 @@ public:
|
|||
bool get_non_final_response() const;
|
||||
void set_expect_final_response(bool f);
|
||||
bool get_expect_final_response() const;
|
||||
void add_response_datalen(size_t len);
|
||||
void dec_response_datalen(size_t len);
|
||||
size_t get_response_datalen() const;
|
||||
void reset_response_datalen();
|
||||
bool response_pseudo_header_allowed(int16_t token) const;
|
||||
|
||||
// Call this method when there is incoming data in downstream
|
||||
// connection.
|
||||
int on_read();
|
||||
|
||||
// Change the priority of downstream
|
||||
int change_priority(int32_t pri);
|
||||
|
||||
bool get_rst_stream_after_end_stream() const;
|
||||
void set_rst_stream_after_end_stream(bool f);
|
||||
|
||||
// Resets upstream read timer. If it is active, timeout value is
|
||||
// reset. If it is not active, timer will be started.
|
||||
void reset_upstream_rtimer();
|
||||
|
@ -361,25 +390,19 @@ public:
|
|||
|
||||
Downstream *dlnext, *dlprev;
|
||||
|
||||
private:
|
||||
Headers request_headers_;
|
||||
Headers response_headers_;
|
||||
// the length of response body sent to upstream client
|
||||
int64_t response_sent_body_length;
|
||||
|
||||
// trailer part. For HTTP/1.1, trailer part is only included with
|
||||
// chunked encoding. For HTTP/2, there is no such limit.
|
||||
Headers request_trailers_;
|
||||
Headers response_trailers_;
|
||||
private:
|
||||
Request req_;
|
||||
Response resp_;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point request_start_time_;
|
||||
|
||||
std::string request_path_;
|
||||
std::string request_http2_scheme_;
|
||||
std::string request_http2_authority_;
|
||||
// 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_;
|
||||
std::string assembled_request_cookie_;
|
||||
|
||||
DefaultMemchunks request_buf_;
|
||||
DefaultMemchunks response_buf_;
|
||||
|
@ -390,76 +413,36 @@ private:
|
|||
ev_timer downstream_rtimer_;
|
||||
ev_timer downstream_wtimer_;
|
||||
|
||||
// the length of request body received so far
|
||||
int64_t request_bodylen_;
|
||||
// the length of response body received so far
|
||||
int64_t response_bodylen_;
|
||||
|
||||
// the length of response body sent to upstream client
|
||||
int64_t response_sent_bodylen_;
|
||||
|
||||
// content-length of request body, -1 if it is unknown.
|
||||
int64_t request_content_length_;
|
||||
// content-length of response body, -1 if it is unknown.
|
||||
int64_t response_content_length_;
|
||||
|
||||
Upstream *upstream_;
|
||||
std::unique_ptr<DownstreamConnection> dconn_;
|
||||
|
||||
// only used by HTTP/2 or SPDY upstream
|
||||
BlockedLink *blocked_link_;
|
||||
|
||||
size_t request_headers_sum_;
|
||||
size_t response_headers_sum_;
|
||||
|
||||
// The number of bytes not consumed by the application yet.
|
||||
size_t request_datalen_;
|
||||
size_t response_datalen_;
|
||||
|
||||
// How many times we tried in backend connection
|
||||
size_t num_retry_;
|
||||
|
||||
// The stream ID in frontend connection
|
||||
int32_t stream_id_;
|
||||
int32_t priority_;
|
||||
// The associated stream ID in frontend connection if this is pushed
|
||||
// stream.
|
||||
int32_t assoc_stream_id_;
|
||||
// stream ID in backend connection
|
||||
int32_t downstream_stream_id_;
|
||||
|
||||
// RST_STREAM error_code from downstream HTTP2 connection
|
||||
uint32_t response_rst_stream_error_code_;
|
||||
|
||||
int request_method_;
|
||||
// request state
|
||||
int request_state_;
|
||||
int request_major_;
|
||||
int request_minor_;
|
||||
|
||||
// response state
|
||||
int response_state_;
|
||||
unsigned int response_http_status_;
|
||||
int response_major_;
|
||||
int response_minor_;
|
||||
|
||||
// only used by HTTP/2 or SPDY upstream
|
||||
int dispatch_state_;
|
||||
|
||||
http2::HeaderIndex request_hdidx_;
|
||||
http2::HeaderIndex response_hdidx_;
|
||||
|
||||
// true if the request contains upgrade token (HTTP Upgrade or
|
||||
// CONNECT)
|
||||
bool upgrade_request_;
|
||||
// true if the connection is upgraded (HTTP Upgrade or CONNECT)
|
||||
// true if the connection is upgraded (HTTP Upgrade or CONNECT),
|
||||
// excluding upgrade to HTTP/2.
|
||||
bool upgraded_;
|
||||
|
||||
bool http2_upgrade_seen_;
|
||||
|
||||
// true if backend request uses chunked transfer-encoding
|
||||
bool chunked_request_;
|
||||
bool request_connection_close_;
|
||||
bool request_header_key_prev_;
|
||||
bool request_trailer_key_prev_;
|
||||
bool request_http2_expect_body_;
|
||||
|
||||
// true if response to client uses chunked transfer-encoding
|
||||
bool chunked_response_;
|
||||
bool response_connection_close_;
|
||||
bool response_header_key_prev_;
|
||||
bool response_trailer_key_prev_;
|
||||
// true if we have not got final response code
|
||||
bool expect_final_response_;
|
||||
// true if downstream request is pending because backend connection
|
||||
// has not been established or should be checked before use;
|
||||
|
|
|
@ -56,7 +56,6 @@ public:
|
|||
virtual int on_timeout() { return 0; }
|
||||
|
||||
virtual void on_upstream_change(Upstream *uptream) = 0;
|
||||
virtual int on_priority_change(int32_t pri) = 0;
|
||||
virtual size_t get_group() const = 0;
|
||||
|
||||
// true if this object is poolable.
|
||||
|
|
|
@ -79,7 +79,7 @@ DownstreamQueue::make_host_key(const std::string &host) const {
|
|||
|
||||
const std::string &
|
||||
DownstreamQueue::make_host_key(Downstream *downstream) const {
|
||||
return make_host_key(downstream->get_request_http2_authority());
|
||||
return make_host_key(downstream->request().authority);
|
||||
}
|
||||
|
||||
void DownstreamQueue::mark_active(Downstream *downstream) {
|
||||
|
|
|
@ -32,17 +32,17 @@
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
void test_downstream_index_request_headers(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_request_header("1", "0");
|
||||
d.add_request_header("2", "1");
|
||||
d.add_request_header("Charlie", "2");
|
||||
d.add_request_header("Alpha", "3");
|
||||
d.add_request_header("Delta", "4");
|
||||
d.add_request_header("BravO", "5");
|
||||
d.add_request_header(":method", "6");
|
||||
d.add_request_header(":authority", "7");
|
||||
d.index_request_headers();
|
||||
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();
|
||||
|
||||
auto ans = Headers{{"1", "0"},
|
||||
{"2", "1"},
|
||||
|
@ -52,62 +52,36 @@ void test_downstream_index_request_headers(void) {
|
|||
{"bravo", "5"},
|
||||
{":method", "6"},
|
||||
{":authority", "7"}};
|
||||
CU_ASSERT(ans == d.get_request_headers());
|
||||
CU_ASSERT(ans == fs.headers());
|
||||
}
|
||||
|
||||
void test_downstream_index_response_headers(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_response_header("Charlie", "0");
|
||||
d.add_response_header("Alpha", "1");
|
||||
d.add_response_header("Delta", "2");
|
||||
d.add_response_header("BravO", "3");
|
||||
d.index_response_headers();
|
||||
|
||||
auto ans =
|
||||
Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
|
||||
CU_ASSERT(ans == d.get_response_headers());
|
||||
}
|
||||
|
||||
void test_downstream_get_request_header(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_request_header("alpha", "0");
|
||||
d.add_request_header(":authority", "1");
|
||||
d.add_request_header("content-length", "2");
|
||||
d.index_request_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();
|
||||
|
||||
// By token
|
||||
CU_ASSERT(Header(":authority", "1") ==
|
||||
*d.get_request_header(http2::HD__AUTHORITY));
|
||||
CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
|
||||
CU_ASSERT(Header(":authority", "1") == *fs.header(http2::HD__AUTHORITY));
|
||||
CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
|
||||
|
||||
// By name
|
||||
CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
|
||||
CU_ASSERT(nullptr == d.get_request_header("bravo"));
|
||||
}
|
||||
|
||||
void test_downstream_get_response_header(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_response_header("alpha", "0");
|
||||
d.add_response_header(":status", "1");
|
||||
d.add_response_header("content-length", "2");
|
||||
d.index_response_headers();
|
||||
|
||||
// By token
|
||||
CU_ASSERT(Header(":status", "1") ==
|
||||
*d.get_response_header(http2::HD__STATUS));
|
||||
CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
|
||||
CU_ASSERT(Header("alpha", "0") == *fs.header("alpha"));
|
||||
CU_ASSERT(nullptr == fs.header("bravo"));
|
||||
}
|
||||
|
||||
void test_downstream_crumble_request_cookie(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_request_header(":method", "get");
|
||||
d.add_request_header(":path", "/");
|
||||
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;;";
|
||||
d.add_request_header(
|
||||
req.fs.add_header(
|
||||
reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
|
||||
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
|
||||
d.add_request_header("cookie", ";delta");
|
||||
d.add_request_header("cookie", "echo");
|
||||
req.fs.add_header("cookie", ";delta");
|
||||
req.fs.add_header("cookie", "echo");
|
||||
|
||||
std::vector<nghttp2_nv> nva;
|
||||
d.crumble_request_cookie(nva);
|
||||
|
@ -138,39 +112,28 @@ void test_downstream_crumble_request_cookie(void) {
|
|||
}
|
||||
|
||||
void test_downstream_assemble_request_cookie(void) {
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.add_request_header(":method", "get");
|
||||
d.add_request_header(":path", "/");
|
||||
d.add_request_header("cookie", "alpha");
|
||||
d.add_request_header("cookie", "bravo;");
|
||||
d.add_request_header("cookie", "charlie; ");
|
||||
d.add_request_header("cookie", "delta;;");
|
||||
d.assemble_request_cookie();
|
||||
CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
|
||||
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;;");
|
||||
CU_ASSERT("alpha; bravo; charlie; delta" == d.assemble_request_cookie());
|
||||
}
|
||||
|
||||
void test_downstream_rewrite_location_response_header(void) {
|
||||
{
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.set_request_downstream_host("localhost:3000");
|
||||
d.add_request_header("host", "localhost");
|
||||
d.add_response_header("location", "http://localhost:3000/");
|
||||
d.index_request_headers();
|
||||
d.index_response_headers();
|
||||
d.rewrite_location_response_header("https");
|
||||
auto location = d.get_response_header(http2::HD_LOCATION);
|
||||
CU_ASSERT("https://localhost/" == (*location).value);
|
||||
}
|
||||
{
|
||||
Downstream d(nullptr, nullptr, 0, 0);
|
||||
d.set_request_downstream_host("localhost");
|
||||
d.set_request_http2_authority("localhost");
|
||||
d.add_response_header("location", "http://localhost:3000/");
|
||||
d.index_response_headers();
|
||||
d.rewrite_location_response_header("https");
|
||||
auto location = d.get_response_header(http2::HD_LOCATION);
|
||||
CU_ASSERT("https://localhost/" == (*location).value);
|
||||
}
|
||||
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");
|
||||
auto location = resp.fs.header(http2::HD_LOCATION);
|
||||
CU_ASSERT("https://localhost:8443/" == (*location).value);
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -31,10 +31,8 @@
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
void test_downstream_index_request_headers(void);
|
||||
void test_downstream_index_response_headers(void);
|
||||
void test_downstream_get_request_header(void);
|
||||
void test_downstream_get_response_header(void);
|
||||
void test_downstream_field_store_index_headers(void);
|
||||
void test_downstream_field_store_header(void);
|
||||
void test_downstream_crumble_request_cookie(void);
|
||||
void test_downstream_assemble_request_cookie(void);
|
||||
void test_downstream_rewrite_location_response_header(void);
|
||||
|
|
|
@ -44,9 +44,8 @@ std::string create_error_html(unsigned int status_code) {
|
|||
res += "</title><body><h1>";
|
||||
res += status;
|
||||
res += "</h1><footer>";
|
||||
res += get_config()->server_name;
|
||||
res += " at port ";
|
||||
res += util::utos(get_config()->port);
|
||||
const auto &server_name = get_config()->http.server_name;
|
||||
res.append(server_name.c_str(), server_name.size());
|
||||
res += "</footer></body></html>";
|
||||
return res;
|
||||
}
|
||||
|
@ -62,6 +61,61 @@ std::string create_via_header_value(int major, int minor) {
|
|||
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;
|
||||
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 += ";";
|
||||
} else {
|
||||
res += "by=\"";
|
||||
res += node_by;
|
||||
res += "\";";
|
||||
}
|
||||
}
|
||||
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 += "\";";
|
||||
} else {
|
||||
res += "for=";
|
||||
res += node_for;
|
||||
res += ";";
|
||||
}
|
||||
}
|
||||
if ((params & FORWARDED_HOST) && !host.empty()) {
|
||||
// Just be quoted to skip checking characters.
|
||||
res += "host=\"";
|
||||
res += host;
|
||||
res += "\";";
|
||||
}
|
||||
if ((params & FORWARDED_PROTO) && !proto.empty()) {
|
||||
// Scheme production rule only allow characters which are all in
|
||||
// token.
|
||||
res += "proto=";
|
||||
res += proto;
|
||||
res += ";";
|
||||
}
|
||||
|
||||
if (res.empty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
res.erase(res.size() - 1);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string colorizeHeaders(const char *hdrs) {
|
||||
std::string nhdrs;
|
||||
const char *p = strchr(hdrs, '\n');
|
||||
|
|
|
@ -39,6 +39,13 @@ std::string create_error_html(unsigned int status_code);
|
|||
|
||||
std::string create_via_header_value(int major, int minor);
|
||||
|
||||
// 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);
|
||||
|
||||
// Adds ANSI color codes to HTTP headers |hdrs|.
|
||||
std::string colorizeHeaders(const char *hdrs);
|
||||
|
||||
|
|
|
@ -46,8 +46,11 @@ namespace shrpx {
|
|||
|
||||
Http2DownstreamConnection::Http2DownstreamConnection(
|
||||
DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
|
||||
: DownstreamConnection(dconn_pool), dlnext(nullptr), dlprev(nullptr),
|
||||
http2session_(http2session), sd_(nullptr) {}
|
||||
: DownstreamConnection(dconn_pool),
|
||||
dlnext(nullptr),
|
||||
dlprev(nullptr),
|
||||
http2session_(http2session),
|
||||
sd_(nullptr) {}
|
||||
|
||||
Http2DownstreamConnection::~Http2DownstreamConnection() {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
|
@ -71,10 +74,12 @@ Http2DownstreamConnection::~Http2DownstreamConnection() {
|
|||
downstream_->get_downstream_stream_id() != -1) {
|
||||
submit_rst_stream(downstream_, error_code);
|
||||
|
||||
http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||
downstream_->get_response_datalen());
|
||||
auto &resp = downstream_->response();
|
||||
|
||||
downstream_->reset_response_datalen();
|
||||
http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||
resp.unconsumed_body_length);
|
||||
|
||||
resp.unconsumed_body_length = 0;
|
||||
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
@ -105,15 +110,18 @@ void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
|
|||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
|
||||
}
|
||||
|
||||
auto &resp = downstream_->response();
|
||||
|
||||
if (submit_rst_stream(downstream) == 0) {
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
||||
if (downstream_->get_downstream_stream_id() != -1) {
|
||||
http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||
downstream_->get_response_datalen());
|
||||
resp.unconsumed_body_length);
|
||||
|
||||
downstream_->reset_response_datalen();
|
||||
resp.unconsumed_body_length = 0;
|
||||
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
@ -165,6 +173,7 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
// on the priority, DATA frame may come first.
|
||||
return NGHTTP2_ERR_DEFERRED;
|
||||
}
|
||||
const auto &req = downstream->request();
|
||||
auto input = downstream->get_request_buf();
|
||||
auto nread = input->remove(buf, length);
|
||||
auto input_empty = input->rleft() == 0;
|
||||
|
@ -190,13 +199,13 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
// If connection is upgraded, don't set EOF flag, since HTTP/1
|
||||
// will set MSG_COMPLETE to request state after upgrade response
|
||||
// header is seen.
|
||||
(!downstream->get_upgrade_request() ||
|
||||
(!req.upgrade_request ||
|
||||
(downstream->get_response_state() == Downstream::HEADER_COMPLETE &&
|
||||
!downstream->get_upgraded()))) {
|
||||
|
||||
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||
|
||||
auto &trailers = downstream->get_request_trailers();
|
||||
const auto &trailers = req.fs.trailers();
|
||||
if (!trailers.empty()) {
|
||||
std::vector<nghttp2_nv> nva;
|
||||
nva.reserve(trailers.size());
|
||||
|
@ -248,107 +257,150 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
|
||||
downstream_->set_request_pending(false);
|
||||
|
||||
auto method = downstream_->get_request_method();
|
||||
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||
get_config()->http2_proxy ||
|
||||
get_config()->client_proxy || method == HTTP_CONNECT;
|
||||
const auto &req = downstream_->request();
|
||||
|
||||
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;
|
||||
|
||||
// http2session_ has already in CONNECTED state, so we can get
|
||||
// addr_idx here.
|
||||
auto addr_idx = http2session_->get_addr_idx();
|
||||
auto group = http2session_->get_group();
|
||||
auto downstream_hostport = get_config()
|
||||
->downstream_addr_groups[group]
|
||||
.addrs[addr_idx]
|
||||
.hostport.get();
|
||||
const auto &downstream_hostport = http2session_->get_addr()->hostport;
|
||||
|
||||
// For HTTP/1.0 request, there is no authority in request. In that
|
||||
// case, we use backend server's host nonetheless.
|
||||
const char *authority = downstream_hostport;
|
||||
auto &req_authority = downstream_->get_request_http2_authority();
|
||||
if (no_host_rewrite && !req_authority.empty()) {
|
||||
authority = req_authority.c_str();
|
||||
auto authority = StringRef(downstream_hostport);
|
||||
|
||||
if (no_host_rewrite && !req.authority.empty()) {
|
||||
authority = StringRef(req.authority);
|
||||
}
|
||||
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
|
||||
auto nheader = downstream_->get_request_headers().size();
|
||||
downstream_->set_request_downstream_host(authority.str());
|
||||
|
||||
size_t num_cookies = 0;
|
||||
if (!get_config()->http2_no_cookie_crumbling) {
|
||||
if (!http2conf.no_cookie_crumbling) {
|
||||
num_cookies = downstream_->count_crumble_request_cookie();
|
||||
}
|
||||
|
||||
// 8 means:
|
||||
// 9 means:
|
||||
// 1. :method
|
||||
// 2. :scheme
|
||||
// 3. :path
|
||||
// 4. :authority
|
||||
// 4. :authority (or host)
|
||||
// 5. via (optional)
|
||||
// 6. x-forwarded-for (optional)
|
||||
// 7. x-forwarded-proto (optional)
|
||||
// 8. te (optional)
|
||||
// 9. forwarded (optional)
|
||||
auto nva = std::vector<nghttp2_nv>();
|
||||
nva.reserve(nheader + 8 + num_cookies +
|
||||
get_config()->add_request_headers.size());
|
||||
nva.reserve(req.fs.headers().size() + 9 + num_cookies +
|
||||
httpconf.add_request_headers.size());
|
||||
|
||||
nva.push_back(
|
||||
http2::make_nv_lc_nocopy(":method", http2::to_method_string(method)));
|
||||
http2::make_nv_lc_nocopy(":method", http2::to_method_string(req.method)));
|
||||
|
||||
auto &scheme = downstream_->get_request_http2_scheme();
|
||||
if (req.method != HTTP_CONNECT) {
|
||||
assert(!req.scheme.empty());
|
||||
|
||||
nva.push_back(http2::make_nv_lc_nocopy(":authority", authority));
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":scheme", req.scheme));
|
||||
|
||||
if (method != HTTP_CONNECT) {
|
||||
assert(!scheme.empty());
|
||||
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
|
||||
|
||||
auto &path = downstream_->get_request_path();
|
||||
if (method == HTTP_OPTIONS && path.empty()) {
|
||||
if (req.method == HTTP_OPTIONS && req.path.empty()) {
|
||||
nva.push_back(http2::make_nv_ll(":path", "*"));
|
||||
} else {
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":path", path));
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":path", req.path));
|
||||
}
|
||||
|
||||
if (!req.no_authority) {
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
|
||||
} else {
|
||||
nva.push_back(http2::make_nv_ls_nocopy("host", authority));
|
||||
}
|
||||
} else {
|
||||
nva.push_back(http2::make_nv_ls_nocopy(":authority", authority));
|
||||
}
|
||||
|
||||
http2::copy_headers_to_nva_nocopy(nva, downstream_->get_request_headers());
|
||||
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers());
|
||||
|
||||
bool chunked_encoding = false;
|
||||
auto transfer_encoding =
|
||||
downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
|
||||
auto transfer_encoding = req.fs.header(http2::HD_TRANSFER_ENCODING);
|
||||
if (transfer_encoding &&
|
||||
util::strieq_l("chunked", (*transfer_encoding).value)) {
|
||||
chunked_encoding = true;
|
||||
}
|
||||
|
||||
if (!get_config()->http2_no_cookie_crumbling) {
|
||||
if (!http2conf.no_cookie_crumbling) {
|
||||
downstream_->crumble_request_cookie(nva);
|
||||
}
|
||||
|
||||
auto upstream = downstream_->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
std::string forwarded_value;
|
||||
|
||||
auto &fwdconf = httpconf.forwarded;
|
||||
|
||||
auto fwd =
|
||||
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
|
||||
|
||||
if (fwdconf.params) {
|
||||
auto params = fwdconf.params;
|
||||
|
||||
if (get_config()->http2_proxy || get_config()->client_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);
|
||||
if (fwd || !value.empty()) {
|
||||
if (fwd) {
|
||||
forwarded_value = fwd->value;
|
||||
|
||||
if (!value.empty()) {
|
||||
forwarded_value += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
forwarded_value += value;
|
||||
|
||||
nva.push_back(http2::make_nv_ls("forwarded", forwarded_value));
|
||||
}
|
||||
} else if (fwd) {
|
||||
nva.push_back(http2::make_nv_ls_nocopy("forwarded", fwd->value));
|
||||
forwarded_value = fwd->value;
|
||||
}
|
||||
|
||||
auto &xffconf = httpconf.xff;
|
||||
|
||||
auto xff = xffconf.strip_incoming ? nullptr
|
||||
: req.fs.header(http2::HD_X_FORWARDED_FOR);
|
||||
|
||||
std::string xff_value;
|
||||
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
|
||||
if (get_config()->add_x_forwarded_for) {
|
||||
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
|
||||
if (xffconf.add) {
|
||||
if (xff) {
|
||||
xff_value = (*xff).value;
|
||||
xff_value += ", ";
|
||||
}
|
||||
xff_value +=
|
||||
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
|
||||
xff_value += upstream->get_client_handler()->get_ipaddr();
|
||||
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
|
||||
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
} else if (xff) {
|
||||
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
|
||||
}
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
downstream_->get_request_method() != HTTP_CONNECT) {
|
||||
req.method != HTTP_CONNECT) {
|
||||
// We use same protocol with :scheme header field
|
||||
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", scheme));
|
||||
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
|
||||
}
|
||||
|
||||
std::string via_value;
|
||||
auto via = downstream_->get_request_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
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));
|
||||
}
|
||||
|
@ -357,12 +409,11 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
via_value = (*via).value;
|
||||
via_value += ", ";
|
||||
}
|
||||
via_value += http::create_via_header_value(
|
||||
downstream_->get_request_major(), downstream_->get_request_minor());
|
||||
via_value += http::create_via_header_value(req.http_major, req.http_minor);
|
||||
nva.push_back(http2::make_nv_ls("via", via_value));
|
||||
}
|
||||
|
||||
auto te = downstream_->get_request_header(http2::HD_TE);
|
||||
auto te = req.fs.header(http2::HD_TE);
|
||||
// HTTP/1 upstream request can contain keyword other than
|
||||
// "trailers". We just forward "trailers".
|
||||
// TODO more strict handling required here.
|
||||
|
@ -370,7 +421,7 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
nva.push_back(http2::make_nv_ll("te", "trailers"));
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_request_headers) {
|
||||
for (auto &p : httpconf.add_request_headers) {
|
||||
nva.push_back(http2::make_nv_nocopy(p.first, p.second));
|
||||
}
|
||||
|
||||
|
@ -382,21 +433,18 @@ int Http2DownstreamConnection::push_request_headers() {
|
|||
DCLOG(INFO, this) << "HTTP request headers\n" << ss.str();
|
||||
}
|
||||
|
||||
auto content_length =
|
||||
downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
|
||||
auto content_length = req.fs.header(http2::HD_CONTENT_LENGTH);
|
||||
// TODO check content-length: 0 case
|
||||
|
||||
if (downstream_->get_request_method() == HTTP_CONNECT || chunked_encoding ||
|
||||
content_length || downstream_->get_request_http2_expect_body()) {
|
||||
if (req.method == HTTP_CONNECT || chunked_encoding || content_length ||
|
||||
req.http2_expect_body) {
|
||||
// Request-body is expected.
|
||||
nghttp2_data_provider data_prd;
|
||||
data_prd.source.ptr = this;
|
||||
data_prd.read_callback = http2_data_read_callback;
|
||||
rv = http2session_->submit_request(this, downstream_->get_priority(),
|
||||
nva.data(), nva.size(), &data_prd);
|
||||
rv = http2session_->submit_request(this, nva.data(), nva.size(), &data_prd);
|
||||
} else {
|
||||
rv = http2session_->submit_request(this, downstream_->get_priority(),
|
||||
nva.data(), nva.size(), nullptr);
|
||||
rv = http2session_->submit_request(this, nva.data(), nva.size(), nullptr);
|
||||
}
|
||||
if (rv != 0) {
|
||||
DCLOG(FATAL, this) << "nghttp2_submit_request() failed";
|
||||
|
@ -456,8 +504,6 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
|
|||
}
|
||||
|
||||
if (consumed > 0) {
|
||||
assert(downstream_->get_response_datalen() >= consumed);
|
||||
|
||||
rv = http2session_->consume(downstream_->get_downstream_stream_id(),
|
||||
consumed);
|
||||
|
||||
|
@ -465,7 +511,9 @@ int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
|
|||
return -1;
|
||||
}
|
||||
|
||||
downstream_->dec_response_datalen(consumed);
|
||||
auto &resp = downstream_->response();
|
||||
|
||||
resp.unconsumed_body_length -= consumed;
|
||||
|
||||
http2session_->signal_write();
|
||||
}
|
||||
|
@ -498,24 +546,6 @@ StreamData *Http2DownstreamConnection::detach_stream_data() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int Http2DownstreamConnection::on_priority_change(int32_t pri) {
|
||||
int rv;
|
||||
if (downstream_->get_priority() == pri) {
|
||||
return 0;
|
||||
}
|
||||
downstream_->set_priority(pri);
|
||||
if (http2session_->get_state() != Http2Session::CONNECTED) {
|
||||
return 0;
|
||||
}
|
||||
rv = http2session_->submit_priority(this, pri);
|
||||
if (rv != 0) {
|
||||
DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
|
||||
return -1;
|
||||
}
|
||||
http2session_->signal_write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http2DownstreamConnection::on_timeout() {
|
||||
if (!downstream_) {
|
||||
return 0;
|
||||
|
|
|
@ -60,7 +60,6 @@ public:
|
|||
virtual int on_timeout();
|
||||
|
||||
virtual void on_upstream_change(Upstream *upstream) {}
|
||||
virtual int on_priority_change(int32_t pri);
|
||||
virtual size_t get_group() const;
|
||||
|
||||
// This object is not poolable because we dont' have facility to
|
||||
|
|
|
@ -57,6 +57,10 @@ const ev_tstamp CONNCHK_TIMEOUT = 5.;
|
|||
const ev_tstamp CONNCHK_PING_TIMEOUT = 1.;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr size_t MAX_BUFFER_SIZE = 32_k;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||
auto http2session = static_cast<Http2Session *>(w->data);
|
||||
|
@ -146,17 +150,26 @@ 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(),
|
||||
get_config()->downstream_write_timeout,
|
||||
get_config()->downstream_read_timeout, 0, 0, 0, 0, writecb, readcb,
|
||||
timeoutcb, this, get_config()->tls_dyn_rec_warmup_threshold,
|
||||
get_config()->tls_dyn_rec_idle_timeout),
|
||||
worker_(worker), connect_blocker_(connect_blocker), ssl_ctx_(ssl_ctx),
|
||||
session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
|
||||
addr_idx_(0), group_(group), index_(idx), state_(DISCONNECTED),
|
||||
connection_check_state_(CONNECTION_CHECK_NONE), flow_control_(false) {
|
||||
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),
|
||||
wb_(worker->get_mcpool()),
|
||||
worker_(worker),
|
||||
connect_blocker_(connect_blocker),
|
||||
ssl_ctx_(ssl_ctx),
|
||||
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_ = on_write_ = &Http2Session::noop;
|
||||
|
||||
on_read_ = &Http2Session::read_noop;
|
||||
on_write_ = &Http2Session::write_noop;
|
||||
|
||||
// We will resuse this many times, so use repeat timeout value. The
|
||||
// timeout value is set later.
|
||||
|
@ -180,7 +193,6 @@ int Http2Session::disconnect(bool hard) {
|
|||
nghttp2_session_del(session_);
|
||||
session_ = nullptr;
|
||||
|
||||
rb_.reset();
|
||||
wb_.reset();
|
||||
|
||||
conn_.rlimit.stopw();
|
||||
|
@ -190,11 +202,13 @@ int Http2Session::disconnect(bool hard) {
|
|||
ev_timer_stop(conn_.loop, &connchk_timer_);
|
||||
|
||||
read_ = write_ = &Http2Session::noop;
|
||||
on_read_ = on_write_ = &Http2Session::noop;
|
||||
|
||||
on_read_ = &Http2Session::read_noop;
|
||||
on_write_ = &Http2Session::write_noop;
|
||||
|
||||
conn_.disconnect();
|
||||
|
||||
addr_idx_ = 0;
|
||||
addr_ = nullptr;
|
||||
|
||||
if (proxy_htp_) {
|
||||
proxy_htp_.reset();
|
||||
|
@ -237,16 +251,10 @@ int Http2Session::disconnect(bool hard) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Http2Session::check_cert() {
|
||||
return ssl::check_cert(
|
||||
conn_.tls.ssl,
|
||||
&get_config()->downstream_addr_groups[group_].addrs[addr_idx_]);
|
||||
}
|
||||
|
||||
int Http2Session::initiate_connection() {
|
||||
int rv = 0;
|
||||
|
||||
auto &addrs = get_config()->downstream_addr_groups[group_].addrs;
|
||||
auto &addrs = get_config()->conn.downstream.addr_groups[group_].addrs;
|
||||
|
||||
if (state_ == DISCONNECTED) {
|
||||
if (connect_blocker_->blocked()) {
|
||||
|
@ -258,40 +266,36 @@ int Http2Session::initiate_connection() {
|
|||
}
|
||||
|
||||
auto &next_downstream = worker_->get_dgrp(group_)->next;
|
||||
addr_idx_ = next_downstream;
|
||||
addr_ = &addrs[next_downstream];
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "Using downstream address idx=" << next_downstream
|
||||
<< " out of " << addrs.size();
|
||||
}
|
||||
|
||||
if (++next_downstream >= addrs.size()) {
|
||||
next_downstream = 0;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "Using downstream address idx=" << addr_idx_
|
||||
<< " out of " << addrs.size();
|
||||
}
|
||||
}
|
||||
|
||||
auto &downstream_addr = addrs[addr_idx_];
|
||||
|
||||
if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) {
|
||||
const auto &proxy = get_config()->downstream_http_proxy;
|
||||
if (!proxy.host.empty() && state_ == DISCONNECTED) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "Connecting to the proxy "
|
||||
<< get_config()->downstream_http_proxy_host.get() << ":"
|
||||
<< get_config()->downstream_http_proxy_port;
|
||||
SSLOG(INFO, this) << "Connecting to the proxy " << proxy.host << ":"
|
||||
<< proxy.port;
|
||||
}
|
||||
|
||||
conn_.fd = util::create_nonblock_socket(
|
||||
get_config()->downstream_http_proxy_addr.su.storage.ss_family);
|
||||
conn_.fd = util::create_nonblock_socket(proxy.addr.su.storage.ss_family);
|
||||
|
||||
if (conn_.fd == -1) {
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = connect(conn_.fd, &get_config()->downstream_http_proxy_addr.su.sa,
|
||||
get_config()->downstream_http_proxy_addr.len);
|
||||
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 "
|
||||
<< get_config()->downstream_http_proxy_host.get()
|
||||
<< ":" << get_config()->downstream_http_proxy_port;
|
||||
SSLOG(ERROR, this) << "Failed to connect to the proxy " << proxy.host
|
||||
<< ":" << proxy.port;
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
}
|
||||
|
@ -334,26 +338,23 @@ int Http2Session::initiate_connection() {
|
|||
conn_.set_ssl(ssl);
|
||||
}
|
||||
|
||||
const char *sni_name = nullptr;
|
||||
if (get_config()->backend_tls_sni_name) {
|
||||
sni_name = get_config()->backend_tls_sni_name.get();
|
||||
} else {
|
||||
sni_name = downstream_addr.host.get();
|
||||
}
|
||||
auto sni_name = !get_config()->tls.backend_sni_name.empty()
|
||||
? StringRef(get_config()->tls.backend_sni_name)
|
||||
: StringRef(addr_->host);
|
||||
|
||||
if (sni_name && !util::numeric_host(sni_name)) {
|
||||
if (!util::numeric_host(sni_name.c_str())) {
|
||||
// TLS extensions: SNI. There is no documentation about the return
|
||||
// code for this function (actually this is macro wrapping SSL_ctrl
|
||||
// at the time of this writing).
|
||||
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name);
|
||||
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
|
||||
}
|
||||
// If state_ == PROXY_CONNECTED, we has connected to the proxy
|
||||
// using conn_.fd and tunnel has been established.
|
||||
if (state_ == DISCONNECTED) {
|
||||
assert(conn_.fd == -1);
|
||||
|
||||
conn_.fd = util::create_nonblock_socket(
|
||||
downstream_addr.addr.su.storage.ss_family);
|
||||
conn_.fd =
|
||||
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
|
||||
if (conn_.fd == -1) {
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
|
@ -361,8 +362,8 @@ int Http2Session::initiate_connection() {
|
|||
|
||||
rv = connect(conn_.fd,
|
||||
// TODO maybe not thread-safe?
|
||||
const_cast<sockaddr *>(&downstream_addr.addr.su.sa),
|
||||
downstream_addr.addr.len);
|
||||
const_cast<sockaddr *>(&addr_->addr.su.sa),
|
||||
addr_->addr.len);
|
||||
if (rv != 0 && errno != EINPROGRESS) {
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
|
@ -378,17 +379,16 @@ int Http2Session::initiate_connection() {
|
|||
// Without TLS and proxy.
|
||||
assert(conn_.fd == -1);
|
||||
|
||||
conn_.fd = util::create_nonblock_socket(
|
||||
downstream_addr.addr.su.storage.ss_family);
|
||||
conn_.fd =
|
||||
util::create_nonblock_socket(addr_->addr.su.storage.ss_family);
|
||||
|
||||
if (conn_.fd == -1) {
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = connect(conn_.fd,
|
||||
const_cast<sockaddr *>(&downstream_addr.addr.su.sa),
|
||||
downstream_addr.addr.len);
|
||||
rv = connect(conn_.fd, const_cast<sockaddr *>(&addr_->addr.su.sa),
|
||||
addr_->addr.len);
|
||||
if (rv != 0 && errno != EINPROGRESS) {
|
||||
connect_blocker_->on_failure();
|
||||
return -1;
|
||||
|
@ -468,29 +468,17 @@ http_parser_settings htp_hooks = {
|
|||
};
|
||||
} // namespace
|
||||
|
||||
int Http2Session::downstream_read_proxy() {
|
||||
if (rb_.rleft() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t nread =
|
||||
int Http2Session::downstream_read_proxy(const uint8_t *data, size_t datalen) {
|
||||
auto nread =
|
||||
http_parser_execute(proxy_htp_.get(), &htp_hooks,
|
||||
reinterpret_cast<const char *>(rb_.pos), rb_.rleft());
|
||||
|
||||
rb_.drain(nread);
|
||||
reinterpret_cast<const char *>(data), datalen);
|
||||
(void)nread;
|
||||
|
||||
auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
|
||||
|
||||
if (htperr == HPE_PAUSED) {
|
||||
switch (state_) {
|
||||
case Http2Session::PROXY_CONNECTED:
|
||||
// we need to increment nread by 1 since http_parser_execute()
|
||||
// returns 1 less value we expect. This means taht
|
||||
// rb_.pos[nread] points to \x0a (LF), which is last byte of
|
||||
// empty line to terminate headers. We want to eat that byte
|
||||
// here.
|
||||
rb_.drain(1);
|
||||
|
||||
// Initiate SSL/TLS handshake through established tunnel.
|
||||
if (initiate_connection() != 0) {
|
||||
return -1;
|
||||
|
@ -514,36 +502,29 @@ int Http2Session::downstream_connect_proxy() {
|
|||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "Connected to the proxy";
|
||||
}
|
||||
auto &downstream_addr =
|
||||
get_config()->downstream_addr_groups[group_].addrs[addr_idx_];
|
||||
|
||||
std::string req = "CONNECT ";
|
||||
req += downstream_addr.hostport.get();
|
||||
if (downstream_addr.port == 80 || downstream_addr.port == 443) {
|
||||
req.append(addr_->hostport.c_str(), addr_->hostport.size());
|
||||
if (addr_->port == 80 || addr_->port == 443) {
|
||||
req += ':';
|
||||
req += util::utos(downstream_addr.port);
|
||||
req += util::utos(addr_->port);
|
||||
}
|
||||
req += " HTTP/1.1\r\nHost: ";
|
||||
req += downstream_addr.host.get();
|
||||
req += addr_->host;
|
||||
req += "\r\n";
|
||||
if (get_config()->downstream_http_proxy_userinfo) {
|
||||
const auto &proxy = get_config()->downstream_http_proxy;
|
||||
if (!proxy.userinfo.empty()) {
|
||||
req += "Proxy-Authorization: Basic ";
|
||||
size_t len = strlen(get_config()->downstream_http_proxy_userinfo.get());
|
||||
req += base64::encode(get_config()->downstream_http_proxy_userinfo.get(),
|
||||
get_config()->downstream_http_proxy_userinfo.get() +
|
||||
len);
|
||||
req += base64::encode(std::begin(proxy.userinfo), std::end(proxy.userinfo));
|
||||
req += "\r\n";
|
||||
}
|
||||
req += "\r\n";
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
SSLOG(INFO, this) << "HTTP proxy request headers\n" << req;
|
||||
}
|
||||
auto nwrite = wb_.write(req.c_str(), req.size());
|
||||
if (nwrite != req.size()) {
|
||||
SSLOG(WARN, this) << "HTTP proxy request is too large";
|
||||
return -1;
|
||||
}
|
||||
on_write_ = &Http2Session::noop;
|
||||
wb_.append(req);
|
||||
|
||||
on_write_ = &Http2Session::write_noop;
|
||||
|
||||
signal_write();
|
||||
return 0;
|
||||
|
@ -567,7 +548,7 @@ void Http2Session::remove_stream_data(StreamData *sd) {
|
|||
delete sd;
|
||||
}
|
||||
|
||||
int Http2Session::submit_request(Http2DownstreamConnection *dconn, int32_t pri,
|
||||
int Http2Session::submit_request(Http2DownstreamConnection *dconn,
|
||||
const nghttp2_nv *nva, size_t nvlen,
|
||||
const nghttp2_data_provider *data_prd) {
|
||||
assert(state_ == CONNECTED);
|
||||
|
@ -605,30 +586,6 @@ int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
|
||||
int32_t pri) {
|
||||
assert(state_ == CONNECTED);
|
||||
if (!dconn) {
|
||||
return 0;
|
||||
}
|
||||
int rv;
|
||||
|
||||
// TODO Disabled temporarily
|
||||
|
||||
// rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE,
|
||||
// dconn->get_downstream()->
|
||||
// get_downstream_stream_id(), pri);
|
||||
|
||||
rv = 0;
|
||||
|
||||
if (rv < NGHTTP2_ERR_FATAL) {
|
||||
SSLOG(FATAL, this) << "nghttp2_submit_priority() failed: "
|
||||
<< nghttp2_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
nghttp2_session *Http2Session::get_session() const { return session_; }
|
||||
|
||||
bool Http2Session::get_flow_control() const { return flow_control_; }
|
||||
|
@ -752,22 +709,43 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto &resp = downstream->response();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
switch (frame->hd.type) {
|
||||
case NGHTTP2_HEADERS: {
|
||||
auto trailer = frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
|
||||
!downstream->get_expect_final_response();
|
||||
|
||||
if (resp.fs.buffer_size() + namelen + valuelen >
|
||||
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;
|
||||
}
|
||||
|
||||
if (trailer) {
|
||||
// We don't care trailer part exceeds header size limit; just
|
||||
// discard it.
|
||||
return 0;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
if (trailer) {
|
||||
// just store header fields for trailer part
|
||||
downstream->add_response_trailer(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
|
||||
resp.fs.add_trailer(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
|
||||
downstream->add_response_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
resp.fs.add_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
}
|
||||
case NGHTTP2_PUSH_PROMISE: {
|
||||
|
@ -783,10 +761,25 @@ int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
|
||||
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 >
|
||||
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
|
||||
<< ", num=" << promised_req.fs.num_fields() + 1;
|
||||
}
|
||||
|
||||
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||
}
|
||||
|
||||
auto token = http2::lookup_token(name, namelen);
|
||||
promised_downstream->add_request_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX,
|
||||
token);
|
||||
promised_req.fs.add_header(name, namelen, value, valuelen,
|
||||
flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -855,18 +848,20 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
int rv;
|
||||
|
||||
auto upstream = downstream->get_upstream();
|
||||
const auto &req = downstream->request();
|
||||
auto &resp = downstream->response();
|
||||
|
||||
auto &nva = downstream->get_response_headers();
|
||||
auto &nva = resp.fs.headers();
|
||||
|
||||
downstream->set_expect_final_response(false);
|
||||
|
||||
auto status = downstream->get_response_header(http2::HD__STATUS);
|
||||
auto status = resp.fs.header(http2::HD__STATUS);
|
||||
// libnghttp2 guarantees this exists and can be parsed
|
||||
auto status_code = http2::parse_http_status_code(status->value);
|
||||
|
||||
downstream->set_response_http_status(status_code);
|
||||
downstream->set_response_major(2);
|
||||
downstream->set_response_minor(0);
|
||||
resp.http_status = status_code;
|
||||
resp.http_major = 2;
|
||||
resp.http_minor = 0;
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
std::stringstream ss;
|
||||
|
@ -902,7 +897,7 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
downstream->check_upgrade_fulfilled();
|
||||
|
||||
if (downstream->get_upgraded()) {
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
// On upgrade sucess, both ends can send data
|
||||
if (upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0) != 0) {
|
||||
// If resume_read fails, just drop connection. Not ideal.
|
||||
|
@ -915,29 +910,24 @@ int on_response_headers(Http2Session *http2session, Downstream *downstream,
|
|||
<< "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
|
||||
}
|
||||
} else {
|
||||
auto content_length =
|
||||
downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
||||
auto content_length = resp.fs.header(http2::HD_CONTENT_LENGTH);
|
||||
if (content_length) {
|
||||
// libnghttp2 guarantees this can be parsed
|
||||
auto len = util::parse_uint(content_length->value);
|
||||
downstream->set_response_content_length(len);
|
||||
resp.fs.content_length = util::parse_uint(content_length->value);
|
||||
}
|
||||
|
||||
if (downstream->get_response_content_length() == -1 &&
|
||||
downstream->expect_response_body()) {
|
||||
if (resp.fs.content_length == -1 && downstream->expect_response_body()) {
|
||||
// Here we have response body but Content-Length is not known in
|
||||
// advance.
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
(downstream->get_request_major() == 1 &&
|
||||
downstream->get_request_minor() == 0)) {
|
||||
if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) {
|
||||
// We simply close connection for pre-HTTP/1.1 in this case.
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
} else {
|
||||
// Otherwise, use chunked encoding to keep upstream connection
|
||||
// open. In HTTP2, we are supporsed not to receive
|
||||
// transfer-encoding.
|
||||
downstream->add_response_header("transfer-encoding", "chunked",
|
||||
http2::HD_TRANSFER_ENCODING);
|
||||
resp.fs.add_header("transfer-encoding", "chunked",
|
||||
http2::HD_TRANSFER_ENCODING);
|
||||
downstream->set_chunked_response(true);
|
||||
}
|
||||
}
|
||||
|
@ -1177,7 +1167,10 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
|||
|
||||
downstream->reset_downstream_rtimer();
|
||||
|
||||
downstream->add_response_bodylen(len);
|
||||
auto &resp = downstream->response();
|
||||
|
||||
resp.recv_body_length += len;
|
||||
resp.unconsumed_body_length += len;
|
||||
|
||||
auto upstream = downstream->get_upstream();
|
||||
rv = upstream->on_downstream_body(downstream, data, len, false);
|
||||
|
@ -1191,8 +1184,6 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
|
|||
downstream->set_response_state(Downstream::MSG_RESET);
|
||||
}
|
||||
|
||||
downstream->add_response_datalen(len);
|
||||
|
||||
call_downstream_readcb(http2session, downstream);
|
||||
return 0;
|
||||
}
|
||||
|
@ -1340,9 +1331,10 @@ int Http2Session::connection_made() {
|
|||
}
|
||||
}
|
||||
|
||||
rv = nghttp2_session_client_new2(&session_,
|
||||
get_config()->http2_downstream_callbacks,
|
||||
this, get_config()->http2_client_option);
|
||||
auto &http2conf = get_config()->http2;
|
||||
|
||||
rv = nghttp2_session_client_new2(&session_, http2conf.downstream.callbacks,
|
||||
this, http2conf.downstream.option);
|
||||
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
|
@ -1353,12 +1345,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 = get_config()->http2_max_concurrent_streams;
|
||||
entry[0].value = http2conf.max_concurrent_streams;
|
||||
|
||||
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
entry[1].value = (1 << get_config()->http2_downstream_window_bits) - 1;
|
||||
entry[1].value = (1 << http2conf.downstream.window_bits) - 1;
|
||||
|
||||
if (get_config()->no_server_push || get_config()->http2_proxy ||
|
||||
if (http2conf.no_server_push || get_config()->http2_proxy ||
|
||||
get_config()->client_proxy) {
|
||||
entry[nentry].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
|
||||
entry[nentry].value = 0;
|
||||
|
@ -1371,17 +1363,17 @@ int Http2Session::connection_made() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (get_config()->http2_downstream_connection_window_bits > 16) {
|
||||
int32_t delta =
|
||||
(1 << get_config()->http2_downstream_connection_window_bits) - 1 -
|
||||
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
|
||||
auto connection_window_bits = http2conf.downstream.connection_window_bits;
|
||||
if (connection_window_bits > 16) {
|
||||
int32_t delta = (1 << connection_window_bits) - 1 -
|
||||
NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
|
||||
rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, delta);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
auto must_terminate = !get_config()->downstream_no_tls &&
|
||||
auto must_terminate = !get_config()->conn.downstream.no_tls &&
|
||||
!nghttp2::ssl::check_http2_requirement(conn_.tls.ssl);
|
||||
|
||||
if (must_terminate) {
|
||||
|
@ -1411,25 +1403,20 @@ int Http2Session::connection_made() {
|
|||
int Http2Session::do_read() { return read_(*this); }
|
||||
int Http2Session::do_write() { return write_(*this); }
|
||||
|
||||
int Http2Session::on_read() { return on_read_(*this); }
|
||||
int Http2Session::on_read(const uint8_t *data, size_t datalen) {
|
||||
return on_read_(*this, data, datalen);
|
||||
}
|
||||
|
||||
int Http2Session::on_write() { return on_write_(*this); }
|
||||
|
||||
int Http2Session::downstream_read() {
|
||||
ssize_t rv = 0;
|
||||
int Http2Session::downstream_read(const uint8_t *data, size_t datalen) {
|
||||
ssize_t rv;
|
||||
|
||||
if (rb_.rleft() > 0) {
|
||||
rv = nghttp2_session_mem_recv(
|
||||
session_, reinterpret_cast<const uint8_t *>(rb_.pos), rb_.rleft());
|
||||
|
||||
if (rv < 0) {
|
||||
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
||||
<< nghttp2_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// nghttp2_session_mem_recv() should consume all input data in
|
||||
// case of success.
|
||||
rb_.reset();
|
||||
rv = nghttp2_session_mem_recv(session_, data, datalen);
|
||||
if (rv < 0) {
|
||||
SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
|
||||
<< nghttp2_strerror(rv);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nghttp2_session_want_read(session_) == 0 &&
|
||||
|
@ -1445,19 +1432,6 @@ int Http2Session::downstream_read() {
|
|||
}
|
||||
|
||||
int Http2Session::downstream_write() {
|
||||
if (data_pending_) {
|
||||
auto n = std::min(wb_.wleft(), data_pendinglen_);
|
||||
wb_.write(data_pending_, n);
|
||||
if (n < data_pendinglen_) {
|
||||
data_pending_ += n;
|
||||
data_pendinglen_ -= n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
data_pending_ = nullptr;
|
||||
data_pendinglen_ = 0;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
const uint8_t *data;
|
||||
auto datalen = nghttp2_session_mem_send(session_, &data);
|
||||
|
@ -1470,11 +1444,10 @@ int Http2Session::downstream_write() {
|
|||
if (datalen == 0) {
|
||||
break;
|
||||
}
|
||||
auto n = wb_.write(data, datalen);
|
||||
if (n < static_cast<decltype(n)>(datalen)) {
|
||||
data_pending_ = data + n;
|
||||
data_pendinglen_ = datalen - n;
|
||||
return 0;
|
||||
wb_.append(data, datalen);
|
||||
|
||||
if (wb_.rleft() >= MAX_BUFFER_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1634,6 +1607,10 @@ int Http2Session::get_connection_check_state() const {
|
|||
|
||||
int Http2Session::noop() { return 0; }
|
||||
|
||||
int Http2Session::read_noop(const uint8_t *data, size_t datalen) { return 0; }
|
||||
|
||||
int Http2Session::write_noop() { return 0; }
|
||||
|
||||
int Http2Session::connected() {
|
||||
if (!util::check_socket_connected(conn_.fd)) {
|
||||
return -1;
|
||||
|
@ -1672,17 +1649,10 @@ int Http2Session::connected() {
|
|||
int Http2Session::read_clear() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
for (;;) {
|
||||
// we should process buffered data first before we read EOF.
|
||||
if (rb_.rleft() && on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (rb_.rleft()) {
|
||||
return 0;
|
||||
}
|
||||
rb_.reset();
|
||||
std::array<uint8_t, 16_k> buf;
|
||||
|
||||
auto nread = conn_.read_clear(rb_.last, rb_.wleft());
|
||||
for (;;) {
|
||||
auto nread = conn_.read_clear(buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
|
@ -1692,16 +1662,21 @@ int Http2Session::read_clear() {
|
|||
return nread;
|
||||
}
|
||||
|
||||
rb_.write(nread);
|
||||
if (on_read(buf.data(), nread) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Http2Session::write_clear() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
std::array<struct iovec, MAX_WR_IOVCNT> iov;
|
||||
|
||||
for (;;) {
|
||||
if (wb_.rleft() > 0) {
|
||||
auto nwrite = conn_.write_clear(wb_.pos, wb_.rleft());
|
||||
auto iovcnt = wb_.riovec(iov.data(), iov.size());
|
||||
auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
|
||||
|
||||
if (nwrite == 0) {
|
||||
return 0;
|
||||
|
@ -1715,7 +1690,6 @@ int Http2Session::write_clear() {
|
|||
continue;
|
||||
}
|
||||
|
||||
wb_.reset();
|
||||
if (on_write() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -1749,8 +1723,8 @@ int Http2Session::tls_handshake() {
|
|||
SSLOG(INFO, this) << "SSL/TLS handshake completed";
|
||||
}
|
||||
|
||||
if (!get_config()->downstream_no_tls && !get_config()->insecure &&
|
||||
check_cert() != 0) {
|
||||
if (!get_config()->tls.insecure &&
|
||||
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -1768,19 +1742,12 @@ int Http2Session::tls_handshake() {
|
|||
int Http2Session::read_tls() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
std::array<uint8_t, 16_k> buf;
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
for (;;) {
|
||||
// we should process buffered data first before we read EOF.
|
||||
if (rb_.rleft() && on_read() != 0) {
|
||||
return -1;
|
||||
}
|
||||
if (rb_.rleft()) {
|
||||
return 0;
|
||||
}
|
||||
rb_.reset();
|
||||
|
||||
auto nread = conn_.read_tls(rb_.last, rb_.wleft());
|
||||
auto nread = conn_.read_tls(buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
|
@ -1790,7 +1757,9 @@ int Http2Session::read_tls() {
|
|||
return nread;
|
||||
}
|
||||
|
||||
rb_.write(nread);
|
||||
if (on_read(buf.data(), nread) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1799,9 +1768,13 @@ int Http2Session::write_tls() {
|
|||
|
||||
ERR_clear_error();
|
||||
|
||||
struct iovec iov;
|
||||
|
||||
for (;;) {
|
||||
if (wb_.rleft() > 0) {
|
||||
auto nwrite = conn_.write_tls(wb_.pos, wb_.rleft());
|
||||
auto iovcnt = wb_.riovec(&iov, 1);
|
||||
assert(iovcnt == 1);
|
||||
auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
|
||||
|
||||
if (nwrite == 0) {
|
||||
return 0;
|
||||
|
@ -1815,7 +1788,7 @@ int Http2Session::write_tls() {
|
|||
|
||||
continue;
|
||||
}
|
||||
wb_.reset();
|
||||
|
||||
if (on_write() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -1843,7 +1816,7 @@ bool Http2Session::should_hard_fail() const {
|
|||
}
|
||||
}
|
||||
|
||||
size_t Http2Session::get_addr_idx() const { return addr_idx_; }
|
||||
const DownstreamAddr *Http2Session::get_addr() const { return addr_; }
|
||||
|
||||
size_t Http2Session::get_group() const { return group_; }
|
||||
|
||||
|
@ -1892,14 +1865,15 @@ int Http2Session::handle_downstream_push_promise(Downstream *downstream,
|
|||
|
||||
int Http2Session::handle_downstream_push_promise_complete(
|
||||
Downstream *downstream, Downstream *promised_downstream) {
|
||||
auto authority =
|
||||
promised_downstream->get_request_header(http2::HD__AUTHORITY);
|
||||
auto path = promised_downstream->get_request_header(http2::HD__PATH);
|
||||
auto method = promised_downstream->get_request_header(http2::HD__METHOD);
|
||||
auto scheme = promised_downstream->get_request_header(http2::HD__SCHEME);
|
||||
auto &promised_req = promised_downstream->request();
|
||||
|
||||
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);
|
||||
auto scheme = promised_req.fs.header(http2::HD__SCHEME);
|
||||
|
||||
if (!authority) {
|
||||
authority = promised_downstream->get_request_header(http2::HD_HOST);
|
||||
authority = promised_req.fs.header(http2::HD_HOST);
|
||||
}
|
||||
|
||||
auto method_token = http2::lookup_method_token(method->value);
|
||||
|
@ -1914,17 +1888,16 @@ 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_downstream->set_request_http2_authority(
|
||||
http2::value_to_str(authority));
|
||||
promised_downstream->set_request_method(method_token);
|
||||
promised_req.authority = http2::value_to_str(authority);
|
||||
promised_req.method = method_token;
|
||||
// libnghttp2 ensures that we don't have CONNECT method in
|
||||
// PUSH_PROMISE, and guarantees that :scheme exists.
|
||||
promised_downstream->set_request_http2_scheme(http2::value_to_str(scheme));
|
||||
promised_req.scheme = http2::value_to_str(scheme);
|
||||
|
||||
// For server-wide OPTIONS request, path is empty.
|
||||
if (method_token != HTTP_OPTIONS || path->value != "*") {
|
||||
promised_downstream->set_request_path(http2::rewrite_clean_path(
|
||||
std::begin(path->value), std::end(path->value)));
|
||||
promised_req.path = http2::rewrite_clean_path(std::begin(path->value),
|
||||
std::end(path->value));
|
||||
}
|
||||
|
||||
promised_downstream->inspect_http2_request();
|
||||
|
|
|
@ -62,8 +62,6 @@ public:
|
|||
size_t idx);
|
||||
~Http2Session();
|
||||
|
||||
int check_cert();
|
||||
|
||||
// If hard is true, all pending requests are abandoned and
|
||||
// associated ClientHandlers will be deleted.
|
||||
int disconnect(bool hard = false);
|
||||
|
@ -74,14 +72,11 @@ public:
|
|||
|
||||
void remove_stream_data(StreamData *sd);
|
||||
|
||||
int submit_request(Http2DownstreamConnection *dconn, int32_t pri,
|
||||
const nghttp2_nv *nva, size_t nvlen,
|
||||
const nghttp2_data_provider *data_prd);
|
||||
int submit_request(Http2DownstreamConnection *dconn, const nghttp2_nv *nva,
|
||||
size_t nvlen, const nghttp2_data_provider *data_prd);
|
||||
|
||||
int submit_rst_stream(int32_t stream_id, uint32_t error_code);
|
||||
|
||||
int submit_priority(Http2DownstreamConnection *dconn, int32_t pri);
|
||||
|
||||
int terminate_session(uint32_t error_code);
|
||||
|
||||
nghttp2_session *get_session() const;
|
||||
|
@ -95,7 +90,7 @@ public:
|
|||
int do_read();
|
||||
int do_write();
|
||||
|
||||
int on_read();
|
||||
int on_read(const uint8_t *data, size_t datalen);
|
||||
int on_write();
|
||||
|
||||
int connected();
|
||||
|
@ -105,13 +100,15 @@ public:
|
|||
int read_tls();
|
||||
int write_tls();
|
||||
|
||||
int downstream_read_proxy();
|
||||
int downstream_read_proxy(const uint8_t *data, size_t datalen);
|
||||
int downstream_connect_proxy();
|
||||
|
||||
int downstream_read();
|
||||
int downstream_read(const uint8_t *data, size_t datalen);
|
||||
int downstream_write();
|
||||
|
||||
int noop();
|
||||
int read_noop(const uint8_t *data, size_t datalen);
|
||||
int write_noop();
|
||||
|
||||
void signal_write();
|
||||
|
||||
|
@ -150,7 +147,7 @@ public:
|
|||
|
||||
void submit_pending_requests();
|
||||
|
||||
size_t get_addr_idx() const;
|
||||
const DownstreamAddr *get_addr() const;
|
||||
|
||||
size_t get_group() const;
|
||||
|
||||
|
@ -188,10 +185,10 @@ public:
|
|||
};
|
||||
|
||||
using ReadBuf = Buffer<8_k>;
|
||||
using WriteBuf = Buffer<32768>;
|
||||
|
||||
private:
|
||||
Connection conn_;
|
||||
DefaultMemchunks wb_;
|
||||
ev_timer settings_timer_;
|
||||
// This timer has 2 purpose: when it first timeout, set
|
||||
// connection_check_state_ = CONNECTION_CHECK_REQUIRED. After
|
||||
|
@ -201,18 +198,17 @@ private:
|
|||
DList<Http2DownstreamConnection> dconns_;
|
||||
DList<StreamData> streams_;
|
||||
std::function<int(Http2Session &)> read_, write_;
|
||||
std::function<int(Http2Session &)> on_read_, on_write_;
|
||||
std::function<int(Http2Session &, const uint8_t *, size_t)> on_read_;
|
||||
std::function<int(Http2Session &)> on_write_;
|
||||
// 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_;
|
||||
// Address of remote endpoint
|
||||
const DownstreamAddr *addr_;
|
||||
nghttp2_session *session_;
|
||||
const uint8_t *data_pending_;
|
||||
size_t data_pendinglen_;
|
||||
// index of get_config()->downstream_addrs this object uses
|
||||
size_t addr_idx_;
|
||||
size_t group_;
|
||||
// index inside group, this is used to pin frontend to certain
|
||||
// HTTP/2 backend for better throughput.
|
||||
|
@ -220,8 +216,6 @@ private:
|
|||
int state_;
|
||||
int connection_check_state_;
|
||||
bool flow_control_;
|
||||
WriteBuf wb_;
|
||||
ReadBuf rb_;
|
||||
};
|
||||
|
||||
nghttp2_session_callbacks *create_http2_downstream_callbacks();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -118,42 +118,21 @@ public:
|
|||
|
||||
int on_request_headers(Downstream *downstream, const nghttp2_frame *frame);
|
||||
|
||||
using WriteBuffer = Buffer<32_k>;
|
||||
DefaultMemchunks *get_response_buf();
|
||||
|
||||
WriteBuffer *get_response_buf();
|
||||
|
||||
void set_pending_data_downstream(Downstream *downstream, size_t n,
|
||||
size_t padlen);
|
||||
// Changes stream priority of |downstream|, which is assumed to be a
|
||||
// pushed stream.
|
||||
int adjust_pushed_stream_priority(Downstream *downstream);
|
||||
|
||||
private:
|
||||
WriteBuffer wb_;
|
||||
DefaultMemchunks wb_;
|
||||
std::unique_ptr<HttpsUpstream> pre_upstream_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ev_timer settings_timer_;
|
||||
ev_timer shutdown_timer_;
|
||||
ev_prepare prep_;
|
||||
// A response buffer used to belong to Downstream object. This is
|
||||
// moved here when response is partially written to wb_ in
|
||||
// send_data_callback, but before writing them all, Downstream
|
||||
// object was destroyed. On destruction of Downstream,
|
||||
// pending_data_downstream_ becomes nullptr.
|
||||
DefaultMemchunks pending_response_buf_;
|
||||
// Downstream object whose DATA frame payload is partillay written
|
||||
// to wb_ in send_data_callback. This field exists to keep track of
|
||||
// its lifetime. When it is destroyed, its response buffer is
|
||||
// transferred to pending_response_buf_, and this field becomes
|
||||
// nullptr.
|
||||
Downstream *pending_data_downstream_;
|
||||
ClientHandler *handler_;
|
||||
nghttp2_session *session_;
|
||||
const uint8_t *data_pending_;
|
||||
// The length of lending data to be written into wb_. If
|
||||
// data_pending_ is not nullptr, data_pending_ points to the data to
|
||||
// write. Otherwise, pending_data_downstream_->get_response_buf()
|
||||
// if pending_data_downstream_ is not nullptr, or
|
||||
// pending_response_buf_ holds data to write.
|
||||
size_t data_pendinglen_;
|
||||
size_t padding_pendinglen_;
|
||||
bool flow_control_;
|
||||
bool shutdown_handled_;
|
||||
};
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "shrpx_downstream_connection_pool.h"
|
||||
#include "shrpx_worker.h"
|
||||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_ssl.h"
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
|
||||
|
@ -54,9 +55,10 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = downstream->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
auto &resp = downstream->response();
|
||||
|
||||
// Do this so that dconn is not pooled
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
|
||||
if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
|
||||
delete handler;
|
||||
|
@ -99,7 +101,7 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
auto downstream = dconn->get_downstream();
|
||||
auto upstream = downstream->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
if (dconn->on_connect() != 0) {
|
||||
if (dconn->connected() != 0) {
|
||||
if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
|
||||
delete handler;
|
||||
}
|
||||
|
@ -110,22 +112,39 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
|
|||
} // namespace
|
||||
|
||||
HttpDownstreamConnection::HttpDownstreamConnection(
|
||||
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop)
|
||||
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop,
|
||||
Worker *worker)
|
||||
: DownstreamConnection(dconn_pool),
|
||||
conn_(loop, -1, nullptr, nullptr, get_config()->downstream_write_timeout,
|
||||
get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb,
|
||||
readcb, timeoutcb, this, get_config()->tls_dyn_rec_warmup_threshold,
|
||||
get_config()->tls_dyn_rec_idle_timeout),
|
||||
ioctrl_(&conn_.rlimit), response_htp_{0}, group_(group), addr_idx_(0),
|
||||
connected_(false) {}
|
||||
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),
|
||||
do_read_(&HttpDownstreamConnection::noop),
|
||||
do_write_(&HttpDownstreamConnection::noop),
|
||||
worker_(worker),
|
||||
ssl_ctx_(worker->get_cl_ssl_ctx()),
|
||||
addr_(nullptr),
|
||||
ioctrl_(&conn_.rlimit),
|
||||
response_htp_{0},
|
||||
group_(group) {}
|
||||
|
||||
HttpDownstreamConnection::~HttpDownstreamConnection() {}
|
||||
HttpDownstreamConnection::~HttpDownstreamConnection() {
|
||||
if (conn_.tls.ssl) {
|
||||
auto session = SSL_get1_session(conn_.tls.ssl);
|
||||
if (session) {
|
||||
worker_->cache_downstream_tls_session(addr_, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
|
||||
}
|
||||
|
||||
auto &downstreamconf = get_config()->conn.downstream;
|
||||
|
||||
if (conn_.fd == -1) {
|
||||
auto connect_blocker = client_handler_->get_connect_blocker();
|
||||
|
||||
|
@ -137,13 +156,21 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
auto worker = client_handler_->get_worker();
|
||||
auto &next_downstream = worker->get_dgrp(group_)->next;
|
||||
if (ssl_ctx_) {
|
||||
auto ssl = ssl::create_ssl(ssl_ctx_);
|
||||
if (!ssl) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
conn_.set_ssl(ssl);
|
||||
}
|
||||
|
||||
auto &next_downstream = worker_->get_dgrp(group_)->next;
|
||||
auto end = next_downstream;
|
||||
auto &addrs = get_config()->downstream_addr_groups[group_].addrs;
|
||||
auto &addrs = downstreamconf.addr_groups[group_].addrs;
|
||||
for (;;) {
|
||||
auto &addr = addrs[next_downstream];
|
||||
auto i = next_downstream;
|
||||
|
||||
if (++next_downstream >= addrs.size()) {
|
||||
next_downstream = 0;
|
||||
}
|
||||
|
@ -181,7 +208,24 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
DCLOG(INFO, this) << "Connecting to downstream server";
|
||||
}
|
||||
|
||||
addr_idx_ = i;
|
||||
addr_ = &addr;
|
||||
|
||||
if (ssl_ctx_) {
|
||||
auto sni_name = !get_config()->tls.backend_sni_name.empty()
|
||||
? StringRef(get_config()->tls.backend_sni_name)
|
||||
: StringRef(addr_->host);
|
||||
if (!util::numeric_host(sni_name.c_str())) {
|
||||
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
|
||||
}
|
||||
|
||||
auto session = worker_->reuse_downstream_tls_session(addr_);
|
||||
if (session) {
|
||||
SSL_set_session(conn_.tls.ssl, session);
|
||||
SSL_SESSION_free(session);
|
||||
}
|
||||
|
||||
conn_.prepare_client_handshake();
|
||||
}
|
||||
|
||||
ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
|
||||
ev_io_set(&conn_.rev, conn_.fd, EV_READ);
|
||||
|
@ -195,7 +239,7 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
ev_timer_again(conn_.loop, &conn_.wt);
|
||||
} else {
|
||||
// we may set read timer cb to idle_timeoutcb. Reset again.
|
||||
conn_.rt.repeat = get_config()->downstream_read_timeout;
|
||||
conn_.rt.repeat = downstreamconf.timeout.read;
|
||||
ev_set_cb(&conn_.rt, timeoutcb);
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
ev_set_cb(&conn_.rev, readcb);
|
||||
|
@ -210,89 +254,82 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
|
|||
}
|
||||
|
||||
int HttpDownstreamConnection::push_request_headers() {
|
||||
auto downstream_hostport = get_config()
|
||||
->downstream_addr_groups[group_]
|
||||
.addrs[addr_idx_]
|
||||
.hostport.get();
|
||||
auto method = downstream_->get_request_method();
|
||||
auto connect_method = method == HTTP_CONNECT;
|
||||
const auto &downstream_hostport = addr_->hostport;
|
||||
const auto &req = downstream_->request();
|
||||
|
||||
auto connect_method = req.method == HTTP_CONNECT;
|
||||
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
// For HTTP/1.0 request, there is no authority in request. In that
|
||||
// case, we use backend server's host nonetheless.
|
||||
const char *authority = downstream_hostport;
|
||||
auto &req_authority = downstream_->get_request_http2_authority();
|
||||
auto no_host_rewrite = get_config()->no_host_rewrite ||
|
||||
auto authority = StringRef(downstream_hostport);
|
||||
auto no_host_rewrite = httpconf.no_host_rewrite ||
|
||||
get_config()->http2_proxy ||
|
||||
get_config()->client_proxy || connect_method;
|
||||
|
||||
if (no_host_rewrite && !req_authority.empty()) {
|
||||
authority = req_authority.c_str();
|
||||
if (no_host_rewrite && !req.authority.empty()) {
|
||||
authority = StringRef(req.authority);
|
||||
}
|
||||
auto authoritylen = strlen(authority);
|
||||
|
||||
downstream_->set_request_downstream_host(authority);
|
||||
|
||||
downstream_->assemble_request_cookie();
|
||||
downstream_->set_request_downstream_host(authority.str());
|
||||
|
||||
auto buf = downstream_->get_request_buf();
|
||||
|
||||
// Assume that method and request path do not contain \r\n.
|
||||
auto meth = http2::to_method_string(method);
|
||||
auto meth = http2::to_method_string(req.method);
|
||||
buf->append(meth, strlen(meth));
|
||||
buf->append(" ");
|
||||
|
||||
auto &scheme = downstream_->get_request_http2_scheme();
|
||||
auto &path = downstream_->get_request_path();
|
||||
|
||||
if (connect_method) {
|
||||
buf->append(authority, authoritylen);
|
||||
buf->append(authority);
|
||||
} else if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
// Construct absolute-form request target because we are going to
|
||||
// send a request to a HTTP/1 proxy.
|
||||
assert(!scheme.empty());
|
||||
buf->append(scheme);
|
||||
assert(!req.scheme.empty());
|
||||
buf->append(req.scheme);
|
||||
buf->append("://");
|
||||
buf->append(authority, authoritylen);
|
||||
buf->append(path);
|
||||
} else if (method == HTTP_OPTIONS && path.empty()) {
|
||||
buf->append(authority);
|
||||
buf->append(req.path);
|
||||
} else if (req.method == HTTP_OPTIONS && req.path.empty()) {
|
||||
// Server-wide OPTIONS
|
||||
buf->append("*");
|
||||
} else {
|
||||
buf->append(path);
|
||||
buf->append(req.path);
|
||||
}
|
||||
buf->append(" HTTP/1.1\r\nHost: ");
|
||||
buf->append(authority, authoritylen);
|
||||
buf->append(authority);
|
||||
buf->append("\r\n");
|
||||
|
||||
http2::build_http1_headers_from_headers(buf,
|
||||
downstream_->get_request_headers());
|
||||
http2::build_http1_headers_from_headers(buf, req.fs.headers());
|
||||
|
||||
if (!downstream_->get_assembled_request_cookie().empty()) {
|
||||
auto cookie = downstream_->assemble_request_cookie();
|
||||
if (!cookie.empty()) {
|
||||
buf->append("Cookie: ");
|
||||
buf->append(downstream_->get_assembled_request_cookie());
|
||||
buf->append(cookie);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
if (!connect_method && downstream_->get_request_http2_expect_body() &&
|
||||
!downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
|
||||
if (!connect_method && req.http2_expect_body &&
|
||||
!req.fs.header(http2::HD_CONTENT_LENGTH)) {
|
||||
|
||||
downstream_->set_chunked_request(true);
|
||||
buf->append("Transfer-Encoding: chunked\r\n");
|
||||
}
|
||||
|
||||
if (downstream_->get_request_connection_close()) {
|
||||
if (req.connection_close) {
|
||||
buf->append("Connection: close\r\n");
|
||||
}
|
||||
|
||||
if (!connect_method && downstream_->get_upgrade_request()) {
|
||||
auto connection = downstream_->get_request_header(http2::HD_CONNECTION);
|
||||
if (!connect_method && req.upgrade_request) {
|
||||
auto connection = req.fs.header(http2::HD_CONNECTION);
|
||||
if (connection) {
|
||||
buf->append("Connection: ");
|
||||
buf->append((*connection).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
auto upgrade = downstream_->get_request_header(http2::HD_UPGRADE);
|
||||
auto upgrade = req.fs.header(http2::HD_UPGRADE);
|
||||
if (upgrade) {
|
||||
buf->append("Upgrade: ");
|
||||
buf->append((*upgrade).value);
|
||||
|
@ -300,16 +337,57 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
}
|
||||
}
|
||||
|
||||
auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
|
||||
if (get_config()->add_x_forwarded_for) {
|
||||
auto upstream = downstream_->get_upstream();
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
auto &fwdconf = httpconf.forwarded;
|
||||
|
||||
auto fwd =
|
||||
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);
|
||||
|
||||
if (fwdconf.params) {
|
||||
auto params = fwdconf.params;
|
||||
|
||||
if (get_config()->http2_proxy || get_config()->client_proxy ||
|
||||
connect_method) {
|
||||
params &= ~FORWARDED_PROTO;
|
||||
}
|
||||
|
||||
auto value = http::create_forwarded(params, handler->get_forwarded_by(),
|
||||
handler->get_forwarded_for(),
|
||||
req.authority, req.scheme);
|
||||
if (fwd || !value.empty()) {
|
||||
buf->append("Forwarded: ");
|
||||
if (fwd) {
|
||||
buf->append(fwd->value);
|
||||
|
||||
if (!value.empty()) {
|
||||
buf->append(", ");
|
||||
}
|
||||
}
|
||||
buf->append(value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
} else if (fwd) {
|
||||
buf->append("Forwarded: ");
|
||||
buf->append(fwd->value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
auto &xffconf = httpconf.xff;
|
||||
|
||||
auto xff = xffconf.strip_incoming ? nullptr
|
||||
: req.fs.header(http2::HD_X_FORWARDED_FOR);
|
||||
|
||||
if (xffconf.add) {
|
||||
buf->append("X-Forwarded-For: ");
|
||||
if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
if (xff) {
|
||||
buf->append((*xff).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
buf->append(client_handler_->get_ipaddr());
|
||||
buf->append("\r\n");
|
||||
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
|
||||
} else if (xff) {
|
||||
buf->append("X-Forwarded-For: ");
|
||||
buf->append((*xff).value);
|
||||
buf->append("\r\n");
|
||||
|
@ -317,12 +395,12 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!connect_method) {
|
||||
buf->append("X-Forwarded-Proto: ");
|
||||
assert(!scheme.empty());
|
||||
buf->append(scheme);
|
||||
assert(!req.scheme.empty());
|
||||
buf->append(req.scheme);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
auto via = downstream_->get_request_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
auto via = req.fs.header(http2::HD_VIA);
|
||||
if (httpconf.no_via) {
|
||||
if (via) {
|
||||
buf->append("Via: ");
|
||||
buf->append((*via).value);
|
||||
|
@ -334,12 +412,11 @@ int HttpDownstreamConnection::push_request_headers() {
|
|||
buf->append((*via).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
buf->append(http::create_via_header_value(
|
||||
downstream_->get_request_major(), downstream_->get_request_minor()));
|
||||
buf->append(http::create_via_header_value(req.http_major, req.http_minor));
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_request_headers) {
|
||||
for (auto &p : httpconf.add_request_headers) {
|
||||
buf->append(p.first);
|
||||
buf->append(": ");
|
||||
buf->append(p.second);
|
||||
|
@ -372,7 +449,7 @@ int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
|
|||
|
||||
if (chunked) {
|
||||
auto chunk_size_hex = util::utox(datalen);
|
||||
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||
output->append(chunk_size_hex);
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
|
@ -392,8 +469,10 @@ int HttpDownstreamConnection::end_upload_data() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
const auto &req = downstream_->request();
|
||||
|
||||
auto output = downstream_->get_request_buf();
|
||||
auto &trailers = downstream_->get_request_trailers();
|
||||
const auto &trailers = req.fs.trailers();
|
||||
if (trailers.empty()) {
|
||||
output->append("0\r\n\r\n");
|
||||
} else {
|
||||
|
@ -442,7 +521,7 @@ void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
|
|||
ev_set_cb(&conn_.rev, idle_readcb);
|
||||
ioctrl_.force_resume_read();
|
||||
|
||||
conn_.rt.repeat = get_config()->downstream_idle_read_timeout;
|
||||
conn_.rt.repeat = get_config()->conn.downstream.timeout.idle_read;
|
||||
ev_set_cb(&conn_.rt, idle_timeoutcb);
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
|
@ -457,7 +536,7 @@ void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
|
|||
int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
|
||||
size_t consumed) {
|
||||
if (downstream_->get_response_buf()->rleft() <=
|
||||
get_config()->downstream_request_buffer_size / 2) {
|
||||
get_config()->conn.downstream.request_buffer_size / 2) {
|
||||
ioctrl_.resume_read(reason);
|
||||
}
|
||||
|
||||
|
@ -484,13 +563,15 @@ namespace {
|
|||
int htp_hdrs_completecb(http_parser *htp) {
|
||||
auto downstream = static_cast<Downstream *>(htp->data);
|
||||
auto upstream = downstream->get_upstream();
|
||||
const auto &req = downstream->request();
|
||||
auto &resp = downstream->response();
|
||||
int rv;
|
||||
|
||||
downstream->set_response_http_status(htp->status_code);
|
||||
downstream->set_response_major(htp->http_major);
|
||||
downstream->set_response_minor(htp->http_minor);
|
||||
resp.http_status = htp->status_code;
|
||||
resp.http_major = htp->http_major;
|
||||
resp.http_minor = htp->http_minor;
|
||||
|
||||
if (downstream->index_response_headers() != 0) {
|
||||
if (resp.fs.index_headers() != 0) {
|
||||
downstream->set_response_state(Downstream::MSG_BAD_HEADER);
|
||||
return -1;
|
||||
}
|
||||
|
@ -502,7 +583,7 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
if (downstream->get_non_final_response()) {
|
||||
// Reset content-length because we reuse same Downstream for the
|
||||
// next response.
|
||||
downstream->set_response_content_length(-1);
|
||||
resp.fs.content_length = -1;
|
||||
// For non-final response code, we just call
|
||||
// on_downstream_header_complete() without changing response
|
||||
// state.
|
||||
|
@ -516,13 +597,13 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
downstream->set_response_connection_close(!http_should_keep_alive(htp));
|
||||
resp.connection_close = !http_should_keep_alive(htp);
|
||||
downstream->set_response_state(Downstream::HEADER_COMPLETE);
|
||||
downstream->inspect_http1_response();
|
||||
if (downstream->get_upgraded()) {
|
||||
// content-length must be ignored for upgraded connection.
|
||||
downstream->set_response_content_length(-1);
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.fs.content_length = -1;
|
||||
resp.connection_close = true;
|
||||
// transfer-encoding not applied to upgraded connection
|
||||
downstream->set_chunked_response(false);
|
||||
}
|
||||
|
@ -542,7 +623,7 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
}
|
||||
}
|
||||
|
||||
unsigned int status = downstream->get_response_http_status();
|
||||
auto status = resp.http_status;
|
||||
// Ignore the response body. HEAD response may contain
|
||||
// Content-Length or Transfer-Encoding: chunked. Some server send
|
||||
// 304 status code with nonzero Content-Length, but without response
|
||||
|
@ -551,29 +632,78 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
|
||||
// TODO It seems that the cases other than HEAD are handled by
|
||||
// http-parser. Need test.
|
||||
return downstream->get_request_method() == HTTP_HEAD ||
|
||||
(100 <= status && status <= 199) || status == 204 ||
|
||||
status == 304
|
||||
return req.method == HTTP_HEAD || (100 <= status && status <= 199) ||
|
||||
status == 204 || status == 304
|
||||
? 1
|
||||
: 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int ensure_header_field_buffer(const Downstream *downstream,
|
||||
const HttpConfig &httpconf, size_t len) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (resp.fs.buffer_size() + len > httpconf.response_header_field_buffer) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream) << "Too large header header field size="
|
||||
<< resp.fs.buffer_size() + len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int ensure_max_header_fields(const Downstream *downstream,
|
||||
const HttpConfig &httpconf) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (resp.fs.num_fields() >= httpconf.max_response_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, downstream)
|
||||
<< "Too many header field num=" << resp.fs.num_fields() + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
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;
|
||||
|
||||
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||
if (downstream->get_response_header_key_prev()) {
|
||||
downstream->append_last_response_header_key(data, len);
|
||||
if (resp.fs.header_key_prev()) {
|
||||
resp.fs.append_last_header_key(data, len);
|
||||
} else {
|
||||
downstream->add_response_header(std::string(data, len), "");
|
||||
if (ensure_max_header_fields(downstream, httpconf) != 0) {
|
||||
return -1;
|
||||
}
|
||||
resp.fs.add_header(std::string(data, len), "");
|
||||
}
|
||||
} else {
|
||||
// trailer part
|
||||
if (downstream->get_response_trailer_key_prev()) {
|
||||
downstream->append_last_response_trailer_key(data, len);
|
||||
if (resp.fs.trailer_key_prev()) {
|
||||
resp.fs.append_last_trailer_key(data, len);
|
||||
} else {
|
||||
downstream->add_response_trailer(std::string(data, len), "");
|
||||
if (ensure_max_header_fields(downstream, httpconf) != 0) {
|
||||
// Could not ignore this trailer field easily, since we may
|
||||
// get its value in htp_hdr_valcb, and it will be added to
|
||||
// wrong place or crash if trailer fields are currently empty.
|
||||
return -1;
|
||||
}
|
||||
resp.fs.add_trailer(std::string(data, len), "");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -583,18 +713,17 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
|||
namespace {
|
||||
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||
auto downstream = static_cast<Downstream *>(htp->data);
|
||||
auto &resp = downstream->response();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (ensure_header_field_buffer(downstream, httpconf, len) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_response_state() == Downstream::INITIAL) {
|
||||
if (downstream->get_response_header_key_prev()) {
|
||||
downstream->set_last_response_header_value(data, len);
|
||||
} else {
|
||||
downstream->append_last_response_header_value(data, len);
|
||||
}
|
||||
resp.fs.append_last_header_value(data, len);
|
||||
} else {
|
||||
if (downstream->get_response_trailer_key_prev()) {
|
||||
downstream->set_last_response_trailer_value(data, len);
|
||||
} else {
|
||||
downstream->append_last_response_trailer_value(data, len);
|
||||
}
|
||||
resp.fs.append_last_trailer_value(data, len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -603,8 +732,9 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
|||
namespace {
|
||||
int htp_bodycb(http_parser *htp, const char *data, size_t len) {
|
||||
auto downstream = static_cast<Downstream *>(htp->data);
|
||||
auto &resp = downstream->response();
|
||||
|
||||
downstream->add_response_bodylen(len);
|
||||
resp.recv_body_length += len;
|
||||
|
||||
return downstream->get_upstream()->on_downstream_body(
|
||||
downstream, reinterpret_cast<const uint8_t *>(data), len, true);
|
||||
|
@ -652,44 +782,13 @@ http_parser_settings htp_hooks = {
|
|||
};
|
||||
} // namespace
|
||||
|
||||
int HttpDownstreamConnection::on_read() {
|
||||
if (!connected_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::read_clear() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
std::array<uint8_t, 8_k> buf;
|
||||
std::array<uint8_t, 16_k> buf;
|
||||
int rv;
|
||||
|
||||
if (downstream_->get_upgraded()) {
|
||||
// For upgraded connection, just pass data to the upstream.
|
||||
for (;;) {
|
||||
auto nread = conn_.read_clear(buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nread < 0) {
|
||||
return nread;
|
||||
}
|
||||
|
||||
rv = downstream_->get_upstream()->on_downstream_body(
|
||||
downstream_, buf.data(), nread, true);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
auto nread = conn_.read_clear(buf.data(), buf.size());
|
||||
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -698,59 +797,18 @@ int HttpDownstreamConnection::on_read() {
|
|||
return nread;
|
||||
}
|
||||
|
||||
auto nproc =
|
||||
http_parser_execute(&response_htp_, &htp_hooks,
|
||||
reinterpret_cast<char *>(buf.data()), nread);
|
||||
|
||||
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
|
||||
|
||||
if (htperr != HPE_OK) {
|
||||
// Handling early return (in other words, response was hijacked
|
||||
// by mruby scripting).
|
||||
if (downstream_->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return SHRPX_ERR_DCONN_CANCELED;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "HTTP parser failure: "
|
||||
<< "(" << http_errno_name(htperr) << ") "
|
||||
<< http_errno_description(htperr);
|
||||
}
|
||||
|
||||
return -1;
|
||||
rv = process_input(buf.data(), nread);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
if (!ev_is_active(&conn_.rev)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (downstream_->get_upgraded()) {
|
||||
if (nproc < static_cast<size_t>(nread)) {
|
||||
// Data from buf.data() + nproc are for upgraded protocol.
|
||||
rv = downstream_->get_upstream()->on_downstream_body(
|
||||
downstream_, buf.data() + nproc, nread - nproc, true);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// call on_read(), so that we can process data left in buffer as
|
||||
// upgrade.
|
||||
return on_read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_write() {
|
||||
if (!connected_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::write_clear() {
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
auto upstream = downstream_->get_upstream();
|
||||
|
@ -778,14 +836,181 @@ int HttpDownstreamConnection::on_write() {
|
|||
ev_timer_stop(conn_.loop, &conn_.wt);
|
||||
|
||||
if (input->rleft() == 0) {
|
||||
auto &req = downstream_->request();
|
||||
|
||||
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
|
||||
downstream_->get_request_datalen());
|
||||
req.unconsumed_body_length);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_connect() {
|
||||
int HttpDownstreamConnection::tls_handshake() {
|
||||
ERR_clear_error();
|
||||
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
auto rv = conn_.tls_handshake();
|
||||
if (rv == SHRPX_ERR_INPROGRESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rv < 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "SSL/TLS handshake completed";
|
||||
}
|
||||
|
||||
if (!get_config()->tls.insecure &&
|
||||
ssl::check_cert(conn_.tls.ssl, addr_) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
do_read_ = &HttpDownstreamConnection::read_tls;
|
||||
do_write_ = &HttpDownstreamConnection::write_tls;
|
||||
|
||||
// TODO Check negotiated ALPN
|
||||
|
||||
return on_write();
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::read_tls() {
|
||||
ERR_clear_error();
|
||||
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
std::array<uint8_t, 16_k> buf;
|
||||
int rv;
|
||||
|
||||
for (;;) {
|
||||
auto nread = conn_.read_tls(buf.data(), buf.size());
|
||||
if (nread == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nread < 0) {
|
||||
return nread;
|
||||
}
|
||||
|
||||
rv = process_input(buf.data(), nread);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!ev_is_active(&conn_.rev)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::write_tls() {
|
||||
ERR_clear_error();
|
||||
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
auto upstream = downstream_->get_upstream();
|
||||
auto input = downstream_->get_request_buf();
|
||||
|
||||
struct iovec iov;
|
||||
|
||||
while (input->rleft() > 0) {
|
||||
auto iovcnt = input->riovec(&iov, 1);
|
||||
assert(iovcnt == 1);
|
||||
auto nwrite = conn_.write_tls(iov.iov_base, iov.iov_len);
|
||||
|
||||
if (nwrite == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nwrite < 0) {
|
||||
return nwrite;
|
||||
}
|
||||
|
||||
input->drain(nwrite);
|
||||
}
|
||||
|
||||
conn_.wlimit.stopw();
|
||||
ev_timer_stop(conn_.loop, &conn_.wt);
|
||||
|
||||
if (input->rleft() == 0) {
|
||||
auto &req = downstream_->request();
|
||||
|
||||
upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
|
||||
req.unconsumed_body_length);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::process_input(const uint8_t *data,
|
||||
size_t datalen) {
|
||||
int rv;
|
||||
|
||||
if (downstream_->get_upgraded()) {
|
||||
// For upgraded connection, just pass data to the upstream.
|
||||
rv = downstream_->get_upstream()->on_downstream_body(downstream_, data,
|
||||
datalen, true);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto nproc =
|
||||
http_parser_execute(&response_htp_, &htp_hooks,
|
||||
reinterpret_cast<const char *>(data), datalen);
|
||||
|
||||
auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
|
||||
|
||||
if (htperr != HPE_OK) {
|
||||
// Handling early return (in other words, response was hijacked by
|
||||
// mruby scripting).
|
||||
if (downstream_->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
return SHRPX_ERR_DCONN_CANCELED;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DCLOG(INFO, this) << "HTTP parser failure: "
|
||||
<< "(" << http_errno_name(htperr) << ") "
|
||||
<< http_errno_description(htperr);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream_->get_upgraded()) {
|
||||
if (nproc < datalen) {
|
||||
// Data from data + nproc are for upgraded protocol.
|
||||
rv = downstream_->get_upstream()->on_downstream_body(
|
||||
downstream_, data + nproc, datalen - nproc, true);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (downstream_->response_buf_full()) {
|
||||
downstream_->pause_read(SHRPX_NO_BUFFER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::connected() {
|
||||
auto connect_blocker = client_handler_->get_connect_blocker();
|
||||
|
||||
if (!util::check_socket_connected(conn_.fd)) {
|
||||
|
@ -800,18 +1025,33 @@ int HttpDownstreamConnection::on_connect() {
|
|||
return -1;
|
||||
}
|
||||
|
||||
connected_ = true;
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
DLOG(INFO, this) << "Connected to downstream host";
|
||||
}
|
||||
|
||||
connect_blocker->on_success();
|
||||
|
||||
conn_.rlimit.startw();
|
||||
ev_timer_again(conn_.loop, &conn_.rt);
|
||||
|
||||
ev_set_cb(&conn_.wev, writecb);
|
||||
|
||||
if (conn_.tls.ssl) {
|
||||
do_read_ = &HttpDownstreamConnection::tls_handshake;
|
||||
do_write_ = &HttpDownstreamConnection::tls_handshake;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
do_read_ = &HttpDownstreamConnection::read_clear;
|
||||
do_write_ = &HttpDownstreamConnection::write_clear;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HttpDownstreamConnection::on_read() { return do_read_(*this); }
|
||||
|
||||
int HttpDownstreamConnection::on_write() { return do_write_(*this); }
|
||||
|
||||
void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
|
||||
|
||||
void HttpDownstreamConnection::signal_write() {
|
||||
|
@ -820,4 +1060,6 @@ void HttpDownstreamConnection::signal_write() {
|
|||
|
||||
size_t HttpDownstreamConnection::get_group() const { return group_; }
|
||||
|
||||
int HttpDownstreamConnection::noop() { return 0; }
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -36,11 +36,12 @@
|
|||
namespace shrpx {
|
||||
|
||||
class DownstreamConnectionPool;
|
||||
class Worker;
|
||||
|
||||
class HttpDownstreamConnection : public DownstreamConnection {
|
||||
public:
|
||||
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group,
|
||||
struct ev_loop *loop);
|
||||
struct ev_loop *loop, Worker *worker);
|
||||
virtual ~HttpDownstreamConnection();
|
||||
virtual int attach_downstream(Downstream *downstream);
|
||||
virtual void detach_downstream(Downstream *downstream);
|
||||
|
@ -57,22 +58,34 @@ public:
|
|||
virtual int on_write();
|
||||
|
||||
virtual void on_upstream_change(Upstream *upstream);
|
||||
virtual int on_priority_change(int32_t pri) { return 0; }
|
||||
virtual size_t get_group() const;
|
||||
|
||||
virtual bool poolable() const { return true; }
|
||||
|
||||
int on_connect();
|
||||
int read_clear();
|
||||
int write_clear();
|
||||
int read_tls();
|
||||
int write_tls();
|
||||
|
||||
int process_input(const uint8_t *data, size_t datalen);
|
||||
int tls_handshake();
|
||||
|
||||
int connected();
|
||||
void signal_write();
|
||||
|
||||
int noop();
|
||||
|
||||
private:
|
||||
Connection conn_;
|
||||
std::function<int(HttpDownstreamConnection &)> do_read_, do_write_;
|
||||
Worker *worker_;
|
||||
// nullptr if TLS is not used.
|
||||
SSL_CTX *ssl_ctx_;
|
||||
// Address of remote endpoint
|
||||
const DownstreamAddr *addr_;
|
||||
IOControl ioctrl_;
|
||||
http_parser response_htp_;
|
||||
size_t group_;
|
||||
// index of get_config()->downstream_addrs this object is using
|
||||
size_t addr_idx_;
|
||||
bool connected_;
|
||||
};
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 "shrpx_http_test.h"
|
||||
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif // HAVE_UNISTD_H
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
|
||||
#include "shrpx_http.h"
|
||||
#include "shrpx_config.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
void test_shrpx_http_create_forwarded(void) {
|
||||
CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
|
||||
"proto=https" ==
|
||||
http::create_forwarded(
|
||||
FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
|
||||
"example.com:3000", "[::1]", "www.example.com", "https"));
|
||||
|
||||
CU_ASSERT("for=192.168.0.1" == http::create_forwarded(FORWARDED_FOR, "alpha",
|
||||
"192.168.0.1", "bravo",
|
||||
"charlie"));
|
||||
|
||||
CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
|
||||
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "_hidden",
|
||||
"[::1]", "", ""));
|
||||
|
||||
CU_ASSERT("by=\"[::1]\";for=_hidden" ==
|
||||
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "[::1]",
|
||||
"_hidden", "", ""));
|
||||
|
||||
CU_ASSERT("" == http::create_forwarded(FORWARDED_BY | FORWARDED_FOR |
|
||||
FORWARDED_HOST | FORWARDED_PROTO,
|
||||
"", "", "", ""));
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 SHRPX_HTTP_TEST_H
|
||||
#define SHRPX_HTTP_TEST_H
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif // HAVE_CONFIG_H
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
void test_shrpx_http_create_forwarded(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
#endif // SHRPX_HTTP_TEST_H
|
|
@ -49,7 +49,8 @@ using namespace nghttp2;
|
|||
namespace shrpx {
|
||||
|
||||
HttpsUpstream::HttpsUpstream(ClientHandler *handler)
|
||||
: handler_(handler), current_header_length_(0),
|
||||
: handler_(handler),
|
||||
current_header_length_(0),
|
||||
ioctrl_(handler->get_rlimit()) {
|
||||
http_parser_init(&htp_, HTTP_REQUEST);
|
||||
htp_.data = this;
|
||||
|
@ -71,9 +72,7 @@ int htp_msg_begin(http_parser *htp) {
|
|||
|
||||
auto handler = upstream->get_client_handler();
|
||||
|
||||
// TODO specify 0 as priority for now
|
||||
auto downstream =
|
||||
make_unique<Downstream>(upstream, handler->get_mcpool(), 0, 0);
|
||||
auto downstream = make_unique<Downstream>(upstream, handler->get_mcpool(), 0);
|
||||
|
||||
upstream->attach_downstream(std::move(downstream));
|
||||
|
||||
|
@ -85,25 +84,28 @@ namespace {
|
|||
int htp_uricb(http_parser *htp, const char *data, size_t len) {
|
||||
auto upstream = static_cast<HttpsUpstream *>(htp->data);
|
||||
auto downstream = upstream->get_downstream();
|
||||
auto &req = downstream->request();
|
||||
|
||||
// We happen to have the same value for method token.
|
||||
downstream->set_request_method(htp->method);
|
||||
req.method = htp->method;
|
||||
|
||||
if (downstream->get_request_headers_sum() + len >
|
||||
get_config()->header_field_buffer) {
|
||||
if (req.fs.buffer_size() + len >
|
||||
get_config()->http.request_header_field_buffer) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Too large URI size="
|
||||
<< downstream->get_request_headers_sum() + len;
|
||||
<< req.fs.buffer_size() + len;
|
||||
}
|
||||
assert(downstream->get_request_state() == Downstream::INITIAL);
|
||||
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
|
||||
return -1;
|
||||
}
|
||||
downstream->add_request_headers_sum(len);
|
||||
if (downstream->get_request_method() == HTTP_CONNECT) {
|
||||
downstream->append_request_http2_authority(data, len);
|
||||
|
||||
req.fs.add_extra_buffer_size(len);
|
||||
|
||||
if (req.method == HTTP_CONNECT) {
|
||||
req.authority.append(data, len);
|
||||
} else {
|
||||
downstream->append_request_path(data, len);
|
||||
req.path.append(data, len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -114,11 +116,13 @@ namespace {
|
|||
int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
||||
auto upstream = static_cast<HttpsUpstream *>(htp->data);
|
||||
auto downstream = upstream->get_downstream();
|
||||
if (downstream->get_request_headers_sum() + len >
|
||||
get_config()->header_field_buffer) {
|
||||
auto &req = downstream->request();
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (req.fs.buffer_size() + len > httpconf.request_header_field_buffer) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Too large header block size="
|
||||
<< downstream->get_request_headers_sum() + len;
|
||||
<< req.fs.buffer_size() + len;
|
||||
}
|
||||
if (downstream->get_request_state() == Downstream::INITIAL) {
|
||||
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
|
||||
|
@ -126,36 +130,33 @@ int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
|
|||
return -1;
|
||||
}
|
||||
if (downstream->get_request_state() == Downstream::INITIAL) {
|
||||
if (downstream->get_request_header_key_prev()) {
|
||||
downstream->append_last_request_header_key(data, len);
|
||||
if (req.fs.header_key_prev()) {
|
||||
req.fs.append_last_header_key(data, len);
|
||||
} else {
|
||||
if (downstream->get_request_headers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Too many header field num="
|
||||
<< downstream->get_request_headers().size() + 1;
|
||||
ULOG(INFO, upstream)
|
||||
<< "Too many header field num=" << req.fs.num_fields() + 1;
|
||||
}
|
||||
downstream->set_request_state(
|
||||
Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
|
||||
return -1;
|
||||
}
|
||||
downstream->add_request_header(std::string(data, len), "");
|
||||
req.fs.add_header(std::string(data, len), "");
|
||||
}
|
||||
} else {
|
||||
// trailer part
|
||||
if (downstream->get_request_trailer_key_prev()) {
|
||||
downstream->append_last_request_trailer_key(data, len);
|
||||
if (req.fs.trailer_key_prev()) {
|
||||
req.fs.append_last_trailer_key(data, len);
|
||||
} else {
|
||||
if (downstream->get_request_headers().size() +
|
||||
downstream->get_request_trailers().size() >=
|
||||
get_config()->max_header_fields) {
|
||||
if (req.fs.num_fields() >= httpconf.max_request_header_fields) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Too many header field num="
|
||||
<< downstream->get_request_headers().size() + 1;
|
||||
ULOG(INFO, upstream)
|
||||
<< "Too many header field num=" << req.fs.num_fields() + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
downstream->add_request_trailer(std::string(data, len), "");
|
||||
req.fs.add_trailer(std::string(data, len), "");
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -166,11 +167,13 @@ namespace {
|
|||
int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
||||
auto upstream = static_cast<HttpsUpstream *>(htp->data);
|
||||
auto downstream = upstream->get_downstream();
|
||||
if (downstream->get_request_headers_sum() + len >
|
||||
get_config()->header_field_buffer) {
|
||||
auto &req = downstream->request();
|
||||
|
||||
if (req.fs.buffer_size() + len >
|
||||
get_config()->http.request_header_field_buffer) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Too large header block size="
|
||||
<< downstream->get_request_headers_sum() + len;
|
||||
<< req.fs.buffer_size() + len;
|
||||
}
|
||||
if (downstream->get_request_state() == Downstream::INITIAL) {
|
||||
downstream->set_request_state(Downstream::HTTP1_REQUEST_HEADER_TOO_LARGE);
|
||||
|
@ -178,30 +181,23 @@ int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
|
|||
return -1;
|
||||
}
|
||||
if (downstream->get_request_state() == Downstream::INITIAL) {
|
||||
if (downstream->get_request_header_key_prev()) {
|
||||
downstream->set_last_request_header_value(data, len);
|
||||
} else {
|
||||
downstream->append_last_request_header_value(data, len);
|
||||
}
|
||||
req.fs.append_last_header_value(data, len);
|
||||
} else {
|
||||
if (downstream->get_request_trailer_key_prev()) {
|
||||
downstream->set_last_request_trailer_value(data, len);
|
||||
} else {
|
||||
downstream->append_last_request_trailer_value(data, len);
|
||||
}
|
||||
req.fs.append_last_trailer_value(data, len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
||||
void rewrite_request_host_path_from_uri(Request &req, const char *uri,
|
||||
http_parser_url &u) {
|
||||
assert(u.field_set & (1 << UF_HOST));
|
||||
|
||||
auto &authority = req.authority;
|
||||
authority.clear();
|
||||
// As per https://tools.ietf.org/html/rfc7230#section-5.4, we
|
||||
// rewrite host header field with authority component.
|
||||
std::string authority;
|
||||
http2::copy_url_component(authority, &u, UF_HOST, uri);
|
||||
// TODO properly check IPv6 numeric address
|
||||
if (authority.find(':') != std::string::npos) {
|
||||
|
@ -212,23 +208,20 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
|||
authority += ':';
|
||||
authority += util::utos(u.port);
|
||||
}
|
||||
downstream->set_request_http2_authority(authority);
|
||||
|
||||
std::string scheme;
|
||||
http2::copy_url_component(scheme, &u, UF_SCHEMA, uri);
|
||||
downstream->set_request_http2_scheme(std::move(scheme));
|
||||
http2::copy_url_component(req.scheme, &u, UF_SCHEMA, uri);
|
||||
|
||||
std::string path;
|
||||
if (u.field_set & (1 << UF_PATH)) {
|
||||
http2::copy_url_component(path, &u, UF_PATH, uri);
|
||||
} else if (downstream->get_request_method() == HTTP_OPTIONS) {
|
||||
} else if (req.method == HTTP_OPTIONS) {
|
||||
// Server-wide OPTIONS takes following form in proxy request:
|
||||
//
|
||||
// OPTIONS http://example.org HTTP/1.1
|
||||
//
|
||||
// Notice that no slash after authority. See
|
||||
// http://tools.ietf.org/html/rfc7230#section-5.3.4
|
||||
downstream->set_request_path("");
|
||||
req.path = "";
|
||||
// we ignore query component here
|
||||
return;
|
||||
} else {
|
||||
|
@ -240,10 +233,9 @@ void rewrite_request_host_path_from_uri(Downstream *downstream, const char *uri,
|
|||
path.append(uri + fdata.off, fdata.len);
|
||||
}
|
||||
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
downstream->set_request_path(std::move(path));
|
||||
req.path = std::move(path);
|
||||
} else {
|
||||
downstream->set_request_path(
|
||||
http2::rewrite_clean_path(std::begin(path), std::end(path)));
|
||||
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -256,46 +248,57 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
ULOG(INFO, upstream) << "HTTP request headers completed";
|
||||
}
|
||||
auto downstream = upstream->get_downstream();
|
||||
auto &req = downstream->request();
|
||||
|
||||
downstream->set_request_major(htp->http_major);
|
||||
downstream->set_request_minor(htp->http_minor);
|
||||
req.http_major = htp->http_major;
|
||||
req.http_minor = htp->http_minor;
|
||||
|
||||
downstream->set_request_connection_close(!http_should_keep_alive(htp));
|
||||
req.connection_close = !http_should_keep_alive(htp);
|
||||
|
||||
auto method = downstream->get_request_method();
|
||||
auto method = req.method;
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
std::stringstream ss;
|
||||
ss << http2::to_method_string(method) << " "
|
||||
<< (method == HTTP_CONNECT ? downstream->get_request_http2_authority()
|
||||
: downstream->get_request_path()) << " "
|
||||
<< "HTTP/" << downstream->get_request_major() << "."
|
||||
<< downstream->get_request_minor() << "\n";
|
||||
const auto &headers = downstream->get_request_headers();
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
ss << TTY_HTTP_HD << headers[i].name << TTY_RST << ": "
|
||||
<< headers[i].value << "\n";
|
||||
<< (method == HTTP_CONNECT ? req.authority : req.path) << " "
|
||||
<< "HTTP/" << req.http_major << "." << req.http_minor << "\n";
|
||||
|
||||
for (const auto &kv : req.fs.headers()) {
|
||||
ss << TTY_HTTP_HD << kv.name << TTY_RST << ": " << kv.value << "\n";
|
||||
}
|
||||
|
||||
ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
|
||||
}
|
||||
|
||||
if (downstream->index_request_headers() != 0) {
|
||||
if (req.fs.index_headers() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream->get_request_major() == 1 &&
|
||||
downstream->get_request_minor() == 1 &&
|
||||
!downstream->get_request_header(http2::HD_HOST)) {
|
||||
auto host = req.fs.header(http2::HD_HOST);
|
||||
|
||||
if (req.http_major == 1 && req.http_minor == 1 && !host) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (host) {
|
||||
const auto &value = host->value;
|
||||
// Not allow at least '"' or '\' in host. They are illegal in
|
||||
// authority component, also they cause headaches when we put them
|
||||
// in quoted-string.
|
||||
if (std::find_if(std::begin(value), std::end(value), [](char c) {
|
||||
return c == '"' || c == '\\';
|
||||
}) != std::end(value)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
downstream->inspect_http1_request();
|
||||
|
||||
if (method != HTTP_CONNECT) {
|
||||
http_parser_url u{};
|
||||
// make a copy of request path, since we may set request path
|
||||
// while we are refering to original request path.
|
||||
auto path = downstream->get_request_path();
|
||||
auto path = req.path;
|
||||
rv = http_parser_parse_url(path.c_str(), path.size(), 0, &u);
|
||||
if (rv != 0) {
|
||||
// Expect to respond with 400 bad request
|
||||
|
@ -308,25 +311,25 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
req.no_authority = true;
|
||||
|
||||
if (method == HTTP_OPTIONS && path == "*") {
|
||||
downstream->set_request_path("");
|
||||
req.path = "";
|
||||
} else {
|
||||
downstream->set_request_path(
|
||||
http2::rewrite_clean_path(std::begin(path), std::end(path)));
|
||||
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path));
|
||||
}
|
||||
|
||||
auto host = downstream->get_request_header(http2::HD_HOST);
|
||||
if (host) {
|
||||
downstream->set_request_http2_authority(host->value);
|
||||
req.authority = host->value;
|
||||
}
|
||||
|
||||
if (upstream->get_client_handler()->get_ssl()) {
|
||||
downstream->set_request_http2_scheme("https");
|
||||
req.scheme = "https";
|
||||
} else {
|
||||
downstream->set_request_http2_scheme("http");
|
||||
req.scheme = "http";
|
||||
}
|
||||
} else {
|
||||
rewrite_request_host_path_from_uri(downstream, path.c_str(), u);
|
||||
rewrite_request_host_path_from_uri(req, path.c_str(), u);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,8 +340,10 @@ int htp_hdrs_completecb(http_parser *htp) {
|
|||
auto worker = handler->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (mruby_ctx->run_on_request_proc(downstream) != 0) {
|
||||
downstream->set_response_http_status(500);
|
||||
resp.http_status = 500;
|
||||
return -1;
|
||||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
@ -527,7 +532,7 @@ int HttpsUpstream::on_read() {
|
|||
if (htperr == HPE_INVALID_METHOD) {
|
||||
status_code = 501;
|
||||
} else if (downstream) {
|
||||
status_code = downstream->get_response_http_status();
|
||||
status_code = downstream->response().http_status;
|
||||
if (status_code == 0) {
|
||||
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
||||
status_code = 503;
|
||||
|
@ -569,20 +574,8 @@ int HttpsUpstream::on_write() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto dconn = downstream->get_downstream_connection();
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
if (output->rleft() == 0 && dconn &&
|
||||
downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||
if (downstream->resume_read(SHRPX_NO_BUFFER,
|
||||
downstream->get_response_datalen()) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (downstream_read(dconn) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
const auto &resp = downstream->response();
|
||||
|
||||
if (output->rleft() > 0) {
|
||||
return 0;
|
||||
|
@ -591,7 +584,7 @@ int HttpsUpstream::on_write() {
|
|||
// We need to postpone detachment until all data are sent so that
|
||||
// we can notify nghttp2 library all data consumed.
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
if (downstream->get_response_connection_close() ||
|
||||
if (resp.connection_close ||
|
||||
downstream->get_request_state() != Downstream::MSG_COMPLETE) {
|
||||
// Connection close
|
||||
downstream->pop_downstream_connection();
|
||||
|
@ -612,8 +605,7 @@ int HttpsUpstream::on_write() {
|
|||
}
|
||||
}
|
||||
|
||||
return downstream->resume_read(SHRPX_NO_BUFFER,
|
||||
downstream->get_response_datalen());
|
||||
return downstream->resume_read(SHRPX_NO_BUFFER, resp.unconsumed_body_length);
|
||||
}
|
||||
|
||||
int HttpsUpstream::on_event() { return 0; }
|
||||
|
@ -766,32 +758,31 @@ int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
|
|||
|
||||
int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
||||
size_t bodylen) {
|
||||
auto major = downstream->get_request_major();
|
||||
auto minor = downstream->get_request_minor();
|
||||
const auto &req = downstream->request();
|
||||
auto &resp = downstream->response();
|
||||
|
||||
auto connection_close = false;
|
||||
if (major <= 0 || (major == 1 && minor == 0)) {
|
||||
if (req.http_major <= 0 || (req.http_major == 1 && req.http_minor == 0)) {
|
||||
connection_close = true;
|
||||
} else {
|
||||
auto c = downstream->get_response_header(http2::HD_CONNECTION);
|
||||
auto c = resp.fs.header(http2::HD_CONNECTION);
|
||||
if (c && util::strieq_l("close", c->value)) {
|
||||
connection_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection_close) {
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
handler_->set_should_close_after_write(true);
|
||||
}
|
||||
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
output->append("HTTP/1.1 ");
|
||||
output->append(
|
||||
http2::get_status_string(downstream->get_response_http_status()));
|
||||
output->append(http2::get_status_string(resp.http_status));
|
||||
output->append("\r\n");
|
||||
|
||||
for (auto &kv : downstream->get_response_headers()) {
|
||||
for (auto &kv : resp.fs.headers()) {
|
||||
if (kv.name.empty() || kv.name[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
|
@ -801,10 +792,9 @@ int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|||
output->append("\r\n");
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_SERVER)) {
|
||||
if (!resp.fs.header(http2::HD_SERVER)) {
|
||||
output->append("Server: ");
|
||||
output->append(get_config()->server_name,
|
||||
strlen(get_config()->server_name));
|
||||
output->append(get_config()->http.server_name);
|
||||
output->append("\r\n");
|
||||
}
|
||||
|
||||
|
@ -812,7 +802,7 @@ int HttpsUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|||
|
||||
output->append(body, bodylen);
|
||||
|
||||
downstream->add_response_sent_bodylen(bodylen);
|
||||
downstream->response_sent_body_length += bodylen;
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
return 0;
|
||||
|
@ -823,37 +813,38 @@ void HttpsUpstream::error_reply(unsigned int status_code) {
|
|||
auto downstream = get_downstream();
|
||||
|
||||
if (!downstream) {
|
||||
attach_downstream(
|
||||
make_unique<Downstream>(this, handler_->get_mcpool(), 1, 1));
|
||||
attach_downstream(make_unique<Downstream>(this, handler_->get_mcpool(), 1));
|
||||
downstream = get_downstream();
|
||||
}
|
||||
|
||||
downstream->set_response_http_status(status_code);
|
||||
auto &resp = downstream->response();
|
||||
|
||||
resp.http_status = status_code;
|
||||
// we are going to close connection for both frontend and backend in
|
||||
// error condition. This is safest option.
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
handler_->set_should_close_after_write(true);
|
||||
|
||||
auto output = downstream->get_response_buf();
|
||||
|
||||
output->append("HTTP/1.1 ");
|
||||
auto status_str = http2::get_status_string(status_code);
|
||||
output->append(status_str.c_str(), status_str.size());
|
||||
output->append(status_str);
|
||||
output->append("\r\nServer: ");
|
||||
output->append(get_config()->server_name, strlen(get_config()->server_name));
|
||||
output->append(get_config()->http.server_name);
|
||||
output->append("\r\nContent-Length: ");
|
||||
auto cl = util::utos(html.size());
|
||||
output->append(cl.c_str(), cl.size());
|
||||
output->append(cl);
|
||||
output->append("\r\nDate: ");
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
auto &date = lgconf->time_http_str;
|
||||
output->append(date.c_str(), date.size());
|
||||
output->append(date);
|
||||
output->append("\r\nContent-Type: text/html; "
|
||||
"charset=UTF-8\r\nConnection: close\r\n\r\n");
|
||||
output->append(html.c_str(), html.size());
|
||||
output->append(html);
|
||||
|
||||
downstream->add_response_sent_bodylen(html.size());
|
||||
downstream->response_sent_body_length += html.size();
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
}
|
||||
|
||||
|
@ -896,6 +887,9 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
}
|
||||
|
||||
const auto &req = downstream->request();
|
||||
auto &resp = downstream->response();
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
if (!downstream->get_non_final_response()) {
|
||||
auto worker = handler_->get_worker();
|
||||
|
@ -912,26 +906,27 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
#endif // HAVE_MRUBY
|
||||
|
||||
auto connect_method = downstream->get_request_method() == HTTP_CONNECT;
|
||||
auto connect_method = req.method == HTTP_CONNECT;
|
||||
|
||||
auto buf = downstream->get_response_buf();
|
||||
|
||||
buf->append("HTTP/");
|
||||
buf->append(util::utos(downstream->get_request_major()));
|
||||
buf->append(util::utos(req.http_major));
|
||||
buf->append(".");
|
||||
buf->append(util::utos(downstream->get_request_minor()));
|
||||
buf->append(util::utos(req.http_minor));
|
||||
buf->append(" ");
|
||||
buf->append(http2::get_status_string(downstream->get_response_http_status()));
|
||||
buf->append(http2::get_status_string(resp.http_status));
|
||||
buf->append("\r\n");
|
||||
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!get_config()->no_location_rewrite) {
|
||||
!httpconf.no_location_rewrite) {
|
||||
downstream->rewrite_location_response_header(
|
||||
get_client_handler()->get_upstream_scheme());
|
||||
}
|
||||
|
||||
http2::build_http1_headers_from_headers(buf,
|
||||
downstream->get_response_headers());
|
||||
http2::build_http1_headers_from_headers(buf, resp.fs.headers());
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
buf->append("\r\n");
|
||||
|
@ -940,7 +935,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
log_response_headers(buf);
|
||||
}
|
||||
|
||||
downstream->clear_response_headers();
|
||||
resp.fs.clear_headers();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -950,15 +945,13 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
// after graceful shutdown commenced, add connection: close header
|
||||
// field.
|
||||
if (worker->get_graceful_shutdown()) {
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
}
|
||||
|
||||
// We check downstream->get_response_connection_close() in case when
|
||||
// the Content-Length is not available.
|
||||
if (!downstream->get_request_connection_close() &&
|
||||
!downstream->get_response_connection_close()) {
|
||||
if (downstream->get_request_major() <= 0 ||
|
||||
downstream->get_request_minor() <= 0) {
|
||||
if (!req.connection_close && !resp.connection_close) {
|
||||
if (req.http_major <= 0 || req.http_minor <= 0) {
|
||||
// We add this header for HTTP/1.0 or HTTP/0.9 clients
|
||||
buf->append("Connection: Keep-Alive\r\n");
|
||||
}
|
||||
|
@ -967,14 +960,14 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
|
||||
if (!connect_method && downstream->get_upgraded()) {
|
||||
auto connection = downstream->get_response_header(http2::HD_CONNECTION);
|
||||
auto connection = resp.fs.header(http2::HD_CONNECTION);
|
||||
if (connection) {
|
||||
buf->append("Connection: ");
|
||||
buf->append((*connection).value);
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
auto upgrade = downstream->get_response_header(http2::HD_UPGRADE);
|
||||
auto upgrade = resp.fs.header(http2::HD_UPGRADE);
|
||||
if (upgrade) {
|
||||
buf->append("Upgrade: ");
|
||||
buf->append((*upgrade).value);
|
||||
|
@ -982,12 +975,12 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
|
||||
if (!resp.fs.header(http2::HD_ALT_SVC)) {
|
||||
// We won't change or alter alt-svc from backend for now
|
||||
if (!get_config()->altsvcs.empty()) {
|
||||
if (!httpconf.altsvcs.empty()) {
|
||||
buf->append("Alt-Svc: ");
|
||||
|
||||
auto &altsvcs = get_config()->altsvcs;
|
||||
auto &altsvcs = httpconf.altsvcs;
|
||||
write_altsvc(buf, altsvcs[0]);
|
||||
for (size_t i = 1; i < altsvcs.size(); ++i) {
|
||||
buf->append(", ");
|
||||
|
@ -999,10 +992,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||
buf->append("Server: ");
|
||||
buf->append(get_config()->server_name, strlen(get_config()->server_name));
|
||||
buf->append(httpconf.server_name);
|
||||
buf->append("\r\n");
|
||||
} else {
|
||||
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||
auto server = resp.fs.header(http2::HD_SERVER);
|
||||
if (server) {
|
||||
buf->append("Server: ");
|
||||
buf->append((*server).value);
|
||||
|
@ -1010,8 +1003,8 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
}
|
||||
}
|
||||
|
||||
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
auto via = resp.fs.header(http2::HD_VIA);
|
||||
if (httpconf.no_via) {
|
||||
if (via) {
|
||||
buf->append("Via: ");
|
||||
buf->append((*via).value);
|
||||
|
@ -1023,12 +1016,12 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
buf->append((*via).value);
|
||||
buf->append(", ");
|
||||
}
|
||||
buf->append(http::create_via_header_value(
|
||||
downstream->get_response_major(), downstream->get_response_minor()));
|
||||
buf->append(
|
||||
http::create_via_header_value(resp.http_major, resp.http_minor));
|
||||
buf->append("\r\n");
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_response_headers) {
|
||||
for (auto &p : httpconf.add_response_headers) {
|
||||
buf->append(p.first);
|
||||
buf->append(": ");
|
||||
buf->append(p.second);
|
||||
|
@ -1052,14 +1045,12 @@ int HttpsUpstream::on_downstream_body(Downstream *downstream,
|
|||
}
|
||||
auto output = downstream->get_response_buf();
|
||||
if (downstream->get_chunked_response()) {
|
||||
auto chunk_size_hex = util::utox(len);
|
||||
chunk_size_hex += "\r\n";
|
||||
|
||||
output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
|
||||
output->append(util::utox(len));
|
||||
output->append("\r\n");
|
||||
}
|
||||
output->append(data, len);
|
||||
|
||||
downstream->add_response_sent_bodylen(len);
|
||||
downstream->response_sent_body_length += len;
|
||||
|
||||
if (downstream->get_chunked_response()) {
|
||||
output->append("\r\n");
|
||||
|
@ -1068,9 +1059,12 @@ int HttpsUpstream::on_downstream_body(Downstream *downstream,
|
|||
}
|
||||
|
||||
int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
||||
const auto &req = downstream->request();
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (downstream->get_chunked_response()) {
|
||||
auto output = downstream->get_response_buf();
|
||||
auto &trailers = downstream->get_response_trailers();
|
||||
const auto &trailers = resp.fs.trailers();
|
||||
if (trailers.empty()) {
|
||||
output->append("0\r\n\r\n");
|
||||
} else {
|
||||
|
@ -1083,12 +1077,11 @@ int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
|
|||
DLOG(INFO, downstream) << "HTTP response completed";
|
||||
}
|
||||
|
||||
if (!downstream->validate_response_bodylen()) {
|
||||
downstream->set_response_connection_close(true);
|
||||
if (!downstream->validate_response_recv_body_length()) {
|
||||
resp.connection_close = true;
|
||||
}
|
||||
|
||||
if (downstream->get_request_connection_close() ||
|
||||
downstream->get_response_connection_close()) {
|
||||
if (req.connection_close || resp.connection_close) {
|
||||
auto handler = get_client_handler();
|
||||
handler->set_should_close_after_write(true);
|
||||
}
|
||||
|
|
|
@ -109,12 +109,14 @@ Log::~Log() {
|
|||
|
||||
auto lgconf = log_config();
|
||||
|
||||
auto &errorconf = get_config()->logging.error;
|
||||
|
||||
if (!log_enabled(severity_) ||
|
||||
(lgconf->errorlog_fd == -1 && !get_config()->errorlog_syslog)) {
|
||||
(lgconf->errorlog_fd == -1 && !errorconf.syslog)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_config()->errorlog_syslog) {
|
||||
if (errorconf.syslog) {
|
||||
if (severity_ == NOTICE) {
|
||||
syslog(severity_to_syslog_level(severity_), "[%s] %s",
|
||||
SEVERITY_STR[severity_], stream_.str().c_str());
|
||||
|
@ -182,6 +184,22 @@ std::pair<OutputIterator, size_t> copy(const std::string &src, size_t avail,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
template <typename OutputIterator>
|
||||
std::pair<OutputIterator, size_t> copy(const StringRef &src, size_t avail,
|
||||
OutputIterator oitr) {
|
||||
return copy(src.c_str(), src.size(), avail, oitr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
template <typename OutputIterator>
|
||||
std::pair<OutputIterator, size_t> copy(const ImmutableString &src, size_t avail,
|
||||
OutputIterator oitr) {
|
||||
return copy(src.c_str(), src.size(), avail, oitr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
template <size_t N, typename OutputIterator>
|
||||
std::pair<OutputIterator, size_t> copy_l(const char(&src)[N], size_t avail,
|
||||
|
@ -211,8 +229,9 @@ std::pair<OutputIterator, size_t> copy_hex_low(const uint8_t *src,
|
|||
void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
||||
const LogSpec &lgsp) {
|
||||
auto lgconf = log_config();
|
||||
auto &accessconf = get_config()->logging.access;
|
||||
|
||||
if (lgconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) {
|
||||
if (lgconf->accesslog_fd == -1 && !accessconf.syslog) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -220,6 +239,11 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
|||
|
||||
auto downstream = lgsp.downstream;
|
||||
|
||||
const Request *req = nullptr;
|
||||
if (downstream) {
|
||||
req = &downstream->request();
|
||||
}
|
||||
|
||||
auto p = buf;
|
||||
auto avail = sizeof(buf) - 2;
|
||||
|
||||
|
@ -230,7 +254,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
|||
for (auto &lf : lfv) {
|
||||
switch (lf.type) {
|
||||
case SHRPX_LOGF_LITERAL:
|
||||
std::tie(p, avail) = copy(lf.value.get(), avail, p);
|
||||
std::tie(p, avail) = copy(lf.value, avail, p);
|
||||
break;
|
||||
case SHRPX_LOGF_REMOTE_ADDR:
|
||||
std::tie(p, avail) = copy(lgsp.remote_addr, avail, p);
|
||||
|
@ -259,8 +283,8 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
|||
std::tie(p, avail) = copy(util::utos(lgsp.body_bytes_sent), avail, p);
|
||||
break;
|
||||
case SHRPX_LOGF_HTTP:
|
||||
if (downstream) {
|
||||
auto hd = downstream->get_request_header(lf.value.get());
|
||||
if (req) {
|
||||
auto hd = req->fs.header(StringRef(lf.value));
|
||||
if (hd) {
|
||||
std::tie(p, avail) = copy((*hd).value, avail, p);
|
||||
break;
|
||||
|
@ -271,10 +295,9 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
|||
|
||||
break;
|
||||
case SHRPX_LOGF_AUTHORITY:
|
||||
if (downstream) {
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
if (!authority.empty()) {
|
||||
std::tie(p, avail) = copy(authority, avail, p);
|
||||
if (req) {
|
||||
if (!req->authority.empty()) {
|
||||
std::tie(p, avail) = copy(req->authority, avail, p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +371,7 @@ void upstream_accesslog(const std::vector<LogFragment> &lfv,
|
|||
|
||||
*p = '\0';
|
||||
|
||||
if (get_config()->accesslog_syslog) {
|
||||
if (accessconf.syslog) {
|
||||
syslog(LOG_INFO, "%s", buf);
|
||||
|
||||
return;
|
||||
|
@ -367,29 +390,27 @@ int reopen_log_files() {
|
|||
int new_errorlog_fd = -1;
|
||||
|
||||
auto lgconf = log_config();
|
||||
auto &accessconf = get_config()->logging.access;
|
||||
auto &errorconf = get_config()->logging.error;
|
||||
|
||||
if (!get_config()->accesslog_syslog && get_config()->accesslog_file) {
|
||||
|
||||
new_accesslog_fd = util::open_log_file(get_config()->accesslog_file.get());
|
||||
if (!accessconf.syslog && accessconf.file) {
|
||||
new_accesslog_fd = util::open_log_file(accessconf.file.get());
|
||||
|
||||
if (new_accesslog_fd == -1) {
|
||||
LOG(ERROR) << "Failed to open accesslog file "
|
||||
<< get_config()->accesslog_file.get();
|
||||
LOG(ERROR) << "Failed to open accesslog file " << accessconf.file.get();
|
||||
res = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!get_config()->errorlog_syslog && get_config()->errorlog_file) {
|
||||
|
||||
new_errorlog_fd = util::open_log_file(get_config()->errorlog_file.get());
|
||||
if (!errorconf.syslog && errorconf.file) {
|
||||
new_errorlog_fd = util::open_log_file(errorconf.file.get());
|
||||
|
||||
if (new_errorlog_fd == -1) {
|
||||
if (lgconf->errorlog_fd != -1) {
|
||||
LOG(ERROR) << "Failed to open errorlog file "
|
||||
<< get_config()->errorlog_file.get();
|
||||
LOG(ERROR) << "Failed to open errorlog file " << errorconf.file.get();
|
||||
} else {
|
||||
std::cerr << "Failed to open errorlog file "
|
||||
<< get_config()->errorlog_file.get() << std::endl;
|
||||
std::cerr << "Failed to open errorlog file " << errorconf.file.get()
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
res = -1;
|
||||
|
@ -432,8 +453,9 @@ void log_chld(pid_t pid, int rstatus, const char *msg) {
|
|||
|
||||
void redirect_stderr_to_errorlog() {
|
||||
auto lgconf = log_config();
|
||||
auto &errorconf = get_config()->logging.error;
|
||||
|
||||
if (get_config()->errorlog_syslog || lgconf->errorlog_fd == -1) {
|
||||
if (errorconf.syslog || lgconf->errorlog_fd == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,9 @@
|
|||
|
||||
#include "shrpx_log_config.h"
|
||||
#include "ssl.h"
|
||||
#include "template.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
#define ENABLE_LOG 1
|
||||
|
||||
|
@ -131,18 +134,18 @@ enum LogFragmentType {
|
|||
};
|
||||
|
||||
struct LogFragment {
|
||||
LogFragment(LogFragmentType type, std::unique_ptr<char[]> value = nullptr)
|
||||
LogFragment(LogFragmentType type, ImmutableString value = ImmutableString())
|
||||
: type(type), value(std::move(value)) {}
|
||||
LogFragmentType type;
|
||||
std::unique_ptr<char[]> value;
|
||||
ImmutableString value;
|
||||
};
|
||||
|
||||
struct LogSpec {
|
||||
Downstream *downstream;
|
||||
const char *remote_addr;
|
||||
const char *method;
|
||||
const char *path;
|
||||
const char *alpn;
|
||||
StringRef remote_addr;
|
||||
StringRef method;
|
||||
StringRef path;
|
||||
StringRef alpn;
|
||||
const nghttp2::ssl::TLSSessionInfo *tls_info;
|
||||
std::chrono::system_clock::time_point time_now;
|
||||
std::chrono::high_resolution_clock::time_point request_start_time;
|
||||
|
@ -150,7 +153,7 @@ struct LogSpec {
|
|||
int major, minor;
|
||||
unsigned int status;
|
||||
int64_t body_bytes_sent;
|
||||
const char *remote_port;
|
||||
StringRef remote_port;
|
||||
uint16_t server_port;
|
||||
pid_t pid;
|
||||
};
|
||||
|
|
|
@ -92,9 +92,12 @@ constexpr ev_tstamp read_timeout = 10.;
|
|||
|
||||
MemcachedConnection::MemcachedConnection(const Address *addr,
|
||||
struct ev_loop *loop)
|
||||
: conn_(loop, -1, nullptr, nullptr, write_timeout, read_timeout, 0, 0, 0, 0,
|
||||
: conn_(loop, -1, nullptr, nullptr, write_timeout, read_timeout, {}, {},
|
||||
connectcb, readcb, timeoutcb, this, 0, 0.),
|
||||
parse_state_{}, addr_(addr), sendsum_(0), connected_(false) {}
|
||||
parse_state_{},
|
||||
addr_(addr),
|
||||
sendsum_(0),
|
||||
connected_(false) {}
|
||||
|
||||
MemcachedConnection::~MemcachedConnection() { disconnect(); }
|
||||
|
||||
|
|
|
@ -96,11 +96,11 @@ int MRubyContext::run_app(Downstream *downstream, int phase) {
|
|||
mrb_->ud = nullptr;
|
||||
|
||||
if (data.request_headers_dirty) {
|
||||
downstream->index_request_headers();
|
||||
downstream->request().fs.index_headers();
|
||||
}
|
||||
|
||||
if (data.response_headers_dirty) {
|
||||
downstream->index_response_headers();
|
||||
downstream->response().fs.index_headers();
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
|
|
@ -49,7 +49,8 @@ namespace {
|
|||
mrb_value request_get_http_version_major(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_request_major());
|
||||
const auto &req = downstream->request();
|
||||
return mrb_fixnum_value(req.http_major);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -57,7 +58,8 @@ namespace {
|
|||
mrb_value request_get_http_version_minor(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_request_minor());
|
||||
const auto &req = downstream->request();
|
||||
return mrb_fixnum_value(req.http_minor);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -65,7 +67,8 @@ namespace {
|
|||
mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto method = http2::to_method_string(downstream->get_request_method());
|
||||
const auto &req = downstream->request();
|
||||
auto method = http2::to_method_string(req.method);
|
||||
|
||||
return mrb_str_new_cstr(mrb, method);
|
||||
}
|
||||
|
@ -75,6 +78,7 @@ namespace {
|
|||
mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
|
@ -90,7 +94,7 @@ mrb_value request_set_method(mrb_state *mrb, mrb_value self) {
|
|||
mrb_raise(mrb, E_RUNTIME_ERROR, "method not supported");
|
||||
}
|
||||
|
||||
downstream->set_request_method(token);
|
||||
req.method = token;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -100,9 +104,9 @@ namespace {
|
|||
mrb_value request_get_authority(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &authority = downstream->get_request_http2_authority();
|
||||
const auto &req = downstream->request();
|
||||
|
||||
return mrb_str_new(mrb, authority.c_str(), authority.size());
|
||||
return mrb_str_new(mrb, req.authority.c_str(), req.authority.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -110,6 +114,7 @@ namespace {
|
|||
mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
|
@ -120,7 +125,7 @@ mrb_value request_set_authority(mrb_state *mrb, mrb_value self) {
|
|||
mrb_raise(mrb, E_RUNTIME_ERROR, "authority must not be empty string");
|
||||
}
|
||||
|
||||
downstream->set_request_http2_authority(std::string(authority, n));
|
||||
req.authority.assign(authority, n);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -130,9 +135,9 @@ namespace {
|
|||
mrb_value request_get_scheme(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &scheme = downstream->get_request_http2_scheme();
|
||||
const auto &req = downstream->request();
|
||||
|
||||
return mrb_str_new(mrb, scheme.c_str(), scheme.size());
|
||||
return mrb_str_new(mrb, req.scheme.c_str(), req.scheme.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -140,6 +145,7 @@ namespace {
|
|||
mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
|
@ -150,7 +156,7 @@ mrb_value request_set_scheme(mrb_state *mrb, mrb_value self) {
|
|||
mrb_raise(mrb, E_RUNTIME_ERROR, "scheme must not be empty string");
|
||||
}
|
||||
|
||||
downstream->set_request_http2_scheme(std::string(scheme, n));
|
||||
req.scheme.assign(scheme, n);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -160,9 +166,9 @@ namespace {
|
|||
mrb_value request_get_path(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &path = downstream->get_request_path();
|
||||
const auto &req = downstream->request();
|
||||
|
||||
return mrb_str_new(mrb, path.c_str(), path.size());
|
||||
return mrb_str_new(mrb, req.path.c_str(), req.path.size());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -170,6 +176,7 @@ namespace {
|
|||
mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
|
@ -177,7 +184,7 @@ mrb_value request_set_path(mrb_state *mrb, mrb_value self) {
|
|||
mrb_int pathlen;
|
||||
mrb_get_args(mrb, "s", &path, &pathlen);
|
||||
|
||||
downstream->set_request_path(std::string(path, pathlen));
|
||||
req.path.assign(path, pathlen);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -187,7 +194,8 @@ namespace {
|
|||
mrb_value request_get_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return create_headers_hash(mrb, downstream->get_request_headers());
|
||||
const auto &req = downstream->request();
|
||||
return create_headers_hash(mrb, req.fs.headers());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -195,6 +203,7 @@ namespace {
|
|||
mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
|
@ -209,7 +218,7 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
|||
|
||||
if (repl) {
|
||||
size_t p = 0;
|
||||
auto &headers = downstream->get_request_headers();
|
||||
auto &headers = req.fs.headers();
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
auto &hd = headers[i];
|
||||
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
|
||||
|
@ -227,14 +236,12 @@ mrb_value request_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
|||
auto n = mrb_ary_len(mrb, values);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
auto value = mrb_ary_entry(values, i);
|
||||
downstream->add_request_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
req.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
}
|
||||
} else if (!mrb_nil_p(values)) {
|
||||
downstream->add_request_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
req.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
}
|
||||
|
||||
data->request_headers_dirty = true;
|
||||
|
@ -259,10 +266,11 @@ namespace {
|
|||
mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &req = downstream->request();
|
||||
|
||||
check_phase(mrb, data->phase, PHASE_REQUEST);
|
||||
|
||||
downstream->clear_request_headers();
|
||||
req.fs.clear_headers();
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ namespace {
|
|||
mrb_value response_get_http_version_major(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_response_major());
|
||||
const auto &resp = downstream->response();
|
||||
return mrb_fixnum_value(resp.http_major);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -57,7 +58,8 @@ namespace {
|
|||
mrb_value response_get_http_version_minor(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return mrb_fixnum_value(downstream->get_response_minor());
|
||||
const auto &resp = downstream->response();
|
||||
return mrb_fixnum_value(resp.http_minor);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -65,8 +67,8 @@ namespace {
|
|||
mrb_value response_get_status(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
|
||||
return mrb_fixnum_value(downstream->get_response_http_status());
|
||||
const auto &resp = downstream->response();
|
||||
return mrb_fixnum_value(resp.http_status);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -74,6 +76,7 @@ namespace {
|
|||
mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &resp = downstream->response();
|
||||
|
||||
mrb_int status;
|
||||
mrb_get_args(mrb, "i", &status);
|
||||
|
@ -83,7 +86,7 @@ mrb_value response_set_status(mrb_state *mrb, mrb_value self) {
|
|||
"invalid status; it should be [200, 999], inclusive");
|
||||
}
|
||||
|
||||
downstream->set_response_http_status(status);
|
||||
resp.http_status = status;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
@ -93,7 +96,9 @@ namespace {
|
|||
mrb_value response_get_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
return create_headers_hash(mrb, downstream->get_response_headers());
|
||||
const auto &resp = downstream->response();
|
||||
|
||||
return create_headers_hash(mrb, resp.fs.headers());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -101,6 +106,7 @@ namespace {
|
|||
mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &resp = downstream->response();
|
||||
|
||||
mrb_value key, values;
|
||||
mrb_get_args(mrb, "oo", &key, &values);
|
||||
|
@ -113,7 +119,7 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
|||
|
||||
if (repl) {
|
||||
size_t p = 0;
|
||||
auto &headers = downstream->get_response_headers();
|
||||
auto &headers = resp.fs.headers();
|
||||
for (size_t i = 0; i < headers.size(); ++i) {
|
||||
auto &hd = headers[i];
|
||||
if (util::streq(std::begin(hd.name), hd.name.size(), RSTRING_PTR(key),
|
||||
|
@ -131,14 +137,12 @@ mrb_value response_mod_header(mrb_state *mrb, mrb_value self, bool repl) {
|
|||
auto n = mrb_ary_len(mrb, values);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
auto value = mrb_ary_entry(values, i);
|
||||
downstream->add_response_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
resp.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
|
||||
}
|
||||
} else if (!mrb_nil_p(values)) {
|
||||
downstream->add_response_header(
|
||||
std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
resp.fs.add_header(std::string(RSTRING_PTR(key), RSTRING_LEN(key)),
|
||||
std::string(RSTRING_PTR(values), RSTRING_LEN(values)));
|
||||
}
|
||||
|
||||
data->response_headers_dirty = true;
|
||||
|
@ -163,8 +167,9 @@ namespace {
|
|||
mrb_value response_clear_headers(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &resp = downstream->response();
|
||||
|
||||
downstream->clear_response_headers();
|
||||
resp.fs.clear_headers();
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
@ -174,6 +179,7 @@ namespace {
|
|||
mrb_value response_return(mrb_state *mrb, mrb_value self) {
|
||||
auto data = static_cast<MRubyAssocData *>(mrb->ud);
|
||||
auto downstream = data->downstream;
|
||||
auto &resp = downstream->response();
|
||||
int rv;
|
||||
|
||||
if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
|
||||
|
@ -187,12 +193,12 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
|
|||
const uint8_t *body = nullptr;
|
||||
size_t bodylen = 0;
|
||||
|
||||
if (downstream->get_response_http_status() == 0) {
|
||||
downstream->set_response_http_status(200);
|
||||
if (resp.http_status == 0) {
|
||||
resp.http_status = 200;
|
||||
}
|
||||
|
||||
if (data->response_headers_dirty) {
|
||||
downstream->index_response_headers();
|
||||
resp.fs.index_headers();
|
||||
data->response_headers_dirty = false;
|
||||
}
|
||||
|
||||
|
@ -201,21 +207,20 @@ mrb_value response_return(mrb_state *mrb, mrb_value self) {
|
|||
bodylen = vallen;
|
||||
}
|
||||
|
||||
auto cl = downstream->get_response_header(http2::HD_CONTENT_LENGTH);
|
||||
auto cl = resp.fs.header(http2::HD_CONTENT_LENGTH);
|
||||
if (cl) {
|
||||
cl->value = util::utos(bodylen);
|
||||
} else {
|
||||
downstream->add_response_header("content-length", util::utos(bodylen),
|
||||
http2::HD_CONTENT_LENGTH);
|
||||
resp.fs.add_header("content-length", util::utos(bodylen),
|
||||
http2::HD_CONTENT_LENGTH);
|
||||
}
|
||||
downstream->set_response_content_length(bodylen);
|
||||
resp.fs.content_length = bodylen;
|
||||
|
||||
auto date = downstream->get_response_header(http2::HD_DATE);
|
||||
auto date = resp.fs.header(http2::HD_DATE);
|
||||
if (!date) {
|
||||
auto lgconf = log_config();
|
||||
lgconf->update_tstamp(std::chrono::system_clock::now());
|
||||
downstream->add_response_header("date", lgconf->time_http_str,
|
||||
http2::HD_DATE);
|
||||
resp.fs.add_header("date", lgconf->time_http_str, http2::HD_DATE);
|
||||
}
|
||||
|
||||
auto upstream = downstream->get_upstream();
|
||||
|
|
|
@ -39,8 +39,13 @@ void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
|
||||
RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst,
|
||||
Connection *conn)
|
||||
: w_(w), loop_(loop), conn_(conn), rate_(rate), burst_(burst),
|
||||
avail_(burst), startw_req_(false) {
|
||||
: w_(w),
|
||||
loop_(loop),
|
||||
conn_(conn),
|
||||
rate_(rate),
|
||||
burst_(burst),
|
||||
avail_(burst),
|
||||
startw_req_(false) {
|
||||
ev_timer_init(&t_, regencb, 0., 1.);
|
||||
t_.data = this;
|
||||
if (rate_ > 0) {
|
||||
|
|
|
@ -49,19 +49,23 @@ using namespace nghttp2;
|
|||
|
||||
namespace shrpx {
|
||||
|
||||
namespace {
|
||||
constexpr size_t MAX_BUFFER_SIZE = 32_k;
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
|
||||
int flags, void *user_data) {
|
||||
auto upstream = static_cast<SpdyUpstream *>(user_data);
|
||||
auto wb = upstream->get_response_buf();
|
||||
|
||||
if (wb->wleft() == 0) {
|
||||
if (wb->rleft() >= MAX_BUFFER_SIZE) {
|
||||
return SPDYLAY_ERR_WOULDBLOCK;
|
||||
}
|
||||
|
||||
auto nread = wb->write(data, len);
|
||||
wb->append(data, len);
|
||||
|
||||
return nread;
|
||||
return len;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -102,9 +106,11 @@ void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
|
|||
return;
|
||||
}
|
||||
|
||||
upstream->consume(stream_id, downstream->get_request_datalen());
|
||||
auto &req = downstream->request();
|
||||
|
||||
downstream->reset_request_datalen();
|
||||
upstream->consume(stream_id, req.unconsumed_body_length);
|
||||
|
||||
req.unconsumed_body_length = 0;
|
||||
|
||||
if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
|
||||
upstream->remove_downstream(downstream);
|
||||
|
@ -144,8 +150,10 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
<< frame->syn_stream.stream_id;
|
||||
}
|
||||
|
||||
auto downstream = upstream->add_pending_downstream(
|
||||
frame->syn_stream.stream_id, frame->syn_stream.pri);
|
||||
auto downstream =
|
||||
upstream->add_pending_downstream(frame->syn_stream.stream_id);
|
||||
|
||||
auto &req = downstream->request();
|
||||
|
||||
downstream->reset_upstream_rtimer();
|
||||
|
||||
|
@ -169,29 +177,31 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
header_buffer += strlen(nv[i]) + strlen(nv[i + 1]);
|
||||
}
|
||||
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
// spdy does not define usage of trailer fields, and we ignores
|
||||
// them.
|
||||
if (header_buffer > get_config()->header_field_buffer ||
|
||||
num_headers > get_config()->max_header_fields) {
|
||||
if (header_buffer > httpconf.request_header_field_buffer ||
|
||||
num_headers > httpconf.max_request_header_fields) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; nv[i]; i += 2) {
|
||||
downstream->add_request_header(nv[i], nv[i + 1]);
|
||||
req.fs.add_header(nv[i], nv[i + 1]);
|
||||
}
|
||||
|
||||
if (downstream->index_request_headers() != 0) {
|
||||
if (req.fs.index_headers() != 0) {
|
||||
if (upstream->error_reply(downstream, 400) != 0) {
|
||||
ULOG(FATAL, upstream) << "error_reply failed";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = downstream->get_request_header(http2::HD__PATH);
|
||||
auto scheme = downstream->get_request_header(http2::HD__SCHEME);
|
||||
auto host = downstream->get_request_header(http2::HD__HOST);
|
||||
auto method = downstream->get_request_header(http2::HD__METHOD);
|
||||
auto path = req.fs.header(http2::HD__PATH);
|
||||
auto scheme = req.fs.header(http2::HD__SCHEME);
|
||||
auto host = req.fs.header(http2::HD__HOST);
|
||||
auto method = req.fs.header(http2::HD__METHOD);
|
||||
|
||||
if (!method) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
|
@ -214,6 +224,27 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
return;
|
||||
}
|
||||
|
||||
if (std::find_if(std::begin(host->value), std::end(host->value),
|
||||
[](char c) { return c == '"' || c == '\\'; }) !=
|
||||
std::end(host->value)) {
|
||||
if (upstream->error_reply(downstream, 400) != 0) {
|
||||
ULOG(FATAL, upstream) << "error_reply failed";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (scheme) {
|
||||
for (auto c : scheme->value) {
|
||||
if (!(util::is_alpha(c) || util::is_digit(c) || c == '+' || c == '-' ||
|
||||
c == '.')) {
|
||||
if (upstream->error_reply(downstream, 400) != 0) {
|
||||
ULOG(FATAL, upstream) << "error_reply failed";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For other than CONNECT method, path must start with "/", except
|
||||
// for OPTIONS method, which can take "*" as path.
|
||||
if (!is_connect && path->value[0] != '/' &&
|
||||
|
@ -222,24 +253,24 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
return;
|
||||
}
|
||||
|
||||
downstream->set_request_method(method_token);
|
||||
req.method = method_token;
|
||||
if (is_connect) {
|
||||
downstream->set_request_http2_authority(path->value);
|
||||
req.authority = path->value;
|
||||
} else {
|
||||
downstream->set_request_http2_scheme(scheme->value);
|
||||
downstream->set_request_http2_authority(host->value);
|
||||
req.scheme = scheme->value;
|
||||
req.authority = host->value;
|
||||
if (get_config()->http2_proxy || get_config()->client_proxy) {
|
||||
downstream->set_request_path(path->value);
|
||||
req.path = path->value;
|
||||
} else if (method_token == HTTP_OPTIONS && path->value == "*") {
|
||||
// Server-wide OPTIONS request. Path is empty.
|
||||
} else {
|
||||
downstream->set_request_path(http2::rewrite_clean_path(
|
||||
std::begin(path->value), std::end(path->value)));
|
||||
req.path = http2::rewrite_clean_path(std::begin(path->value),
|
||||
std::end(path->value));
|
||||
}
|
||||
}
|
||||
|
||||
if (!(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN)) {
|
||||
downstream->set_request_http2_expect_body(true);
|
||||
req.http2_expect_body = true;
|
||||
}
|
||||
|
||||
downstream->inspect_http2_request();
|
||||
|
@ -261,7 +292,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
#endif // HAVE_MRUBY
|
||||
|
||||
if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
|
||||
if (!downstream->validate_request_bodylen()) {
|
||||
if (!downstream->validate_request_recv_body_length()) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
@ -285,8 +316,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
|
|||
} // namespace
|
||||
|
||||
void SpdyUpstream::start_downstream(Downstream *downstream) {
|
||||
if (downstream_queue_.can_activate(
|
||||
downstream->get_request_http2_authority())) {
|
||||
if (downstream_queue_.can_activate(downstream->request().authority)) {
|
||||
initiate_downstream(downstream);
|
||||
return;
|
||||
}
|
||||
|
@ -346,31 +376,33 @@ void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
|
|||
return;
|
||||
}
|
||||
|
||||
auto &http2conf = get_config()->http2;
|
||||
|
||||
// If connection-level window control is not enabled (e.g,
|
||||
// spdy/3), spdylay_session_get_recv_data_length() is always
|
||||
// returns 0.
|
||||
if (spdylay_session_get_recv_data_length(session) >
|
||||
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
|
||||
1 << get_config()->http2_upstream_connection_window_bits)) {
|
||||
1 << http2conf.upstream.connection_window_bits)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream)
|
||||
<< "Flow control error on connection: "
|
||||
<< "recv_window_size="
|
||||
<< spdylay_session_get_recv_data_length(session) << ", window_size="
|
||||
<< (1 << get_config()->http2_upstream_connection_window_bits);
|
||||
ULOG(INFO, upstream) << "Flow control error on connection: "
|
||||
<< "recv_window_size="
|
||||
<< spdylay_session_get_recv_data_length(session)
|
||||
<< ", window_size="
|
||||
<< (1 << http2conf.upstream.connection_window_bits);
|
||||
}
|
||||
spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
if (spdylay_session_get_stream_recv_data_length(session, stream_id) >
|
||||
std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
|
||||
1 << get_config()->http2_upstream_window_bits)) {
|
||||
1 << http2conf.upstream.window_bits)) {
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
ULOG(INFO, upstream) << "Flow control error: recv_window_size="
|
||||
<< spdylay_session_get_stream_recv_data_length(
|
||||
session, stream_id)
|
||||
<< ", initial_window_size="
|
||||
<< (1 << get_config()->http2_upstream_window_bits);
|
||||
<< (1 << http2conf.upstream.window_bits);
|
||||
}
|
||||
upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR);
|
||||
return;
|
||||
|
@ -385,7 +417,7 @@ void on_data_recv_callback(spdylay_session *session, uint8_t flags,
|
|||
auto downstream = static_cast<Downstream *>(
|
||||
spdylay_session_get_stream_user_data(session, stream_id));
|
||||
if (downstream && (flags & SPDYLAY_DATA_FLAG_FIN)) {
|
||||
if (!downstream->validate_request_bodylen()) {
|
||||
if (!downstream->validate_request_recv_body_length()) {
|
||||
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
@ -464,14 +496,16 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
|
|||
} // namespace
|
||||
|
||||
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
||||
: downstream_queue_(
|
||||
: wb_(handler->get_worker()->get_mcpool()),
|
||||
downstream_queue_(
|
||||
get_config()->http2_proxy
|
||||
? get_config()->downstream_connections_per_host
|
||||
: get_config()->downstream_proto == PROTO_HTTP
|
||||
? get_config()->downstream_connections_per_frontend
|
||||
? get_config()->conn.downstream.connections_per_host
|
||||
: get_config()->conn.downstream.proto == PROTO_HTTP
|
||||
? get_config()->conn.downstream.connections_per_frontend
|
||||
: 0,
|
||||
!get_config()->http2_proxy),
|
||||
handler_(handler), session_(nullptr) {
|
||||
handler_(handler),
|
||||
session_(nullptr) {
|
||||
spdylay_session_callbacks callbacks{};
|
||||
callbacks.send_callback = send_callback;
|
||||
callbacks.recv_callback = recv_callback;
|
||||
|
@ -494,10 +528,12 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
|||
&max_buffer, sizeof(max_buffer));
|
||||
assert(rv == 0);
|
||||
|
||||
auto &http2conf = get_config()->http2;
|
||||
|
||||
if (version >= SPDYLAY_PROTO_SPDY3) {
|
||||
int val = 1;
|
||||
flow_control_ = true;
|
||||
initial_window_size_ = 1 << get_config()->http2_upstream_window_bits;
|
||||
initial_window_size_ = 1 << http2conf.upstream.window_bits;
|
||||
rv = spdylay_session_set_option(
|
||||
session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE2, &val, sizeof(val));
|
||||
assert(rv == 0);
|
||||
|
@ -508,7 +544,7 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
|||
// TODO Maybe call from outside?
|
||||
std::array<spdylay_settings_entry, 2> entry;
|
||||
entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS;
|
||||
entry[0].value = get_config()->http2_max_concurrent_streams;
|
||||
entry[0].value = http2conf.max_concurrent_streams;
|
||||
entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
|
||||
|
||||
entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
|
||||
|
@ -520,15 +556,15 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
|
|||
assert(rv == 0);
|
||||
|
||||
if (version >= SPDYLAY_PROTO_SPDY3_1 &&
|
||||
get_config()->http2_upstream_connection_window_bits > 16) {
|
||||
int32_t delta = (1 << get_config()->http2_upstream_connection_window_bits) -
|
||||
http2conf.upstream.connection_window_bits > 16) {
|
||||
int32_t delta = (1 << http2conf.upstream.connection_window_bits) -
|
||||
SPDYLAY_INITIAL_WINDOW_SIZE;
|
||||
rv = spdylay_submit_window_update(session_, 0, delta);
|
||||
assert(rv == 0);
|
||||
}
|
||||
|
||||
handler_->reset_upstream_read_timeout(
|
||||
get_config()->http2_upstream_read_timeout);
|
||||
get_config()->conn.upstream.timeout.http2_read);
|
||||
|
||||
handler_->signal_write();
|
||||
}
|
||||
|
@ -556,8 +592,8 @@ int SpdyUpstream::on_read() {
|
|||
int SpdyUpstream::on_write() {
|
||||
int rv = 0;
|
||||
|
||||
if (wb_.rleft() == 0) {
|
||||
wb_.reset();
|
||||
if (wb_.rleft() >= MAX_BUFFER_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv = spdylay_session_send(session_);
|
||||
|
@ -756,17 +792,6 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
|||
auto body = downstream->get_response_buf();
|
||||
assert(body);
|
||||
|
||||
auto dconn = downstream->get_downstream_connection();
|
||||
|
||||
if (body->rleft() == 0 && dconn &&
|
||||
downstream->get_response_state() != Downstream::MSG_COMPLETE) {
|
||||
// Try to read more if buffer is empty. This will help small
|
||||
// buffer and make priority handling a bit better.
|
||||
if (upstream->downstream_read(dconn) != 0) {
|
||||
return SPDYLAY_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
auto nread = body->remove(buf, length);
|
||||
auto body_empty = body->rleft() == 0;
|
||||
|
||||
|
@ -801,7 +826,7 @@ ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
|
|||
}
|
||||
|
||||
if (nread > 0) {
|
||||
downstream->add_response_sent_bodylen(nread);
|
||||
downstream->response_sent_body_length += nread;
|
||||
}
|
||||
|
||||
return nread;
|
||||
|
@ -819,10 +844,11 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|||
data_prd_ptr = &data_prd;
|
||||
}
|
||||
|
||||
auto status_string =
|
||||
http2::get_status_string(downstream->get_response_http_status());
|
||||
const auto &resp = downstream->response();
|
||||
|
||||
auto &headers = downstream->get_response_headers();
|
||||
auto status_string = http2::get_status_string(resp.http_status);
|
||||
|
||||
const auto &headers = resp.fs.headers();
|
||||
|
||||
auto nva = std::vector<const char *>();
|
||||
// 3 for :status, :version and server
|
||||
|
@ -848,9 +874,9 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|||
nva.push_back(kv.value.c_str());
|
||||
}
|
||||
|
||||
if (!downstream->get_response_header(http2::HD_SERVER)) {
|
||||
if (!resp.fs.header(http2::HD_SERVER)) {
|
||||
nva.push_back("server");
|
||||
nva.push_back(get_config()->server_name);
|
||||
nva.push_back(get_config()->http.server_name.c_str());
|
||||
}
|
||||
|
||||
nva.push_back(nullptr);
|
||||
|
@ -875,10 +901,12 @@ int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body,
|
|||
int SpdyUpstream::error_reply(Downstream *downstream,
|
||||
unsigned int status_code) {
|
||||
int rv;
|
||||
auto &resp = downstream->response();
|
||||
|
||||
auto html = http::create_error_html(status_code);
|
||||
downstream->set_response_http_status(status_code);
|
||||
resp.http_status = status_code;
|
||||
auto body = downstream->get_response_buf();
|
||||
body->append(html.c_str(), html.size());
|
||||
body->append(html);
|
||||
downstream->set_response_state(Downstream::MSG_COMPLETE);
|
||||
|
||||
spdylay_data_provider data_prd;
|
||||
|
@ -892,7 +920,7 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
|||
std::string status_string = http2::get_status_string(status_code);
|
||||
const char *nv[] = {":status", status_string.c_str(), ":version", "http/1.1",
|
||||
"content-type", "text/html; charset=UTF-8", "server",
|
||||
get_config()->server_name, "content-length",
|
||||
get_config()->http.server_name.c_str(), "content-length",
|
||||
content_length.c_str(), "date",
|
||||
lgconf->time_http_str.c_str(), nullptr};
|
||||
|
||||
|
@ -907,10 +935,9 @@ int SpdyUpstream::error_reply(Downstream *downstream,
|
|||
return 0;
|
||||
}
|
||||
|
||||
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id,
|
||||
int32_t priority) {
|
||||
auto downstream = make_unique<Downstream>(this, handler_->get_mcpool(),
|
||||
stream_id, priority);
|
||||
Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id) {
|
||||
auto downstream =
|
||||
make_unique<Downstream>(this, handler_->get_mcpool(), stream_id);
|
||||
spdylay_session_set_stream_user_data(session_, stream_id, downstream.get());
|
||||
auto res = downstream.get();
|
||||
|
||||
|
@ -937,15 +964,19 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) {
|
|||
// WARNING: Never call directly or indirectly spdylay_session_send or
|
||||
// spdylay_session_recv. These calls may delete downstream.
|
||||
int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (downstream->get_non_final_response()) {
|
||||
// SPDY does not support non-final response. We could send it
|
||||
// with HEADERS and final response in SYN_REPLY, but it is not
|
||||
// official way.
|
||||
downstream->clear_response_headers();
|
||||
resp.fs.clear_headers();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto &req = downstream->request();
|
||||
|
||||
#ifdef HAVE_MRUBY
|
||||
auto worker = handler_->get_worker();
|
||||
auto mruby_ctx = worker->get_mruby_context();
|
||||
|
@ -967,25 +998,26 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||
}
|
||||
|
||||
auto &httpconf = get_config()->http;
|
||||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
|
||||
!get_config()->no_location_rewrite) {
|
||||
downstream->rewrite_location_response_header(
|
||||
downstream->get_request_http2_scheme());
|
||||
!httpconf.no_location_rewrite) {
|
||||
downstream->rewrite_location_response_header(req.scheme);
|
||||
}
|
||||
size_t nheader = downstream->get_response_headers().size();
|
||||
|
||||
// 8 means server, :status, :version and possible via header field.
|
||||
auto nv = make_unique<const char *[]>(
|
||||
nheader * 2 + 8 + get_config()->add_response_headers.size() * 2 + 1);
|
||||
auto nv =
|
||||
make_unique<const char *[]>(resp.fs.headers().size() * 2 + 8 +
|
||||
httpconf.add_response_headers.size() * 2 + 1);
|
||||
|
||||
size_t hdidx = 0;
|
||||
std::string via_value;
|
||||
std::string status_string =
|
||||
http2::get_status_string(downstream->get_response_http_status());
|
||||
auto status_string = http2::get_status_string(resp.http_status);
|
||||
nv[hdidx++] = ":status";
|
||||
nv[hdidx++] = status_string.c_str();
|
||||
nv[hdidx++] = ":version";
|
||||
nv[hdidx++] = "HTTP/1.1";
|
||||
for (auto &hd : downstream->get_response_headers()) {
|
||||
for (auto &hd : resp.fs.headers()) {
|
||||
if (hd.name.empty() || hd.name.c_str()[0] == ':') {
|
||||
continue;
|
||||
}
|
||||
|
@ -1005,17 +1037,17 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
|
||||
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
|
||||
nv[hdidx++] = "server";
|
||||
nv[hdidx++] = get_config()->server_name;
|
||||
nv[hdidx++] = httpconf.server_name.c_str();
|
||||
} else {
|
||||
auto server = downstream->get_response_header(http2::HD_SERVER);
|
||||
auto server = resp.fs.header(http2::HD_SERVER);
|
||||
if (server) {
|
||||
nv[hdidx++] = "server";
|
||||
nv[hdidx++] = server->value.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
auto via = downstream->get_response_header(http2::HD_VIA);
|
||||
if (get_config()->no_via) {
|
||||
auto via = resp.fs.header(http2::HD_VIA);
|
||||
if (httpconf.no_via) {
|
||||
if (via) {
|
||||
nv[hdidx++] = "via";
|
||||
nv[hdidx++] = via->value.c_str();
|
||||
|
@ -1025,13 +1057,13 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
|
|||
via_value = via->value;
|
||||
via_value += ", ";
|
||||
}
|
||||
via_value += http::create_via_header_value(
|
||||
downstream->get_response_major(), downstream->get_response_minor());
|
||||
via_value +=
|
||||
http::create_via_header_value(resp.http_major, resp.http_minor);
|
||||
nv[hdidx++] = "via";
|
||||
nv[hdidx++] = via_value.c_str();
|
||||
}
|
||||
|
||||
for (auto &p : get_config()->add_response_headers) {
|
||||
for (auto &p : httpconf.add_response_headers) {
|
||||
nv[hdidx++] = p.first.c_str();
|
||||
nv[hdidx++] = p.second.c_str();
|
||||
}
|
||||
|
@ -1084,9 +1116,11 @@ int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) {
|
|||
DLOG(INFO, downstream) << "HTTP response completed";
|
||||
}
|
||||
|
||||
if (!downstream->validate_response_bodylen()) {
|
||||
auto &resp = downstream->response();
|
||||
|
||||
if (!downstream->validate_response_recv_body_length()) {
|
||||
rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
|
||||
downstream->set_response_connection_close(true);
|
||||
resp.connection_close = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1103,13 +1137,13 @@ void SpdyUpstream::pause_read(IOCtrlReason reason) {}
|
|||
int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
|
||||
size_t consumed) {
|
||||
if (get_flow_control()) {
|
||||
assert(downstream->get_request_datalen() >= consumed);
|
||||
|
||||
if (consume(downstream->get_stream_id(), consumed) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
downstream->dec_request_datalen(consumed);
|
||||
auto &req = downstream->request();
|
||||
|
||||
req.consume(consumed);
|
||||
}
|
||||
|
||||
handler_->signal_write();
|
||||
|
@ -1220,17 +1254,14 @@ int SpdyUpstream::response_riovec(struct iovec *iov, int iovcnt) const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
iov->iov_base = wb_.pos;
|
||||
iov->iov_len = wb_.rleft();
|
||||
|
||||
return 1;
|
||||
return wb_.riovec(iov, iovcnt);
|
||||
}
|
||||
|
||||
void SpdyUpstream::response_drain(size_t n) { wb_.drain(n); }
|
||||
|
||||
bool SpdyUpstream::response_empty() const { return wb_.rleft() == 0; }
|
||||
|
||||
SpdyUpstream::WriteBuffer *SpdyUpstream::get_response_buf() { return &wb_; }
|
||||
DefaultMemchunks *SpdyUpstream::get_response_buf() { return &wb_; }
|
||||
|
||||
Downstream *
|
||||
SpdyUpstream::on_downstream_push_promise(Downstream *downstream,
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
virtual int downstream_write(DownstreamConnection *dconn);
|
||||
virtual int downstream_eof(DownstreamConnection *dconn);
|
||||
virtual int downstream_error(DownstreamConnection *dconn, int events);
|
||||
Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
|
||||
Downstream *add_pending_downstream(int32_t stream_id);
|
||||
void remove_downstream(Downstream *downstream);
|
||||
|
||||
int rst_stream(Downstream *downstream, int status_code);
|
||||
|
@ -97,12 +97,10 @@ public:
|
|||
void start_downstream(Downstream *downstream);
|
||||
void initiate_downstream(Downstream *downstream);
|
||||
|
||||
using WriteBuffer = Buffer<32_k>;
|
||||
|
||||
WriteBuffer *get_response_buf();
|
||||
DefaultMemchunks *get_response_buf();
|
||||
|
||||
private:
|
||||
WriteBuffer wb_;
|
||||
DefaultMemchunks wb_;
|
||||
DownstreamQueue downstream_queue_;
|
||||
ClientHandler *handler_;
|
||||
spdylay_session *session_;
|
||||
|
|
471
src/shrpx_ssl.cc
471
src/shrpx_ssl.cc
|
@ -70,7 +70,7 @@ namespace ssl {
|
|||
namespace {
|
||||
int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
|
||||
void *arg) {
|
||||
auto &prefs = get_config()->alpn_prefs;
|
||||
auto &prefs = get_config()->tls.alpn_prefs;
|
||||
*data = prefs.data();
|
||||
*len = prefs.size();
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
|
@ -124,13 +124,13 @@ set_alpn_prefs(const std::vector<std::string> &protos) {
|
|||
namespace {
|
||||
int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
|
||||
auto config = static_cast<Config *>(user_data);
|
||||
int len = (int)strlen(config->private_key_passwd.get());
|
||||
int len = (int)strlen(config->tls.private_key_passwd.get());
|
||||
if (size < len + 1) {
|
||||
LOG(ERROR) << "ssl_pem_passwd_cb: buf is too small " << size;
|
||||
return 0;
|
||||
}
|
||||
// Copy string including last '\0'.
|
||||
memcpy(buf, config->private_key_passwd.get(), len + 1);
|
||||
memcpy(buf, config->tls.private_key_passwd.get(), len + 1);
|
||||
return len;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -346,7 +346,7 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
|
|||
|
||||
std::copy(std::begin(key.data.name), std::end(key.data.name), key_name);
|
||||
|
||||
EVP_EncryptInit_ex(ctx, get_config()->tls_ticket_key_cipher, nullptr,
|
||||
EVP_EncryptInit_ex(ctx, get_config()->tls.ticket.cipher, nullptr,
|
||||
key.data.enc_key.data(), iv);
|
||||
HMAC_Init_ex(hctx, key.data.hmac_key.data(), key.hmac_keylen, key.hmac,
|
||||
nullptr);
|
||||
|
@ -411,7 +411,7 @@ int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
|
|||
// We assume that get_config()->npn_list contains ALPN protocol
|
||||
// identifier sorted by preference order. So we just break when we
|
||||
// found the first overlap.
|
||||
for (const auto &target_proto_id : get_config()->npn_list) {
|
||||
for (const auto &target_proto_id : get_config()->tls.npn_list) {
|
||||
for (auto p = in, end = in + inlen; p < end;) {
|
||||
auto proto_id = p + 1;
|
||||
auto proto_len = *p;
|
||||
|
@ -477,22 +477,24 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE |
|
||||
SSL_OP_SINGLE_DH_USE | SSL_OP_CIPHER_SERVER_PREFERENCE;
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, ssl_opts | get_config()->tls_proto_mask);
|
||||
auto &tlsconf = get_config()->tls;
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
|
||||
|
||||
const unsigned char sid_ctx[] = "shrpx";
|
||||
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
|
||||
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
|
||||
|
||||
if (get_config()->session_cache_memcached_host) {
|
||||
if (tlsconf.session_cache.memcached.host) {
|
||||
SSL_CTX_sess_set_new_cb(ssl_ctx, tls_session_new_cb);
|
||||
SSL_CTX_sess_set_get_cb(ssl_ctx, tls_session_get_cb);
|
||||
}
|
||||
|
||||
SSL_CTX_set_timeout(ssl_ctx, get_config()->tls_session_timeout.count());
|
||||
SSL_CTX_set_timeout(ssl_ctx, tlsconf.session_timeout.count());
|
||||
|
||||
const char *ciphers;
|
||||
if (get_config()->ciphers) {
|
||||
ciphers = get_config()->ciphers.get();
|
||||
if (tlsconf.ciphers) {
|
||||
ciphers = tlsconf.ciphers.get();
|
||||
} else {
|
||||
ciphers = nghttp2::ssl::DEFAULT_CIPHER_LIST;
|
||||
}
|
||||
|
@ -525,9 +527,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
|
||||
#endif // OPENSSL_NO_EC
|
||||
|
||||
if (get_config()->dh_param_file) {
|
||||
if (tlsconf.dh_param_file) {
|
||||
// Read DH parameters from file
|
||||
auto bio = BIO_new_file(get_config()->dh_param_file.get(), "r");
|
||||
auto bio = BIO_new_file(tlsconf.dh_param_file.get(), "r");
|
||||
if (bio == nullptr) {
|
||||
LOG(FATAL) << "BIO_new_file() failed: "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
|
@ -546,7 +548,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
if (get_config()->private_key_passwd) {
|
||||
if (tlsconf.private_key_passwd) {
|
||||
SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
|
||||
SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)get_config());
|
||||
}
|
||||
|
@ -576,14 +578,13 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
if (get_config()->verify_client) {
|
||||
if (get_config()->verify_client_cacert) {
|
||||
if (tlsconf.client_verify.enabled) {
|
||||
if (tlsconf.client_verify.cacert) {
|
||||
if (SSL_CTX_load_verify_locations(
|
||||
ssl_ctx, get_config()->verify_client_cacert.get(), nullptr) !=
|
||||
1) {
|
||||
ssl_ctx, tlsconf.client_verify.cacert.get(), nullptr) != 1) {
|
||||
|
||||
LOG(FATAL) << "Could not load trusted ca certificates from "
|
||||
<< get_config()->verify_client_cacert.get() << ": "
|
||||
<< tlsconf.client_verify.cacert.get() << ": "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
|
@ -591,11 +592,10 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
// error even though it returns success. See
|
||||
// http://forum.nginx.org/read.php?29,242540
|
||||
ERR_clear_error();
|
||||
auto list =
|
||||
SSL_load_client_CA_file(get_config()->verify_client_cacert.get());
|
||||
auto list = SSL_load_client_CA_file(tlsconf.client_verify.cacert.get());
|
||||
if (!list) {
|
||||
LOG(FATAL) << "Could not load ca certificates from "
|
||||
<< get_config()->verify_client_cacert.get() << ": "
|
||||
<< tlsconf.client_verify.cacert.get() << ": "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
|
@ -628,9 +628,9 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
|
|||
}
|
||||
|
||||
namespace {
|
||||
int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
|
||||
const unsigned char *in, unsigned int inlen,
|
||||
void *arg) {
|
||||
int select_h2_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg) {
|
||||
if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
|
||||
inlen)) {
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
@ -640,6 +640,24 @@ int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int select_h1_next_proto_cb(SSL *ssl, unsigned char **out,
|
||||
unsigned char *outlen, const unsigned char *in,
|
||||
unsigned int inlen, void *arg) {
|
||||
auto end = in + inlen;
|
||||
for (; in < end;) {
|
||||
if (util::streq_l(NGHTTP2_H1_1_ALPN, in, in[0] + 1)) {
|
||||
*out = const_cast<unsigned char *>(in) + 1;
|
||||
*outlen = in[0];
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
in += in[0] + 1;
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SSL_CTX *create_ssl_client_context(
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
neverbleed_t *nb
|
||||
|
@ -656,11 +674,13 @@ SSL_CTX *create_ssl_client_context(
|
|||
SSL_OP_NO_COMPRESSION |
|
||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, ssl_opts | get_config()->tls_proto_mask);
|
||||
auto &tlsconf = get_config()->tls;
|
||||
|
||||
SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask);
|
||||
|
||||
const char *ciphers;
|
||||
if (get_config()->ciphers) {
|
||||
ciphers = get_config()->ciphers.get();
|
||||
if (tlsconf.ciphers) {
|
||||
ciphers = tlsconf.ciphers.get();
|
||||
} else {
|
||||
ciphers = nghttp2::ssl::DEFAULT_CIPHER_LIST;
|
||||
}
|
||||
|
@ -678,57 +698,71 @@ SSL_CTX *create_ssl_client_context(
|
|||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
}
|
||||
|
||||
if (get_config()->cacert) {
|
||||
if (SSL_CTX_load_verify_locations(ssl_ctx, get_config()->cacert.get(),
|
||||
nullptr) != 1) {
|
||||
if (tlsconf.cacert) {
|
||||
if (SSL_CTX_load_verify_locations(ssl_ctx, tlsconf.cacert.get(), nullptr) !=
|
||||
1) {
|
||||
|
||||
LOG(FATAL) << "Could not load trusted ca certificates from "
|
||||
<< get_config()->cacert.get() << ": "
|
||||
<< tlsconf.cacert.get() << ": "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
}
|
||||
|
||||
if (get_config()->client_private_key_file) {
|
||||
if (tlsconf.client.private_key_file) {
|
||||
#ifndef HAVE_NEVERBLEED
|
||||
if (SSL_CTX_use_PrivateKey_file(ssl_ctx,
|
||||
get_config()->client_private_key_file.get(),
|
||||
tlsconf.client.private_key_file.get(),
|
||||
SSL_FILETYPE_PEM) != 1) {
|
||||
LOG(FATAL) << "Could not load client private key from "
|
||||
<< get_config()->client_private_key_file.get() << ": "
|
||||
<< tlsconf.client.private_key_file.get() << ": "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
#else // HAVE_NEVERBLEED
|
||||
std::array<char, NEVERBLEED_ERRBUF_SIZE> errbuf;
|
||||
if (neverbleed_load_private_key_file(
|
||||
nb, ssl_ctx, get_config()->client_private_key_file.get(),
|
||||
errbuf.data()) != 1) {
|
||||
if (neverbleed_load_private_key_file(nb, ssl_ctx,
|
||||
tlsconf.client.private_key_file.get(),
|
||||
errbuf.data()) != 1) {
|
||||
LOG(FATAL) << "neverbleed_load_private_key_file failed: "
|
||||
<< errbuf.data();
|
||||
DIE();
|
||||
}
|
||||
#endif // HAVE_NEVERBLEED
|
||||
}
|
||||
if (get_config()->client_cert_file) {
|
||||
if (tlsconf.client.cert_file) {
|
||||
if (SSL_CTX_use_certificate_chain_file(
|
||||
ssl_ctx, get_config()->client_cert_file.get()) != 1) {
|
||||
ssl_ctx, tlsconf.client.cert_file.get()) != 1) {
|
||||
|
||||
LOG(FATAL) << "Could not load client certificate from "
|
||||
<< get_config()->client_cert_file.get() << ": "
|
||||
<< tlsconf.client.cert_file.get() << ": "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr);
|
||||
DIE();
|
||||
}
|
||||
}
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, nullptr);
|
||||
|
||||
auto &downstreamconf = get_config()->conn.downstream;
|
||||
|
||||
if (downstreamconf.proto == PROTO_HTTP2) {
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h2_next_proto_cb, nullptr);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
// ALPN advertisement; We only advertise HTTP/2
|
||||
auto proto_list = util::get_default_alpn();
|
||||
// ALPN advertisement; We only advertise HTTP/2
|
||||
auto proto_list = util::get_default_alpn();
|
||||
|
||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||
SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
} else {
|
||||
// NPN selection callback
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_h1_next_proto_cb, nullptr);
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
SSL_CTX_set_alpn_protos(
|
||||
ssl_ctx, reinterpret_cast<const unsigned char *>(NGHTTP2_H1_1_ALPN),
|
||||
str_size(NGHTTP2_H1_1_ALPN));
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
}
|
||||
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
@ -745,19 +779,23 @@ SSL *create_ssl(SSL_CTX *ssl_ctx) {
|
|||
}
|
||||
|
||||
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
||||
int addrlen) {
|
||||
int addrlen, const UpstreamAddr *faddr) {
|
||||
char host[NI_MAXHOST];
|
||||
char service[NI_MAXSERV];
|
||||
int rv;
|
||||
rv = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
if (rv != 0) {
|
||||
LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
if (addr->sa_family == AF_UNIX) {
|
||||
std::copy_n("localhost", sizeof("localhost"), host);
|
||||
service[0] = '\0';
|
||||
} else {
|
||||
rv = getnameinfo(addr, addrlen, host, sizeof(host), service,
|
||||
sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
if (rv != 0) {
|
||||
LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (addr->sa_family != AF_UNIX) {
|
||||
rv = util::make_socket_nodelay(fd);
|
||||
if (rv == -1) {
|
||||
LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
|
||||
|
@ -777,30 +815,39 @@ ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
|||
}
|
||||
}
|
||||
|
||||
return new ClientHandler(worker, fd, ssl, host, service);
|
||||
return new ClientHandler(worker, fd, ssl, host, service, addr->sa_family,
|
||||
faddr);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool tls_hostname_match(const char *pattern, const char *hostname) {
|
||||
const char *ptWildcard = strchr(pattern, '*');
|
||||
if (ptWildcard == nullptr) {
|
||||
return util::strieq(pattern, hostname);
|
||||
bool tls_hostname_match(const char *pattern, size_t plen, const char *hostname,
|
||||
size_t hlen) {
|
||||
auto pend = pattern + plen;
|
||||
auto ptWildcard = std::find(pattern, pend, '*');
|
||||
if (ptWildcard == pend) {
|
||||
return util::strieq(pattern, plen, hostname, hlen);
|
||||
}
|
||||
const char *ptLeftLabelEnd = strchr(pattern, '.');
|
||||
bool wildcardEnabled = true;
|
||||
|
||||
auto ptLeftLabelEnd = std::find(pattern, pend, '.');
|
||||
auto wildcardEnabled = true;
|
||||
// Do case-insensitive match. At least 2 dots are required to enable
|
||||
// wildcard match. Also wildcard must be in the left-most label.
|
||||
// Don't attempt to match a presented identifier where the wildcard
|
||||
// character is embedded within an A-label.
|
||||
if (ptLeftLabelEnd == 0 || strchr(ptLeftLabelEnd + 1, '.') == 0 ||
|
||||
ptLeftLabelEnd < ptWildcard || util::istarts_with(pattern, "xn--")) {
|
||||
if (ptLeftLabelEnd == pend ||
|
||||
std::find(ptLeftLabelEnd + 1, pend, '.') == pend ||
|
||||
ptLeftLabelEnd < ptWildcard ||
|
||||
util::istarts_with(pattern, plen, "xn--")) {
|
||||
wildcardEnabled = false;
|
||||
}
|
||||
|
||||
if (!wildcardEnabled) {
|
||||
return util::strieq(pattern, hostname);
|
||||
return util::strieq(pattern, plen, hostname, hlen);
|
||||
}
|
||||
const char *hnLeftLabelEnd = strchr(hostname, '.');
|
||||
if (hnLeftLabelEnd == 0 || !util::strieq(ptLeftLabelEnd, hnLeftLabelEnd)) {
|
||||
|
||||
auto hend = hostname + hlen;
|
||||
auto hnLeftLabelEnd = std::find(hostname, hend, '.');
|
||||
if (hnLeftLabelEnd == hend ||
|
||||
!util::strieq(ptLeftLabelEnd, pend, hnLeftLabelEnd, hend)) {
|
||||
return false;
|
||||
}
|
||||
// Perform wildcard match. Here '*' must match at least one
|
||||
|
@ -812,107 +859,143 @@ bool tls_hostname_match(const char *pattern, const char *hostname) {
|
|||
util::iends_with(hostname, hnLeftLabelEnd, ptWildcard + 1,
|
||||
ptLeftLabelEnd);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int verify_hostname(const char *hostname, const Address *addr,
|
||||
const std::vector<std::string> &dns_names,
|
||||
const std::vector<std::string> &ip_addrs,
|
||||
const std::string &common_name) {
|
||||
if (util::numeric_host(hostname)) {
|
||||
if (ip_addrs.empty()) {
|
||||
return util::strieq(common_name.c_str(), hostname) ? 0 : -1;
|
||||
}
|
||||
const void *saddr;
|
||||
switch (addr->su.storage.ss_family) {
|
||||
case AF_INET:
|
||||
saddr = &addr->su.in.sin_addr;
|
||||
ssize_t get_common_name(unsigned char **out_ptr, X509 *cert) {
|
||||
auto subjectname = X509_get_subject_name(cert);
|
||||
if (!subjectname) {
|
||||
LOG(WARN) << "Could not get X509 name object from the certificate.";
|
||||
return -1;
|
||||
}
|
||||
int lastpos = -1;
|
||||
for (;;) {
|
||||
lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
|
||||
if (lastpos == -1) {
|
||||
break;
|
||||
case AF_INET6:
|
||||
saddr = &addr->su.in6.sin6_addr;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < ip_addrs.size(); ++i) {
|
||||
if (addr->len == ip_addrs[i].size() &&
|
||||
memcmp(saddr, ip_addrs[i].c_str(), addr->len) == 0) {
|
||||
return 0;
|
||||
}
|
||||
auto entry = X509_NAME_get_entry(subjectname, lastpos);
|
||||
|
||||
auto outlen = ASN1_STRING_to_UTF8(out_ptr, X509_NAME_ENTRY_get_data(entry));
|
||||
if (outlen < 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (dns_names.empty()) {
|
||||
return tls_hostname_match(common_name.c_str(), hostname) ? 0 : -1;
|
||||
}
|
||||
for (size_t i = 0; i < dns_names.size(); ++i) {
|
||||
if (tls_hostname_match(dns_names[i].c_str(), hostname)) {
|
||||
return 0;
|
||||
}
|
||||
if (std::find(*out_ptr, *out_ptr + outlen, '\0') != *out_ptr + outlen) {
|
||||
// Embedded NULL is not permitted.
|
||||
continue;
|
||||
}
|
||||
return outlen;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
|
||||
std::vector<std::string> &ip_addrs,
|
||||
std::string &common_name) {
|
||||
GENERAL_NAMES *altnames = static_cast<GENERAL_NAMES *>(
|
||||
namespace {
|
||||
int verify_numeric_hostname(X509 *cert, const char *hostname, size_t hlen,
|
||||
const Address *addr) {
|
||||
const void *saddr;
|
||||
switch (addr->su.storage.ss_family) {
|
||||
case AF_INET:
|
||||
saddr = &addr->su.in.sin_addr;
|
||||
break;
|
||||
case AF_INET6:
|
||||
saddr = &addr->su.in6.sin6_addr;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto altnames = static_cast<GENERAL_NAMES *>(
|
||||
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
|
||||
if (altnames) {
|
||||
auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
|
||||
size_t n = sk_GENERAL_NAME_num(altnames);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const GENERAL_NAME *altname = sk_GENERAL_NAME_value(altnames, i);
|
||||
if (altname->type == GEN_DNS) {
|
||||
const char *name;
|
||||
name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
size_t len = ASN1_STRING_length(altname->d.ia5);
|
||||
if (std::find(name, name + len, '\0') != name + len) {
|
||||
// Embedded NULL is not permitted.
|
||||
continue;
|
||||
}
|
||||
dns_names.push_back(std::string(name, len));
|
||||
} else if (altname->type == GEN_IPADD) {
|
||||
const unsigned char *ip_addr = altname->d.iPAddress->data;
|
||||
if (!ip_addr) {
|
||||
continue;
|
||||
}
|
||||
size_t len = altname->d.iPAddress->length;
|
||||
ip_addrs.push_back(
|
||||
std::string(reinterpret_cast<const char *>(ip_addr), len));
|
||||
auto altname = sk_GENERAL_NAME_value(altnames, i);
|
||||
if (altname->type != GEN_IPADD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ip_addr = altname->d.iPAddress->data;
|
||||
if (!ip_addr) {
|
||||
continue;
|
||||
}
|
||||
size_t ip_addrlen = altname->d.iPAddress->length;
|
||||
|
||||
if (addr->len == ip_addrlen && memcmp(saddr, ip_addr, ip_addrlen) == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
X509_NAME *subjectname = X509_get_subject_name(cert);
|
||||
if (!subjectname) {
|
||||
LOG(WARN) << "Could not get X509 name object from the certificate.";
|
||||
return;
|
||||
|
||||
unsigned char *cn;
|
||||
auto cnlen = get_common_name(&cn, cert);
|
||||
if (cnlen == -1) {
|
||||
return -1;
|
||||
}
|
||||
int lastpos = -1;
|
||||
while (1) {
|
||||
lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
|
||||
if (lastpos == -1) {
|
||||
break;
|
||||
}
|
||||
X509_NAME_ENTRY *entry = X509_NAME_get_entry(subjectname, lastpos);
|
||||
unsigned char *out;
|
||||
int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry));
|
||||
if (outlen < 0) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(out, out + outlen, '\0') != out + outlen) {
|
||||
// Embedded NULL is not permitted.
|
||||
continue;
|
||||
}
|
||||
common_name.assign(&out[0], &out[outlen]);
|
||||
OPENSSL_free(out);
|
||||
break;
|
||||
|
||||
// cn is not NULL terminated
|
||||
auto rv = util::streq(hostname, hlen, cn, cnlen);
|
||||
OPENSSL_free(cn);
|
||||
|
||||
if (rv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int verify_hostname(X509 *cert, const char *hostname, size_t hlen,
|
||||
const Address *addr) {
|
||||
if (util::numeric_host(hostname)) {
|
||||
return verify_numeric_hostname(cert, hostname, hlen, addr);
|
||||
}
|
||||
|
||||
auto altnames = static_cast<GENERAL_NAMES *>(
|
||||
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
|
||||
if (altnames) {
|
||||
auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
|
||||
size_t n = sk_GENERAL_NAME_num(altnames);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto altname = sk_GENERAL_NAME_value(altnames, i);
|
||||
if (altname->type != GEN_DNS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto len = ASN1_STRING_length(altname->d.ia5);
|
||||
if (std::find(name, name + len, '\0') != name + len) {
|
||||
// Embedded NULL is not permitted.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tls_hostname_match(name, len, hostname, hlen)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char *cn;
|
||||
auto cnlen = get_common_name(&cn, cert);
|
||||
if (cnlen == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto rv = util::strieq(hostname, hlen, cn, cnlen);
|
||||
OPENSSL_free(cn);
|
||||
|
||||
if (rv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int check_cert(SSL *ssl, const DownstreamAddr *addr) {
|
||||
auto cert = SSL_get_peer_certificate(ssl);
|
||||
|
@ -921,21 +1004,19 @@ int check_cert(SSL *ssl, const DownstreamAddr *addr) {
|
|||
return -1;
|
||||
}
|
||||
auto cert_deleter = defer(X509_free, cert);
|
||||
long verify_res = SSL_get_verify_result(ssl);
|
||||
auto verify_res = SSL_get_verify_result(ssl);
|
||||
if (verify_res != X509_V_OK) {
|
||||
LOG(ERROR) << "Certificate verification failed: "
|
||||
<< X509_verify_cert_error_string(verify_res);
|
||||
return -1;
|
||||
}
|
||||
std::string common_name;
|
||||
std::vector<std::string> dns_names;
|
||||
std::vector<std::string> ip_addrs;
|
||||
get_altnames(cert, dns_names, ip_addrs, common_name);
|
||||
auto hostname = get_config()->backend_tls_sni_name
|
||||
? get_config()->backend_tls_sni_name.get()
|
||||
: addr->host.get();
|
||||
if (verify_hostname(hostname, &addr->addr, dns_names, ip_addrs,
|
||||
common_name) != 0) {
|
||||
|
||||
auto &backend_sni_name = get_config()->tls.backend_sni_name;
|
||||
|
||||
auto hostname = !backend_sni_name.empty() ? StringRef(backend_sni_name)
|
||||
: StringRef(addr->host);
|
||||
if (verify_hostname(cert, hostname.c_str(), hostname.size(), &addr->addr) !=
|
||||
0) {
|
||||
LOG(ERROR) << "Certificate verification failed: hostname does not match";
|
||||
return -1;
|
||||
}
|
||||
|
@ -969,7 +1050,7 @@ void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname,
|
|||
// some restrictions for wildcard hostname. We just ignore
|
||||
// these rules here but do the proper check when we do the
|
||||
// match.
|
||||
node->wildcard_certs.emplace_back(hostname, ssl_ctx);
|
||||
node->wildcard_certs.push_back({ssl_ctx, hostname, len});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -986,7 +1067,7 @@ void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname,
|
|||
new_node->ssl_ctx = ssl_ctx;
|
||||
} else {
|
||||
new_node->ssl_ctx = nullptr;
|
||||
new_node->wildcard_certs.emplace_back(hostname, ssl_ctx);
|
||||
new_node->wildcard_certs.push_back({ssl_ctx, hostname, len});
|
||||
}
|
||||
node->next.push_back(std::move(new_node));
|
||||
return;
|
||||
|
@ -1073,9 +1154,11 @@ SSL_CTX *cert_lookup_tree_lookup(CertNode *node, const char *hostname,
|
|||
// one character.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto &wildcert : node->wildcard_certs) {
|
||||
if (tls_hostname_match(wildcert.first, hostname)) {
|
||||
return wildcert.second;
|
||||
if (tls_hostname_match(wildcert.hostname, wildcert.hostnamelen, hostname,
|
||||
len)) {
|
||||
return wildcert.ssl_ctx;
|
||||
}
|
||||
}
|
||||
auto c = util::lowcase(hostname[j]);
|
||||
|
@ -1111,14 +1194,43 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
|
|||
return -1;
|
||||
}
|
||||
auto cert_deleter = defer(X509_free, cert);
|
||||
std::string common_name;
|
||||
std::vector<std::string> dns_names;
|
||||
std::vector<std::string> ip_addrs;
|
||||
get_altnames(cert, dns_names, ip_addrs, common_name);
|
||||
for (auto &dns_name : dns_names) {
|
||||
lt->add_cert(ssl_ctx, dns_name.c_str(), dns_name.size());
|
||||
|
||||
auto altnames = static_cast<GENERAL_NAMES *>(
|
||||
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
|
||||
if (altnames) {
|
||||
auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
|
||||
size_t n = sk_GENERAL_NAME_num(altnames);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto altname = sk_GENERAL_NAME_value(altnames, i);
|
||||
if (altname->type != GEN_DNS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto len = ASN1_STRING_length(altname->d.ia5);
|
||||
if (std::find(name, name + len, '\0') != name + len) {
|
||||
// Embedded NULL is not permitted.
|
||||
continue;
|
||||
}
|
||||
|
||||
lt->add_cert(ssl_ctx, name, len);
|
||||
}
|
||||
}
|
||||
lt->add_cert(ssl_ctx, common_name.c_str(), common_name.size());
|
||||
|
||||
unsigned char *cn;
|
||||
auto cnlen = get_common_name(&cn, cert);
|
||||
if (cnlen == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
lt->add_cert(ssl_ctx, reinterpret_cast<char *>(cn), cnlen);
|
||||
|
||||
OPENSSL_free(cn);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1139,12 +1251,14 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
neverbleed_t *nb
|
||||
#endif // HAVE_NEVERBLEED
|
||||
) {
|
||||
if (get_config()->upstream_no_tls) {
|
||||
if (get_config()->conn.upstream.no_tls) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
|
||||
get_config()->cert_file.get()
|
||||
auto &tlsconf = get_config()->tls;
|
||||
|
||||
auto ssl_ctx = ssl::create_ssl_context(tlsconf.private_key_file.get(),
|
||||
tlsconf.cert_file.get()
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
,
|
||||
nb
|
||||
|
@ -1153,7 +1267,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
|
||||
all_ssl_ctx.push_back(ssl_ctx);
|
||||
|
||||
if (get_config()->subcerts.empty()) {
|
||||
if (tlsconf.subcerts.empty()) {
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
||||
|
@ -1163,7 +1277,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
for (auto &keycert : get_config()->subcerts) {
|
||||
for (auto &keycert : tlsconf.subcerts) {
|
||||
auto ssl_ctx =
|
||||
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str()
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
|
@ -1179,8 +1293,8 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
}
|
||||
}
|
||||
|
||||
if (ssl::cert_lookup_tree_add_cert_from_file(
|
||||
cert_tree, ssl_ctx, get_config()->cert_file.get()) == -1) {
|
||||
if (ssl::cert_lookup_tree_add_cert_from_file(cert_tree, ssl_ctx,
|
||||
tlsconf.cert_file.get()) == -1) {
|
||||
LOG(FATAL) << "Failed to add default certificate.";
|
||||
DIE();
|
||||
}
|
||||
|
@ -1188,13 +1302,7 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
bool downstream_tls_enabled() {
|
||||
if (get_config()->client_mode) {
|
||||
return !get_config()->downstream_no_tls;
|
||||
}
|
||||
|
||||
return get_config()->http2_bridge && !get_config()->downstream_no_tls;
|
||||
}
|
||||
bool downstream_tls_enabled() { return !get_config()->conn.downstream.no_tls; }
|
||||
|
||||
SSL_CTX *setup_client_ssl_context(
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
|
@ -1213,7 +1321,8 @@ SSL_CTX *setup_client_ssl_context(
|
|||
}
|
||||
|
||||
CertLookupTree *create_cert_lookup_tree() {
|
||||
if (get_config()->upstream_no_tls || get_config()->subcerts.empty()) {
|
||||
if (get_config()->conn.upstream.no_tls ||
|
||||
get_config()->tls.subcerts.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return new ssl::CertLookupTree();
|
||||
|
|
|
@ -45,6 +45,7 @@ class ClientHandler;
|
|||
class Worker;
|
||||
class DownstreamConnectionPool;
|
||||
struct DownstreamAddr;
|
||||
struct UpstreamAddr;
|
||||
|
||||
namespace ssl {
|
||||
|
||||
|
@ -76,7 +77,7 @@ SSL_CTX *create_ssl_client_context(
|
|||
);
|
||||
|
||||
ClientHandler *accept_connection(Worker *worker, int fd, sockaddr *addr,
|
||||
int addrlen);
|
||||
int addrlen, const UpstreamAddr *faddr);
|
||||
|
||||
// Check peer's certificate against first downstream address in
|
||||
// Config::downstream_addrs. We only consider first downstream since
|
||||
|
@ -108,10 +109,16 @@ void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
|
|||
// them. If there is a match, its SSL_CTX is returned. If none
|
||||
// matches, query is continued to the next character.
|
||||
|
||||
struct WildcardCert {
|
||||
SSL_CTX *ssl_ctx;
|
||||
char *hostname;
|
||||
size_t hostnamelen;
|
||||
};
|
||||
|
||||
struct CertNode {
|
||||
// list of wildcard domain name and its SSL_CTX pair, the wildcard
|
||||
// '*' appears in this position.
|
||||
std::vector<std::pair<char *, SSL_CTX *>> wildcard_certs;
|
||||
std::vector<WildcardCert> wildcard_certs;
|
||||
// Next CertNode index of CertLookupTree::nodes
|
||||
std::vector<std::unique_ptr<CertNode>> next;
|
||||
// SSL_CTX for exact match
|
||||
|
@ -198,6 +205,13 @@ SSL *create_ssl(SSL_CTX *ssl_ctx);
|
|||
// Returns true if SSL/TLS is enabled on downstream
|
||||
bool downstream_tls_enabled();
|
||||
|
||||
// Performs TLS hostname match. |pattern| of length |plen| can
|
||||
// contain wildcard character '*', which matches prefix of target
|
||||
// hostname. There are several restrictions to make wildcard work.
|
||||
// The matching algorithm is based on RFC 6125.
|
||||
bool tls_hostname_match(const char *pattern, size_t plen, const char *hostname,
|
||||
size_t hlen);
|
||||
|
||||
} // namespace ssl
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -115,4 +115,37 @@ void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void) {
|
|||
SSL_CTX_free(ssl_ctx);
|
||||
}
|
||||
|
||||
template <size_t N, size_t M>
|
||||
bool tls_hostname_match_wrapper(const char(&pattern)[N],
|
||||
const char(&hostname)[M]) {
|
||||
return ssl::tls_hostname_match(pattern, N, hostname, M);
|
||||
}
|
||||
|
||||
void test_shrpx_ssl_tls_hostname_match(void) {
|
||||
CU_ASSERT(tls_hostname_match_wrapper("example.com", "example.com"));
|
||||
CU_ASSERT(tls_hostname_match_wrapper("example.com", "EXAMPLE.com"));
|
||||
|
||||
// check wildcard
|
||||
CU_ASSERT(tls_hostname_match_wrapper("*.example.com", "www.example.com"));
|
||||
CU_ASSERT(tls_hostname_match_wrapper("*w.example.com", "www.example.com"));
|
||||
CU_ASSERT(tls_hostname_match_wrapper("www*.example.com", "www1.example.com"));
|
||||
CU_ASSERT(
|
||||
tls_hostname_match_wrapper("www*.example.com", "WWW12.EXAMPLE.com"));
|
||||
// at least 2 dots are required after '*'
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("*.com", "example.com"));
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("*", "example.com"));
|
||||
// '*' must be in left most label
|
||||
CU_ASSERT(
|
||||
!tls_hostname_match_wrapper("blog.*.example.com", "blog.my.example.com"));
|
||||
// prefix is wrong
|
||||
CU_ASSERT(
|
||||
!tls_hostname_match_wrapper("client*.example.com", "server.example.com"));
|
||||
// '*' must match at least one character
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("www*.example.com", "www.example.com"));
|
||||
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("example.com", "nghttp2.org"));
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("www.example.com", "example.com"));
|
||||
CU_ASSERT(!tls_hostname_match_wrapper("example.com", "www.example.com"));
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace shrpx {
|
|||
|
||||
void test_shrpx_ssl_create_lookup_tree(void);
|
||||
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void);
|
||||
void test_shrpx_ssl_tls_hostname_match(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue