Merge branch 'nghttpx-rewrite-h2-coalesce'

This commit is contained in:
Tatsuhiro Tsujikawa 2016-02-29 00:52:19 +09:00
commit a59445ccff
47 changed files with 1227 additions and 952 deletions

View File

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

View File

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

View File

@ -12,37 +12,38 @@ use-cases. It also covers some useful options later.
Default mode Default mode
------------ ------------
If nghttpx is invoked without any :option:`--http2-proxy`, If nghttpx is invoked without :option:`--http2-proxy`, it operates in
:option:`--client`, and :option:`--client-proxy`, it operates in default mode. In this mode, it works as reverse proxy (gateway) for
default mode. In this mode, nghttpx frontend listens for HTTP/2 both HTTP/2 and HTTP/1 clients to backend servers. This is also known
requests and translates them to HTTP/1 requests. Thus it works as as "HTTP/2 router". If nghttpx is linked with spdylay library and
reverse proxy (gateway) for HTTP/2 clients to HTTP/1 web server. This frontend connection is SSL/TLS, the frontend also supports SPDY
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. protocol.
By default, this mode's frontend connection is encrypted using By default, frontend connection is encrypted using SSL/TLS. So
SSL/TLS. So server's private key and certificate must be supplied to server's private key and certificate must be supplied to the command
the command line (or through configuration file). In this case, the line (or through configuration file). In this case, the frontend
frontend protocol selection will be done via ALPN or NPN. protocol selection will be done via ALPN or NPN.
With :option:`--frontend-no-tls` option, user can turn off SSL/TLS in With :option:`--frontend-no-tls` option, user can turn off SSL/TLS in
frontend connection. In this case, SPDY protocol is not available frontend connection. In this case, SPDY protocol is not available
even if spdylay library is liked to nghttpx. HTTP/2 and HTTP/1 are even if spdylay library is liked to nghttpx. HTTP/2 and HTTP/1 are
available on the frontend and a HTTP/1 connection can be upgraded to available on the frontend, and an HTTP/1 connection can be upgraded to
HTTP/2 using HTTP Upgrade. Starting HTTP/2 connection by sending HTTP/2 using HTTP Upgrade. Starting HTTP/2 connection by sending
HTTP/2 connection preface is also supported. HTTP/2 connection preface is also supported.
By default, backend HTTP/1 connections are not encrypted. To enable By default, backend connections are not encrypted. To enable TLS
TLS on HTTP/1 backend connections, use :option:`--backend-http1-tls` encryption on backend connections, use :option:`--backend-tls` option.
option. This applies to all mode whose backend connections are Using patterns and ``proto`` keyword in :option:`--backend` option,
HTTP/1. backend application protocol can be specified per host/request path
pattern. It means that you can use both HTTP/2 and HTTP/1 in backend
connections at the same time. Note that default backend protocol is
HTTP/1.1. To use HTTP/2 in backend, you have to specify ``h2`` in
``proto`` keyword in :option:`--backend` explicitly.
The backend is supposed to be HTTP/1 Web server. For example, to make The backend is supposed to be Web server. For example, to make
nghttpx listen to encrypted HTTP/2 requests at port 8443, and a 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 backend Web server is configured to listen to HTTP request at port
port 8080 in the same host, run nghttpx command-line like this:: 8080 in the same host, run nghttpx command-line like this::
$ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt $ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
@ -58,8 +59,8 @@ If nghttpx is invoked with :option:`--http2-proxy` (or its shorthand
:option:`-s`) option, it operates in HTTP/2 proxy mode. The supported :option:`-s`) option, it operates in HTTP/2 proxy mode. The supported
protocols in frontend and backend connections are the same in `default protocols in frontend and backend connections are the same in `default
mode`_. The difference is that this mode acts like forward proxy and mode`_. The difference is that this mode acts like forward proxy and
assumes the backend is HTTP/1 proxy server (e.g., squid, traffic assumes the backend is HTTP proxy server (e.g., Squid, Apache Traffic
server). So HTTP/1 request must include absolute URI in request line. Server). HTTP/1 request must include absolute URI in request line.
By default, frontend connection is encrypted. So this mode is also By default, frontend connection is encrypted. So this mode is also
called secure proxy. If nghttpx is linked with spdylay, it supports called secure proxy. If nghttpx is linked with spdylay, it supports
@ -68,16 +69,22 @@ SPDY protocols and it works as so called SPDY proxy.
With :option:`--frontend-no-tls` option, SSL/TLS is turned off in With :option:`--frontend-no-tls` option, SSL/TLS is turned off in
frontend connection, so the connection gets insecure. frontend connection, so the connection gets insecure.
The backend must be HTTP/1 proxy server. nghttpx supports multiple The backend must be HTTP proxy server. nghttpx supports multiple
backend server addresses. It translates incoming requests to HTTP/1 backend server addresses. It translates incoming requests to HTTP
request to backend server. The backend server performs real proxy request to backend server. The backend server performs real proxy
work for each request, for example, dispatching requests to the origin work for each request, for example, dispatching requests to the origin
server and caching contents. server and caching contents.
The backend connection is not encrypted by default. To enable
encryption, use :option:`--backend-tls` option. The default backend
protocol is HTTP/1.1. To use HTTP/2 in backend connection, use
:option:`--backend` option, and specify ``h2`` in ``proto`` keyword
explicitly.
For example, to make nghttpx listen to encrypted HTTP/2 requests at For example, to make nghttpx listen to encrypted HTTP/2 requests at
port 8443, and a backend HTTP/1 proxy server is configured to listen port 8443, and a backend HTTP proxy server is configured to listen to
to HTTP/1 request at port 8080 in the same host, run nghttpx HTTP/1 request at port 8080 in the same host, run nghttpx command-line
command-line like this:: like this::
$ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt $ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
@ -122,132 +129,19 @@ Consult Traffic server `documentation
to know how to configure traffic server as forward proxy and its to know how to configure traffic server as forward proxy and its
security implications. security implications.
Client mode Disable frontend SSL/TLS
----------- ------------------------
If nghttpx is invoked with :option:`--client` option, it operates in The frontend connections are encrypted with SSL/TLS by default. To
client mode. In this mode, nghttpx listens for plain, unencrypted turn off SSL/TLS, use :option:`--frontend-no-tls` option. If this
HTTP/2 and HTTP/1 requests and translates them to encrypted HTTP/2 option is used, the private key and certificate are not required to
requests to the backend. User cannot enable SSL/TLS in frontend run nghttpx.
connection.
HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP Enable backend SSL/TLS
Upgrade. To disable SSL/TLS in backend connection, use ----------------------
:option:`--backend-no-tls` option.
By default, the number of backend HTTP/2 connections per worker The backend connections are not encrypted by default. To enable
(thread) is determined by number of :option:`--backend` option. To SSL/TLS encryption, :option:`--backend-tls` option.
adjust this value, use
:option:`--backend-http2-connections-per-worker` option.
The backend server is supporsed to be a HTTP/2 web server (e.g.,
nghttpd). The one use-case of this mode is utilize existing HTTP/1
clients to test HTTP/2 deployment. Suppose that HTTP/2 web server
listens to port 80 without encryption. Then run nghttpx as client
mode to access to that web server::
$ nghttpx --client -f127.0.0.1,8080 -b127.0.0.1,80 --backend-no-tls
.. note::
You may need :option:`--insecure` (or its shorthand :option:`-k`)
option if HTTP/2 server enables SSL/TLS and its certificate is
self-signed. But please note that it is insecure, and you should
know what you are doing.
Then you can use curl to access HTTP/2 server via nghttpx::
$ curl http://localhost:8080/
Client proxy mode
-----------------
If nghttpx is invoked with :option:`--client-proxy` (or its shorthand
:option:`-p`) option, it operates in client proxy mode. This mode
behaves like `client mode`_, but it works like forward proxy. So
HTTP/1 request must include absolute URI in request line.
HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP
Upgrade. To disable SSL/TLS in backend connection, use
:option:`--backend-no-tls` option.
By default, the number of backend HTTP/2 connections per worker
(thread) is determined by number of :option:`--backend` option. To
adjust this value, use
:option:`--backend-http2-connections-per-worker` option.
The backend server must be a HTTP/2 proxy. You can use nghttpx in
`HTTP/2 proxy mode`_ as backend server. The one use-case of this mode
is utilize existing HTTP/1 clients to test HTTP/2 connections between
2 proxies. The another use-case is use this mode to aggregate local
HTTP/1 connections to one HTTP/2 backend encrypted connection. This
makes HTTP/1 clients which does not support secure proxy can use
secure HTTP/2 proxy via nghttpx client mode.
Suppose that HTTP/2 proxy listens to port 8443, just like we saw in
`HTTP/2 proxy mode`_. To run nghttpx in client proxy mode to access
that server, invoke nghttpx like this::
$ nghttpx -p -f127.0.0.1,8080 -b127.0.0.1,8443
.. note::
You may need :option:`--insecure` (or its shorthand :option:`-k`)
option if HTTP/2 server's certificate is self-signed. But please
note that it is insecure, and you should know what you are doing.
Then you can use curl to issue HTTP request via HTTP/2 proxy::
$ curl --http-proxy=http://localhost:8080 http://www.google.com/
You can configure web browser to use localhost:8080 as forward
proxy.
HTTP/2 bridge mode
------------------
If nghttpx is invoked with :option:`--http2-bridge` option, it
operates in HTTP/2 bridge mode. The supported protocols in frontend
connections are the same in `default mode`_. The protocol in backend
is HTTP/2 only.
With :option:`--frontend-no-tls` option, SSL/TLS is turned off in
frontend connection, so the connection gets insecure. To disable
SSL/TLS in backend connection, use :option:`--backend-no-tls` option.
By default, the number of backend HTTP/2 connections per worker
(thread) is determined by number of :option:`--backend` option. To
adjust this value, use
:option:`--backend-http2-connections-per-worker` option.
The backend server is supporsed to be a HTTP/2 web server or HTTP/2
proxy. If backend server is HTTP/2 proxy, use
:option:`--no-location-rewrite` option to disable rewriting
``Location`` header field.
The use-case of this mode is aggregate the incoming connections to one
HTTP/2 connection. One backend HTTP/2 connection is created per
worker (thread).
Disable SSL/TLS
---------------
In `default mode`_, `HTTP/2 proxy mode`_ and `HTTP/2 bridge mode`_,
frontend connections are encrypted with SSL/TLS by default. To turn
off SSL/TLS, use :option:`--frontend-no-tls` option. If this option
is used, the private key and certificate are not required to run
nghttpx.
In `client mode`_, `client proxy mode`_ and `HTTP/2 bridge mode`_,
backend connections are encrypted with SSL/TLS by default. To turn
off SSL/TLS, use :option:`--backend-no-tls` option.
Enable SSL/TLS on HTTP/1 backend
--------------------------------
In all modes which use HTTP/1 as backend protocol, backend HTTP/1
connection is not encrypted by default. To enable encryption, use
:option:`--backend-http1-tls` option.
Enable SSL/TLS on memcached connection Enable SSL/TLS on memcached connection
-------------------------------------- --------------------------------------
@ -387,5 +281,37 @@ servers ``serv1:3000`` and ``serv2:3000`` for request host
backend=serv1,3000;example.com/myservice backend=serv1,3000;example.com/myservice
backend=serv2,3000;example.com/myservice backend=serv2,3000;example.com/myservice
For HTTP/2 backend, see also You can also specify backend application protocol in
:option:`--backend-http2-connections-per-worker` option. :option:`--backend` option using ``proto`` keyword after pattern.
Utilizing this allows ngttpx to route certain request to HTTP/2, other
requests to HTTP/1. For example, to route requests to ``/ws/`` in
backend HTTP/1.1 connection, and use backend HTTP/2 for other
requests, do this:
.. code-block:: text
backend=serv1,3000;/;proto=h2
backend=serv1,3000;/ws/;proto=http/1.1
Note that the backends share the same pattern must have the same
backend protocol. The default backend protocol is HTTP/1.1.
Deprecated modes
----------------
As of nghttpx 1.9.0, ``--http2-bridge``, ``--client`` and
``--client-proxy`` options were removed. These functionality can be
used using combinations of options.
* ``--http2-bridge``: Use
:option:`--backend`\='-b<ADDR>,<PORT>;;proto=h2', and
:option:`--backend-tls`.
* ``--client``: Use :option:`--frontend-no-tls`,
:option:`--backend`\='-b<ADDR>,<PORT>;;proto=h2', and
:option:`--backend-tls`.
* ``--client-proxy``: Use :option:`--http2-proxy`,
:option:`--frontend-no-tls`,
:option:`--backend`\='-b<ADDR>,<PORT>;;proto=h2', and
:option:`--backend-tls`.

View File

@ -122,7 +122,12 @@ OPTIONS = [
"tls-ticket-key-memcached-cert-file", "tls-ticket-key-memcached-cert-file",
"tls-ticket-key-memcached-private-key-file", "tls-ticket-key-memcached-private-key-file",
"tls-ticket-key-memcached-address-family", "tls-ticket-key-memcached-address-family",
"backend-address-family" "backend-address-family",
"frontend-http2-max-concurrent-streams",
"backend-http2-max-concurrent-streams",
"backend-connections-per-frontend",
"backend-tls",
"backend-connections-per-host"
] ]
LOGVARS = [ LOGVARS = [

View File

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

View File

@ -169,6 +169,7 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
shrpx_ssl_test.cc shrpx_ssl_test.h \ shrpx_ssl_test.cc shrpx_ssl_test.h \
shrpx_downstream_test.cc shrpx_downstream_test.h \ shrpx_downstream_test.cc shrpx_downstream_test.h \
shrpx_config_test.cc shrpx_config_test.h \ shrpx_config_test.cc shrpx_config_test.h \
shrpx_worker_test.cc shrpx_worker_test.h \
shrpx_http_test.cc shrpx_http_test.h \ shrpx_http_test.cc shrpx_http_test.h \
http2_test.cc http2_test.h \ http2_test.cc http2_test.h \
util_test.cc util_test.h \ util_test.cc util_test.h \

View File

@ -1416,9 +1416,9 @@ int lookup_method_token(const uint8_t *name, size_t namelen) {
return -1; return -1;
} }
const char *to_method_string(int method_token) { StringRef to_method_string(int method_token) {
// we happened to use same value for method with http-parser. // we happened to use same value for method with http-parser.
return http_method_str(static_cast<http_method>(method_token)); return StringRef{http_method_str(static_cast<http_method>(method_token))};
} }
int get_pure_path_component(const char **base, size_t *baselen, int get_pure_path_component(const char **base, size_t *baselen,

View File

@ -322,7 +322,11 @@ bool expect_response_body(int status_code);
int lookup_method_token(const uint8_t *name, size_t namelen); int lookup_method_token(const uint8_t *name, size_t namelen);
int lookup_method_token(const std::string &name); int lookup_method_token(const std::string &name);
const char *to_method_string(int method_token); // Returns string representation of |method_token|. This is wrapper
// function over http_method_str from http-parser. If |method_token|
// is not known to http-parser, "<unknown>" is returned. The returned
// StringRef is guaranteed to be NULL-terminated.
StringRef to_method_string(int method_token);
template <typename InputIt> template <typename InputIt>
std::string normalize_path(InputIt first, InputIt last) { std::string normalize_path(InputIt first, InputIt last) {

View File

@ -33,6 +33,7 @@
#include "shrpx_ssl_test.h" #include "shrpx_ssl_test.h"
#include "shrpx_downstream_test.h" #include "shrpx_downstream_test.h"
#include "shrpx_config_test.h" #include "shrpx_config_test.h"
#include "shrpx_worker_test.h"
#include "http2_test.h" #include "http2_test.h"
#include "util_test.h" #include "util_test.h"
#include "nghttp2_gzip_test.h" #include "nghttp2_gzip_test.h"
@ -118,8 +119,8 @@ int main(int argc, char *argv[]) {
shrpx::test_shrpx_config_read_tls_ticket_key_file) || shrpx::test_shrpx_config_read_tls_ticket_key_file) ||
!CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256", !CU_add_test(pSuite, "config_read_tls_ticket_key_file_aes_256",
shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) || shrpx::test_shrpx_config_read_tls_ticket_key_file_aes_256) ||
!CU_add_test(pSuite, "config_match_downstream_addr_group", !CU_add_test(pSuite, "worker_match_downstream_addr_group",
shrpx::test_shrpx_config_match_downstream_addr_group) || shrpx::test_shrpx_worker_match_downstream_addr_group) ||
!CU_add_test(pSuite, "http_create_forwarded", !CU_add_test(pSuite, "http_create_forwarded",
shrpx::test_shrpx_http_create_forwarded) || shrpx::test_shrpx_http_create_forwarded) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||

View File

@ -656,7 +656,7 @@ int create_tcp_server_socket(UpstreamAddr &faddr,
} }
faddr.fd = fd; faddr.fd = fd;
faddr.hostport = util::make_http_hostport(host.data(), faddr.port); faddr.hostport = util::make_http_hostport(StringRef{host.data()}, faddr.port);
LOG(NOTICE) << "Listening on " << faddr.hostport; LOG(NOTICE) << "Listening on " << faddr.hostport;
@ -1079,7 +1079,8 @@ void fill_default_config() {
tlsconf.session_timeout = std::chrono::hours(12); tlsconf.session_timeout = std::chrono::hours(12);
auto &httpconf = mod_config()->http; auto &httpconf = mod_config()->http;
httpconf.server_name = "nghttpx nghttp2/" NGHTTP2_VERSION; httpconf.server_name =
StringRef::from_lit("nghttpx nghttp2/" NGHTTP2_VERSION);
httpconf.no_host_rewrite = true; httpconf.no_host_rewrite = true;
httpconf.request_header_field_buffer = 64_k; httpconf.request_header_field_buffer = 64_k;
httpconf.max_request_header_fields = 100; httpconf.max_request_header_fields = 100;
@ -1096,6 +1097,7 @@ void fill_default_config() {
// HTTP/2 SPDY/3.1 has connection-level flow control. The default // HTTP/2 SPDY/3.1 has connection-level flow control. The default
// window size for HTTP/2 is 64KiB - 1. SPDY/3's default is 64KiB // window size for HTTP/2 is 64KiB - 1. SPDY/3's default is 64KiB
upstreamconf.connection_window_bits = 16; upstreamconf.connection_window_bits = 16;
upstreamconf.max_concurrent_streams = 100;
nghttp2_option_new(&upstreamconf.option); nghttp2_option_new(&upstreamconf.option);
nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1); nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1);
@ -1105,15 +1107,14 @@ void fill_default_config() {
{ {
auto &downstreamconf = http2conf.downstream; auto &downstreamconf = http2conf.downstream;
downstreamconf.window_bits = 16; downstreamconf.window_bits = 16;
downstreamconf.connection_window_bits = 16; downstreamconf.connection_window_bits = 30;
downstreamconf.max_concurrent_streams = 100;
nghttp2_option_new(&downstreamconf.option); nghttp2_option_new(&downstreamconf.option);
nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1); nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1);
nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100); nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100);
} }
http2conf.max_concurrent_streams = 100;
auto &loggingconf = mod_config()->logging; auto &loggingconf = mod_config()->logging;
{ {
auto &accessconf = loggingconf.access; auto &accessconf = loggingconf.access;
@ -1165,6 +1166,7 @@ void fill_default_config() {
downstreamconf.request_buffer_size = 16_k; downstreamconf.request_buffer_size = 16_k;
downstreamconf.response_buffer_size = 128_k; downstreamconf.response_buffer_size = 128_k;
downstreamconf.family = AF_UNSPEC; downstreamconf.family = AF_UNSPEC;
downstreamconf.no_tls = true;
} }
} }
@ -1188,48 +1190,49 @@ void print_help(std::ostream &out) {
print_usage(out); print_usage(out);
out << R"( out << R"(
<PRIVATE_KEY> <PRIVATE_KEY>
Set path to server's private key. Required unless -p, Set path to server's private key. Required unless
--client or --frontend-no-tls are given. --frontend-no-tls are given.
<CERT> Set path to server's certificate. Required unless -p, <CERT> Set path to server's certificate. Required unless
--client or --frontend-no-tls are given. To make OCSP --frontend-no-tls are given. To make OCSP stapling
stapling work, this must be absolute path. work, this must be an absolute path.
Options: Options:
The options are categorized into several groups. The options are categorized into several groups.
Connections: Connections:
-b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;<PATTERN>[:...]] -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][;proto=<PROTO>]]
Set backend host and port. The multiple backend Set backend host and port. The multiple backend
addresses are accepted by repeating this option. UNIX addresses are accepted by repeating this option. UNIX
domain socket can be specified by prefixing path name domain socket can be specified by prefixing path name
with "unix:" (e.g., unix:/var/run/backend.sock). with "unix:" (e.g., unix:/var/run/backend.sock).
Optionally, if <PATTERN>s are given, the backend address Optionally, if <PATTERN>s are given, the backend address
is only used if request matches the pattern. If -s or is only used if request matches the pattern. If
-p is used, <PATTERN>s are ignored. The pattern --http2-proxy is used, <PATTERN>s are ignored. The
matching is closely designed to ServeMux in net/http pattern matching is closely designed to ServeMux in
package of Go programming language. <PATTERN> consists net/http package of Go programming language. <PATTERN>
of path, host + path or just host. The path must start consists of path, host + path or just host. The path
with "/". If it ends with "/", it matches all request must start with "/". If it ends with "/", it matches
path in its subtree. To deal with the request to the all request path in its subtree. To deal with the
directory without trailing slash, the path which ends request to the directory without trailing slash, the
with "/" also matches the request path which only lacks path which ends with "/" also matches the request path
trailing '/' (e.g., path "/foo/" matches request path which only lacks trailing '/' (e.g., path "/foo/"
"/foo"). If it does not end with "/", it performs exact matches request path "/foo"). If it does not end with
match against the request path. If host is given, it "/", it performs exact match against the request path.
performs exact match against the request host. If host If host is given, it performs exact match against the
alone is given, "/" is appended to it, so that it request host. If host alone is given, "/" is appended
matches all request paths under the host (e.g., to it, so that it matches all request paths under the
specifying "nghttp2.org" equals to "nghttp2.org/"). host (e.g., specifying "nghttp2.org" equals to
"nghttp2.org/").
Patterns with host take precedence over patterns with Patterns with host take precedence over patterns with
just path. Then, longer patterns take precedence over just path. Then, longer patterns take precedence over
shorter ones, breaking a tie by the order of the shorter ones, breaking a tie by the order of the
appearance in the configuration. appearance in the configuration.
If <PATTERN> is omitted, "/" is used as pattern, which If <PATTERN> is omitted or empty string, "/" is used as
matches all request paths (catch-all pattern). The pattern, which matches all request paths (catch-all
catch-all backend must be given. pattern). The catch-all backend must be given.
When doing a match, nghttpx made some normalization to When doing a match, nghttpx made some normalization to
pattern, request host and path. For host part, they are pattern, request host and path. For host part, they are
@ -1252,6 +1255,15 @@ Connections:
The backend addresses sharing same <PATTERN> are grouped The backend addresses sharing same <PATTERN> are grouped
together forming load balancing group. together forming load balancing group.
Optionally, backend application protocol can be
specified in <PROTO>. All that share the same <PATTERN>
must have the same <PROTO> value if it is given.
<PROTO> should be one of the following list without
quotes: "h2", "http/1.1". The default value of <PROTO>
is "http/1.1". Note that usually "h2" refers to HTTP/2
over TLS. But in this option, it may mean HTTP/2 over
cleartext TCP unless --backend-tls is used.
Since ";" and ":" are used as delimiter, <PATTERN> must Since ";" and ":" are used as delimiter, <PATTERN> must
not contain these characters. Since ";" has special not contain these characters. Since ";" has special
meaning in shell, the option value must be quoted. meaning in shell, the option value must be quoted.
@ -1290,16 +1302,8 @@ Connections:
--backend-write-timeout options. --backend-write-timeout options.
--accept-proxy-protocol --accept-proxy-protocol
Accept PROXY protocol version 1 on frontend connection. Accept PROXY protocol version 1 on frontend connection.
--backend-no-tls --backend-tls
Disable SSL/TLS on backend connections. For HTTP/2 Enable SSL/TLS on backend connections.
backend connections, TLS is enabled by default. For
HTTP/1 backend connections, TLS is disabled by default,
and can be enabled by --backend-http1-tls option. If
both --backend-no-tls and --backend-http1-tls options
are used, --backend-no-tls has the precedence.
--backend-http1-tls
Enable SSL/TLS on backend HTTP/1 connections. See also
--backend-no-tls option.
Performance: Performance:
-n, --workers=<N> -n, --workers=<N>
@ -1352,31 +1356,24 @@ Performance:
accepts. Setting 0 means unlimited. accepts. Setting 0 means unlimited.
Default: )" << get_config()->conn.upstream.worker_connections Default: )" << get_config()->conn.upstream.worker_connections
<< R"( << R"(
--backend-http2-connections-per-worker=<N> --backend-connections-per-host=<N>
Set maximum number of backend HTTP/2 physical Set maximum number of backend concurrent connections
connections per worker. If pattern is used in -b (and/or streams in case of HTTP/2) per origin host.
option, this limit is applied to each pattern group (in This option is meaningful when --http2-proxy option is
other words, each pattern group can have maximum <N> used. The origin host is determined by authority
HTTP/2 connections). The default value is 0, which portion of request URI (or :authority header field for
means that the value is adjusted to the number of HTTP/2). To limit the number of connections per
backend addresses. If pattern is used, this adjustment frontend for default mode, use
is done for each pattern group. --backend-connections-per-frontend.
--backend-http1-connections-per-host=<N>
Set maximum number of backend concurrent HTTP/1
connections per origin host. This option is meaningful
when -s option is used. The origin host is determined
by authority portion of request URI (or :authority
header field for HTTP/2). To limit the number of
connections per frontend for default mode, use
--backend-http1-connections-per-frontend.
Default: )" << get_config()->conn.downstream.connections_per_host Default: )" << get_config()->conn.downstream.connections_per_host
<< R"( << R"(
--backend-http1-connections-per-frontend=<N> --backend-connections-per-frontend=<N>
Set maximum number of backend concurrent HTTP/1 Set maximum number of backend concurrent connections
connections per frontend. This option is only used for (and/or streams in case of HTTP/2) per frontend. This
default mode. 0 means unlimited. To limit the number option is only used for default mode. 0 means
of connections per host for HTTP/2 or SPDY proxy mode unlimited. To limit the number of connections per host
(-s option), use --backend-http1-connections-per-host. with --http2-proxy option, use
--backend-connections-per-host.
Default: )" Default: )"
<< get_config()->conn.downstream.connections_per_frontend << R"( << get_config()->conn.downstream.connections_per_frontend << R"(
--rlimit-nofile=<N> --rlimit-nofile=<N>
@ -1630,10 +1627,18 @@ SSL/TLS:
the complete HTTP/2 cipher suites black list. the complete HTTP/2 cipher suites black list.
HTTP/2 and SPDY: HTTP/2 and SPDY:
-c, --http2-max-concurrent-streams=<N> -c, --frontend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one Set the maximum number of the concurrent streams in one
HTTP/2 and SPDY session. frontend HTTP/2 and SPDY session.
Default: )" << get_config()->http2.max_concurrent_streams << R"( Default: )"
<< get_config()->http2.upstream.max_concurrent_streams << R"(
--backend-http2-max-concurrent-streams=<N>
Set the maximum number of the concurrent streams in one
backend HTTP/2 session. This sets maximum number of
concurrent opened pushed streams. The maximum number of
concurrent requests are set by a remote server.
Default: )"
<< get_config()->http2.downstream.max_concurrent_streams << R"(
--frontend-http2-window-bits=<N> --frontend-http2-window-bits=<N>
Sets the per-stream initial window size of HTTP/2 SPDY Sets the per-stream initial window size of HTTP/2 SPDY
frontend connection. For HTTP/2, the size is 2**<N>-1. frontend connection. For HTTP/2, the size is 2**<N>-1.
@ -1667,37 +1672,20 @@ HTTP/2 and SPDY:
Disable HTTP/2 server push. Server push is supported by Disable HTTP/2 server push. Server push is supported by
default mode and HTTP/2 frontend via Link header field. default mode and HTTP/2 frontend via Link header field.
It is also supported if both frontend and backend are It is also supported if both frontend and backend are
HTTP/2 (which implies --http2-bridge or --client mode). HTTP/2 in default mode. In this case, server push from
In this case, server push from backend session is backend session is relayed to frontend, and server push
relayed to frontend, and server push via Link header via Link header field is also supported. SPDY frontend
field is also supported. HTTP SPDY frontend does not does not support server push.
support server push.
Mode: Mode:
(default mode) (default mode)
Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. If Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. If
--frontend-no-tls is used, accept HTTP/2 and HTTP/1.1. --frontend-no-tls is used, accept HTTP/2 and HTTP/1.1.
The incoming HTTP/1.1 connection can be upgraded to The incoming HTTP/1.1 connection can be upgraded to
HTTP/2 through HTTP Upgrade. The protocol to the HTTP/2 through HTTP Upgrade.
backend is HTTP/1.1.
-s, --http2-proxy -s, --http2-proxy
Like default mode, but enable secure proxy mode. Like default mode, but enable forward proxy. This is so
--http2-bridge called HTTP/2 proxy mode.
Like default mode, but communicate with the backend in
HTTP/2 over SSL/TLS. Thus the incoming all connections
are converted to HTTP/2 connection and relayed to the
backend. See --backend-http-proxy-uri option if you are
behind the proxy and want to connect to the outside
HTTP/2 proxy.
--client Accept HTTP/2 and HTTP/1.1 without SSL/TLS. The
incoming HTTP/1.1 connection can be upgraded to HTTP/2
connection through HTTP Upgrade. The protocol to the
backend is HTTP/2. To use nghttpx as a forward proxy,
use -p option instead.
-p, --client-proxy
Like --client option, but it also requires the request
path from frontend must be an absolute URI, suitable for
use as a forward proxy.
Logging: Logging:
-L, --log-level=<LEVEL> -L, --log-level=<LEVEL>
@ -1798,15 +1786,13 @@ HTTP:
--no-via Don't append to Via header field. If Via header field --no-via Don't append to Via header field. If Via header field
is received, it is left unaltered. is received, it is left unaltered.
--no-location-rewrite --no-location-rewrite
Don't rewrite location header field on --http2-bridge, Don't rewrite location header field in default mode.
--client and default mode. For --http2-proxy and When --http2-proxy is used, location header field will
--client-proxy mode, location header field will not be not be altered regardless of this option.
altered regardless of this option.
--host-rewrite --host-rewrite
Rewrite host and :authority header fields on Rewrite host and :authority header fields in default
--http2-bridge, --client and default mode. For mode. When --http2-proxy is used, these headers will
--http2-proxy and --client-proxy mode, these headers not be altered regardless of this option.
will not be altered regardless of this option.
--altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]> --altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
Specify protocol ID, port, host and origin of Specify protocol ID, port, host and origin of
alternative service. <HOST> and <ORIGIN> are optional. alternative service. <HOST> and <ORIGIN> are optional.
@ -2055,29 +2041,6 @@ void process_options(
upstreamconf.worker_connections = std::numeric_limits<size_t>::max(); upstreamconf.worker_connections = std::numeric_limits<size_t>::max();
} }
if (get_config()->http2_proxy + get_config()->http2_bridge +
get_config()->client_proxy + get_config()->client >
1) {
LOG(FATAL) << "--http2-proxy, --http2-bridge, --client-proxy and --client "
<< "cannot be used at the same time.";
exit(EXIT_FAILURE);
}
if (get_config()->client || get_config()->client_proxy) {
mod_config()->client_mode = true;
upstreamconf.no_tls = true;
}
if (get_config()->client_mode || get_config()->http2_bridge) {
downstreamconf.proto = PROTO_HTTP2;
} else {
downstreamconf.proto = PROTO_HTTP;
}
if (downstreamconf.proto == PROTO_HTTP && !downstreamconf.http1_tls) {
downstreamconf.no_tls = true;
}
if (!upstreamconf.no_tls && if (!upstreamconf.no_tls &&
(tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) { (tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) {
print_usage(std::cerr); print_usage(std::cerr);
@ -2098,23 +2061,34 @@ void process_options(
auto &addr_groups = downstreamconf.addr_groups; auto &addr_groups = downstreamconf.addr_groups;
if (addr_groups.empty()) { if (addr_groups.empty()) {
DownstreamAddr addr{}; DownstreamAddrConfig addr{};
addr.host = ImmutableString::from_lit(DEFAULT_DOWNSTREAM_HOST); addr.host = ImmutableString::from_lit(DEFAULT_DOWNSTREAM_HOST);
addr.port = DEFAULT_DOWNSTREAM_PORT; addr.port = DEFAULT_DOWNSTREAM_PORT;
DownstreamAddrGroup g(StringRef::from_lit("/")); DownstreamAddrGroupConfig g(StringRef::from_lit("/"));
g.proto = PROTO_HTTP1;
g.addrs.push_back(std::move(addr)); g.addrs.push_back(std::move(addr));
mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size()); mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size());
addr_groups.push_back(std::move(g)); addr_groups.push_back(std::move(g));
} else if (get_config()->http2_proxy || get_config()->client_proxy) { } else if (get_config()->http2_proxy) {
// We don't support host mapping in these cases. Move all // We don't support host mapping in these cases. Move all
// non-catch-all patterns to catch-all pattern. // non-catch-all patterns to catch-all pattern.
DownstreamAddrGroup catch_all(StringRef::from_lit("/")); DownstreamAddrGroupConfig catch_all(StringRef::from_lit("/"));
auto proto = PROTO_NONE;
for (auto &g : addr_groups) { for (auto &g : addr_groups) {
if (proto == PROTO_NONE) {
proto = g.proto;
} else if (proto != g.proto) {
LOG(ERROR) << SHRPX_OPT_BACKEND << ": <PATTERN> was ignored with "
"--http2-proxy, and protocol must "
"be the same for all backends.";
exit(EXIT_FAILURE);
}
std::move(std::begin(g.addrs), std::end(g.addrs), std::move(std::begin(g.addrs), std::end(g.addrs),
std::back_inserter(catch_all.addrs)); std::back_inserter(catch_all.addrs));
} }
std::vector<DownstreamAddrGroup>().swap(addr_groups); catch_all.proto = proto;
std::vector<DownstreamAddrGroupConfig>().swap(addr_groups);
// maybe not necessary? // maybe not necessary?
mod_config()->router = Router(); mod_config()->router = Router();
mod_config()->router.add_route(StringRef{catch_all.pattern}, mod_config()->router.add_route(StringRef{catch_all.pattern},
@ -2134,7 +2108,7 @@ void process_options(
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern
<< "'"; << "', proto=" << strproto(g.proto);
for (auto &addr : g.addrs) { for (auto &addr : g.addrs) {
LOG(INFO) << "group " << i << " -> " << addr.host.c_str() LOG(INFO) << "group " << i << " -> " << addr.host.c_str()
<< (addr.host_unix ? "" : ":" + util::utos(addr.port)); << (addr.host_unix ? "" : ":" + util::utos(addr.port));
@ -2143,7 +2117,7 @@ void process_options(
} }
if (catch_all_group == -1) { if (catch_all_group == -1) {
LOG(FATAL) << "-b: No catch-all backend address is configured"; LOG(FATAL) << "backend: No catch-all backend address is configured";
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -2187,7 +2161,7 @@ void process_options(
addr.hostport = ImmutableString( addr.hostport = ImmutableString(
util::make_http_hostport(StringRef(addr.host), addr.port)); util::make_http_hostport(StringRef(addr.host), addr.port));
auto hostport = util::make_hostport(addr.host.c_str(), addr.port); auto hostport = util::make_hostport(StringRef{addr.host}, addr.port);
if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port, if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,
downstreamconf.family) == -1) { downstreamconf.family) == -1) {
@ -2201,7 +2175,7 @@ void process_options(
auto &proxy = mod_config()->downstream_http_proxy; auto &proxy = mod_config()->downstream_http_proxy;
if (!proxy.host.empty()) { if (!proxy.host.empty()) {
auto hostport = util::make_hostport(proxy.host.c_str(), proxy.port); auto hostport = util::make_hostport(StringRef{proxy.host}, proxy.port);
if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port, if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port,
AF_UNSPEC) == -1) { AF_UNSPEC) == -1) {
LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport; LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport;
@ -2460,6 +2434,14 @@ int main(int argc, char **argv) {
{SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY,
required_argument, &flag, 115}, required_argument, &flag, 115},
{SHRPX_OPT_BACKEND_ADDRESS_FAMILY, required_argument, &flag, 116}, {SHRPX_OPT_BACKEND_ADDRESS_FAMILY, required_argument, &flag, 116},
{SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, required_argument,
&flag, 117},
{SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, required_argument,
&flag, 118},
{SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND, required_argument, &flag,
119},
{SHRPX_OPT_BACKEND_TLS, no_argument, &flag, 120},
{SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, required_argument, &flag, 121},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -2955,6 +2937,29 @@ int main(int argc, char **argv) {
// --backend-address-family // --backend-address-family
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, optarg); cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, optarg);
break; break;
case 117:
// --frontend-http2-max-concurrent-streams
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
optarg);
break;
case 118:
// --backend-http2-max-concurrent-streams
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
optarg);
break;
case 119:
// --backend-connections-per-frontend
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND,
optarg);
break;
case 120:
// --backend-tls
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, "yes");
break;
case 121:
// --backend-connections-per-host
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, optarg);
break;
default: default:
break; break;
} }

View File

@ -385,12 +385,7 @@ ClientHandler::ClientHandler(Worker *worker, int fd, SSL *ssl,
get_config()->conn.upstream.ratelimit.write, get_config()->conn.upstream.ratelimit.write,
get_config()->conn.upstream.ratelimit.read, writecb, readcb, get_config()->conn.upstream.ratelimit.read, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout), get_config()->tls.dyn_rec.idle_timeout, PROTO_NONE),
pinned_http2sessions_(
get_config()->conn.downstream.proto == PROTO_HTTP2
? make_unique<std::vector<ssize_t>>(
worker->get_downstream_addr_groups().size(), -1)
: nullptr),
ipaddr_(ipaddr), ipaddr_(ipaddr),
port_(port), port_(port),
faddr_(faddr), faddr_(faddr),
@ -642,13 +637,18 @@ void ClientHandler::pool_downstream_connection(
if (!dconn->poolable()) { if (!dconn->poolable()) {
return; return;
} }
dconn->set_client_handler(nullptr);
auto group = dconn->get_downstream_addr_group();
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get() CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get()
<< " in group " << dconn->get_group(); << " in group " << group;
} }
dconn->set_client_handler(nullptr);
auto dconn_pool = worker_->get_dconn_pool(); auto &dconn_pool = group->dconn_pool;
dconn_pool->add_downstream_connection(std::move(dconn)); dconn_pool.add_downstream_connection(std::move(dconn));
} }
void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) { void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
@ -656,13 +656,13 @@ void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
CLOG(INFO, this) << "Removing downstream connection DCONN:" << dconn CLOG(INFO, this) << "Removing downstream connection DCONN:" << dconn
<< " from pool"; << " from pool";
} }
auto dconn_pool = worker_->get_dconn_pool(); auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool->remove_downstream_connection(dconn); dconn_pool.remove_downstream_connection(dconn);
} }
std::unique_ptr<DownstreamConnection> std::unique_ptr<DownstreamConnection>
ClientHandler::get_downstream_connection(Downstream *downstream) { ClientHandler::get_downstream_connection(Downstream *downstream) {
size_t group; size_t group_idx;
auto &downstreamconf = get_config()->conn.downstream; auto &downstreamconf = get_config()->conn.downstream;
auto catch_all = downstreamconf.addr_group_catch_all; auto catch_all = downstreamconf.addr_group_catch_all;
auto &groups = worker_->get_downstream_addr_groups(); auto &groups = worker_->get_downstream_addr_groups();
@ -672,26 +672,26 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
// Fast path. If we have one group, it must be catch-all group. // Fast path. If we have one group, it must be catch-all group.
// HTTP/2 and client proxy modes fall in this case. // HTTP/2 and client proxy modes fall in this case.
if (groups.size() == 1) { if (groups.size() == 1) {
group = 0; group_idx = 0;
} else if (req.method == HTTP_CONNECT) { } else if (req.method == HTTP_CONNECT) {
// We don't know how to treat CONNECT request in host-path // We don't know how to treat CONNECT request in host-path
// mapping. It most likely appears in proxy scenario. Since we // mapping. It most likely appears in proxy scenario. Since we
// have dealt with proxy case already, just use catch-all group. // have dealt with proxy case already, just use catch-all group.
group = catch_all; group_idx = catch_all;
} else { } else {
auto &router = get_config()->router; auto &router = get_config()->router;
if (!req.authority.empty()) { if (!req.authority.empty()) {
group = group_idx =
match_downstream_addr_group(router, StringRef{req.authority}, match_downstream_addr_group(router, StringRef{req.authority},
StringRef{req.path}, groups, catch_all); StringRef{req.path}, groups, catch_all);
} else { } else {
auto h = req.fs.header(http2::HD_HOST); auto h = req.fs.header(http2::HD_HOST);
if (h) { if (h) {
group = group_idx =
match_downstream_addr_group(router, StringRef{h->value}, match_downstream_addr_group(router, StringRef{h->value},
StringRef{req.path}, groups, catch_all); StringRef{req.path}, groups, catch_all);
} else { } else {
group = group_idx =
match_downstream_addr_group(router, StringRef::from_lit(""), match_downstream_addr_group(router, StringRef::from_lit(""),
StringRef{req.path}, groups, catch_all); StringRef{req.path}, groups, catch_all);
} }
@ -699,11 +699,12 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
} }
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Downstream address group: " << group; CLOG(INFO, this) << "Downstream address group_idx: " << group_idx;
} }
auto dconn_pool = worker_->get_dconn_pool(); auto &group = worker_->get_downstream_addr_groups()[group_idx];
auto dconn = dconn_pool->pop_downstream_connection(group); auto &dconn_pool = group.dconn_pool;
auto dconn = dconn_pool.pop_downstream_connection();
if (!dconn) { if (!dconn) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -711,22 +712,34 @@ ClientHandler::get_downstream_connection(Downstream *downstream) {
<< " Create new one"; << " Create new one";
} }
auto dconn_pool = worker_->get_dconn_pool(); if (group.proto == PROTO_HTTP2) {
if (group.http2_freelist.empty()) {
if (downstreamconf.proto == PROTO_HTTP2) { if (LOG_ENABLED(INFO)) {
Http2Session *http2session; CLOG(INFO, this)
auto &pinned = (*pinned_http2sessions_)[group]; << "http2_freelist is empty; create new Http2Session";
if (pinned == -1) { }
http2session = worker_->next_http2_session(group); auto session = make_unique<Http2Session>(
pinned = http2session->get_index(); conn_.loop, worker_->get_cl_ssl_ctx(), worker_, &group);
} else { group.http2_freelist.append(session.release());
auto dgrp = worker_->get_dgrp(group);
http2session = dgrp->http2sessions[pinned].get();
} }
dconn = make_unique<Http2DownstreamConnection>(dconn_pool, http2session);
auto http2session = group.http2_freelist.head;
// TODO max_concurrent_streams option must be independent from
// frontend and backend.
if (http2session->max_concurrency_reached(1)) {
if (LOG_ENABLED(INFO)) {
CLOG(INFO, this) << "Maximum streams are reached for Http2Session("
<< http2session
<< "). Remove Http2Session from http2_freelist";
}
group.http2_freelist.remove(http2session);
}
dconn = make_unique<Http2DownstreamConnection>(http2session);
} else { } else {
dconn = make_unique<HttpDownstreamConnection>(dconn_pool, group, dconn =
conn_.loop, worker_); make_unique<HttpDownstreamConnection>(&group, conn_.loop, worker_);
} }
dconn->set_client_handler(this); dconn->set_client_handler(this);
return dconn; return dconn;
@ -840,11 +853,11 @@ void ClientHandler::write_accesslog(Downstream *downstream) {
upstream_accesslog( upstream_accesslog(
get_config()->logging.access.format, get_config()->logging.access.format,
LogSpec{ LogSpec{
downstream, StringRef(ipaddr_), http2::to_method_string(req.method), downstream, StringRef{ipaddr_}, http2::to_method_string(req.method),
req.method == HTTP_CONNECT req.method == HTTP_CONNECT
? StringRef(req.authority) ? StringRef(req.authority)
: (get_config()->http2_proxy || get_config()->client_proxy) : get_config()->http2_proxy
? StringRef(construct_absolute_request_uri(req)) ? StringRef(construct_absolute_request_uri(req))
: req.path.empty() : req.path.empty()
? req.method == HTTP_OPTIONS ? req.method == HTTP_OPTIONS
@ -1125,18 +1138,18 @@ int ClientHandler::proxy_protocol_read() {
return on_proxy_protocol_finish(); return on_proxy_protocol_finish();
} }
StringRef ClientHandler::get_forwarded_by() { StringRef ClientHandler::get_forwarded_by() const {
auto &fwdconf = get_config()->http.forwarded; auto &fwdconf = get_config()->http.forwarded;
if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) { if (fwdconf.by_node_type == FORWARDED_NODE_OBFUSCATED) {
return StringRef(fwdconf.by_obfuscated); return StringRef(fwdconf.by_obfuscated);
} }
return StringRef(faddr_->hostport); return StringRef{faddr_->hostport};
} }
const std::string &ClientHandler::get_forwarded_for() const { StringRef ClientHandler::get_forwarded_for() const {
return forwarded_for_; return StringRef{forwarded_for_};
} }
} // namespace shrpx } // namespace shrpx

View File

@ -135,16 +135,15 @@ public:
// Returns string suitable for use in "by" parameter of Forwarded // Returns string suitable for use in "by" parameter of Forwarded
// header field. // header field.
StringRef get_forwarded_by(); StringRef get_forwarded_by() const;
// Returns string suitable for use in "for" parameter of Forwarded // Returns string suitable for use in "for" parameter of Forwarded
// header field. // header field.
const std::string &get_forwarded_for() const; StringRef get_forwarded_for() const;
private: private:
Connection conn_; Connection conn_;
ev_timer reneg_shutdown_timer_; ev_timer reneg_shutdown_timer_;
std::unique_ptr<Upstream> upstream_; 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 // IP address of client. If UNIX domain socket is used, this is
// "localhost". // "localhost".
std::string ipaddr_; std::string ipaddr_;

View File

@ -571,33 +571,70 @@ int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) {
} // namespace } // namespace
namespace { namespace {
// Parses host-path mapping patterns in |src|, and stores mappings in // Parses host-path mapping patterns in |src_pattern|, and stores
// config. We will store each host-path pattern found in |src| with // mappings in config. We will store each host-path pattern found in
// |addr|. |addr| will be copied accordingly. Also we make a group // |src| with |addr|. |addr| will be copied accordingly. Also we
// based on the pattern. The "/" pattern is considered as catch-all. // make a group based on the pattern. The "/" pattern is considered
void parse_mapping(const DownstreamAddr &addr, const char *src) { // as catch-all. We also parse protocol specified in |src_proto|.
//
// This function returns 0 if it succeeds, or -1.
int parse_mapping(const DownstreamAddrConfig &addr,
const StringRef &src_pattern, const StringRef &src_proto) {
// This returns at least 1 element (it could be empty string). We // This returns at least 1 element (it could be empty string). We
// will append '/' to all patterns, so it becomes catch-all pattern. // will append '/' to all patterns, so it becomes catch-all pattern.
auto mapping = util::split_config_str_list(src, ':'); auto mapping = util::split_str(src_pattern, ':');
assert(!mapping.empty()); assert(!mapping.empty());
auto &addr_groups = mod_config()->conn.downstream.addr_groups; auto &addr_groups = mod_config()->conn.downstream.addr_groups;
auto proto = PROTO_HTTP1;
if (!src_proto.empty()) {
if (!util::istarts_with_l(src_proto, "proto=")) {
LOG(ERROR) << "backend: proto keyword not found";
return -1;
}
auto protostr = StringRef{std::begin(src_proto) + str_size("proto="),
std::end(src_proto)};
if (protostr.empty()) {
LOG(ERROR) << "backend: protocol is empty";
return -1;
}
if (util::streq_l("h2", std::begin(protostr), protostr.size())) {
proto = PROTO_HTTP2;
} else if (util::streq_l("http/1.1", std::begin(protostr),
protostr.size())) {
proto = PROTO_HTTP1;
} else {
LOG(ERROR) << "backend: unknown protocol " << protostr;
return -1;
}
}
for (const auto &raw_pattern : mapping) { for (const auto &raw_pattern : mapping) {
auto done = false; auto done = false;
std::string pattern; std::string pattern;
auto slash = std::find(raw_pattern.first, raw_pattern.second, '/'); auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/');
if (slash == raw_pattern.second) { if (slash == std::end(raw_pattern)) {
// This effectively makes empty pattern to "/". // This effectively makes empty pattern to "/".
pattern.assign(raw_pattern.first, raw_pattern.second); pattern.assign(std::begin(raw_pattern), std::end(raw_pattern));
util::inp_strlower(pattern); util::inp_strlower(pattern);
pattern += '/'; pattern += '/';
} else { } else {
pattern.assign(raw_pattern.first, slash); pattern.assign(std::begin(raw_pattern), slash);
util::inp_strlower(pattern); util::inp_strlower(pattern);
pattern += http2::normalize_path(slash, raw_pattern.second); pattern += http2::normalize_path(slash, std::end(raw_pattern));
} }
for (auto &g : addr_groups) { for (auto &g : addr_groups) {
if (g.pattern == pattern) { if (g.pattern == pattern) {
if (g.proto != proto) {
LOG(ERROR) << "backend: protocol mismatch. We saw protocol "
<< strproto(g.proto) << " for pattern " << g.pattern
<< ", but another protocol " << strproto(proto);
return -1;
}
g.addrs.push_back(addr); g.addrs.push_back(addr);
done = true; done = true;
break; break;
@ -606,13 +643,15 @@ void parse_mapping(const DownstreamAddr &addr, const char *src) {
if (done) { if (done) {
continue; continue;
} }
DownstreamAddrGroup g(StringRef{pattern}); DownstreamAddrGroupConfig g(StringRef{pattern});
g.addrs.push_back(addr); g.addrs.push_back(addr);
g.proto = proto;
mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size()); mod_config()->router.add_route(StringRef{g.pattern}, addr_groups.size());
addr_groups.push_back(std::move(g)); addr_groups.push_back(std::move(g));
} }
return 0;
} }
} // namespace } // namespace
@ -654,12 +693,15 @@ enum {
SHRPX_OPTID_ALTSVC, SHRPX_OPTID_ALTSVC,
SHRPX_OPTID_BACKEND, SHRPX_OPTID_BACKEND,
SHRPX_OPTID_BACKEND_ADDRESS_FAMILY, SHRPX_OPTID_BACKEND_ADDRESS_FAMILY,
SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND,
SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST,
SHRPX_OPTID_BACKEND_HTTP_PROXY_URI, SHRPX_OPTID_BACKEND_HTTP_PROXY_URI,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST, SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
SHRPX_OPTID_BACKEND_HTTP1_TLS, SHRPX_OPTID_BACKEND_HTTP1_TLS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS, SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS,
SHRPX_OPTID_BACKEND_IPV4, SHRPX_OPTID_BACKEND_IPV4,
SHRPX_OPTID_BACKEND_IPV6, SHRPX_OPTID_BACKEND_IPV6,
@ -668,6 +710,7 @@ enum {
SHRPX_OPTID_BACKEND_READ_TIMEOUT, SHRPX_OPTID_BACKEND_READ_TIMEOUT,
SHRPX_OPTID_BACKEND_REQUEST_BUFFER, SHRPX_OPTID_BACKEND_REQUEST_BUFFER,
SHRPX_OPTID_BACKEND_RESPONSE_BUFFER, SHRPX_OPTID_BACKEND_RESPONSE_BUFFER,
SHRPX_OPTID_BACKEND_TLS,
SHRPX_OPTID_BACKEND_TLS_SNI_FIELD, SHRPX_OPTID_BACKEND_TLS_SNI_FIELD,
SHRPX_OPTID_BACKEND_WRITE_TIMEOUT, SHRPX_OPTID_BACKEND_WRITE_TIMEOUT,
SHRPX_OPTID_BACKLOG, SHRPX_OPTID_BACKLOG,
@ -692,6 +735,7 @@ enum {
SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS,
SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT, SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT,
SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS, SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS,
SHRPX_OPTID_FRONTEND_NO_TLS, SHRPX_OPTID_FRONTEND_NO_TLS,
@ -911,6 +955,11 @@ int option_lookup_token(const char *name, size_t namelen) {
break; break;
case 11: case 11:
switch (name[10]) { switch (name[10]) {
case 's':
if (util::strieq_l("backend-tl", name, 10)) {
return SHRPX_OPTID_BACKEND_TLS;
}
break;
case 't': case 't':
if (util::strieq_l("write-burs", name, 10)) { if (util::strieq_l("write-burs", name, 10)) {
return SHRPX_OPTID_WRITE_BURST; return SHRPX_OPTID_WRITE_BURST;
@ -1322,6 +1371,11 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS; return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS;
} }
break; break;
case 't':
if (util::strieq_l("backend-connections-per-hos", name, 27)) {
return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST;
}
break;
} }
break; break;
case 30: case 30:
@ -1342,6 +1396,15 @@ int option_lookup_token(const char *name, size_t namelen) {
break; break;
} }
break; break;
case 32:
switch (name[31]) {
case 'd':
if (util::strieq_l("backend-connections-per-fronten", name, 31)) {
return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND;
}
break;
}
break;
case 33: case 33:
switch (name[32]) { switch (name[32]) {
case 'l': case 'l':
@ -1398,6 +1461,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) { if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS; return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS;
} }
if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) {
return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS;
}
break; break;
} }
break; break;
@ -1412,6 +1478,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) { if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) {
return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS; return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS;
} }
if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) {
return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS;
}
break; break;
} }
break; break;
@ -1477,19 +1546,17 @@ int parse_config(const char *opt, const char *optarg,
switch (optid) { switch (optid) {
case SHRPX_OPTID_BACKEND: { case SHRPX_OPTID_BACKEND: {
auto optarglen = strlen(optarg); auto src = StringRef{optarg};
const char *pat_delim = strchr(optarg, ';'); auto addr_end = std::find(std::begin(src), std::end(src), ';');
if (!pat_delim) {
pat_delim = optarg + optarglen; DownstreamAddrConfig addr{};
}
DownstreamAddr addr{};
if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
auto path = optarg + str_size(SHRPX_UNIX_PATH_PREFIX); auto path = std::begin(src) + str_size(SHRPX_UNIX_PATH_PREFIX);
addr.host = ImmutableString(path, pat_delim); addr.host = ImmutableString(path, addr_end);
addr.host_unix = true; addr.host_unix = true;
} else { } else {
if (split_host_port(host, sizeof(host), &port, optarg, if (split_host_port(host, sizeof(host), &port, &src[0],
pat_delim - optarg) == -1) { addr_end - std::begin(src)) == -1) {
return -1; return -1;
} }
@ -1497,14 +1564,16 @@ int parse_config(const char *opt, const char *optarg,
addr.port = port; addr.port = port;
} }
auto mapping = pat_delim < optarg + optarglen ? pat_delim + 1 : pat_delim; auto mapping = addr_end == std::end(src) ? addr_end : addr_end + 1;
// We may introduce new parameter after additional ';', so don't auto mapping_end = std::find(mapping, std::end(src), ';');
// allow extra ';' in pattern for now.
if (strchr(mapping, ';') != nullptr) { auto proto = mapping_end == std::end(src) ? mapping_end : mapping_end + 1;
LOG(ERROR) << opt << ": ';' must not be used in pattern"; auto proto_end = std::find(proto, std::end(src), ';');
if (parse_mapping(addr, StringRef{mapping, mapping_end},
StringRef{proto, proto_end}) != 0) {
return -1; return -1;
} }
parse_mapping(addr, mapping);
return 0; return 0;
} }
@ -1559,8 +1628,20 @@ int parse_config(const char *opt, const char *optarg,
#else // !NOTHREADS #else // !NOTHREADS
return parse_uint(&mod_config()->num_worker, opt, optarg); return parse_uint(&mod_config()->num_worker, opt, optarg);
#endif // !NOTHREADS #endif // !NOTHREADS
case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: {
return parse_uint(&mod_config()->http2.max_concurrent_streams, opt, optarg); LOG(WARN) << opt << ": deprecated. Use "
<< SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and "
<< SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead.";
size_t n;
if (parse_uint(&n, opt, optarg) != 0) {
return -1;
}
auto &http2conf = mod_config()->http2;
http2conf.upstream.max_concurrent_streams = n;
http2conf.downstream.max_concurrent_streams = n;
return 0;
}
case SHRPX_OPTID_LOG_LEVEL: case SHRPX_OPTID_LOG_LEVEL:
if (Log::set_severity_level_by_name(optarg) == -1) { if (Log::set_severity_level_by_name(optarg) == -1) {
LOG(ERROR) << opt << ": Invalid severity level: " << optarg; LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
@ -1577,13 +1658,13 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_HTTP2_BRIDGE: case SHRPX_OPTID_HTTP2_BRIDGE:
mod_config()->http2_bridge = util::strieq(optarg, "yes"); LOG(ERROR) << opt << ": deprecated. Use backend=<addr>,<port>;;proto=h2 "
"and backend-tls";
return 0; return -1;
case SHRPX_OPTID_CLIENT_PROXY: case SHRPX_OPTID_CLIENT_PROXY:
mod_config()->client_proxy = util::strieq(optarg, "yes"); LOG(ERROR) << opt << ": deprecated. Use http2-proxy, frontend-no-tls, "
"backend=<addr>,<port>;;proto=h2 and backend-tls";
return 0; return -1;
case SHRPX_OPTID_ADD_X_FORWARDED_FOR: case SHRPX_OPTID_ADD_X_FORWARDED_FOR:
mod_config()->http.xff.add = util::strieq(optarg, "yes"); mod_config()->http.xff.add = util::strieq(optarg, "yes");
@ -1716,8 +1797,8 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_BACKEND_NO_TLS: case SHRPX_OPTID_BACKEND_NO_TLS:
mod_config()->conn.downstream.no_tls = util::strieq(optarg, "yes"); LOG(WARN) << opt << ": deprecated. backend connection is not encrypted by "
"default. See also " << SHRPX_OPT_BACKEND_TLS;
return 0; return 0;
case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD: case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD:
mod_config()->tls.backend_sni_name = optarg; mod_config()->tls.backend_sni_name = optarg;
@ -1804,9 +1885,9 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_CLIENT: case SHRPX_OPTID_CLIENT:
mod_config()->client = util::strieq(optarg, "yes"); LOG(ERROR) << opt << ": deprecated. Use frontend-no-tls, "
"backend=<addr>,<port>;;proto=h2 and backend-tls";
return 0; return -1;
case SHRPX_OPTID_INSECURE: case SHRPX_OPTID_INSECURE:
mod_config()->tls.insecure = util::strieq(optarg, "yes"); mod_config()->tls.insecure = util::strieq(optarg, "yes");
@ -2007,7 +2088,11 @@ int parse_config(const char *opt, const char *optarg,
"--host-rewrite option."; "--host-rewrite option.";
return 0; return 0;
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST: { case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST:
LOG(WARN) << opt
<< ": deprecated. Use backend-connections-per-host instead.";
// fall through
case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST: {
int n; int n;
if (parse_uint(&n, opt, optarg) != 0) { if (parse_uint(&n, opt, optarg) != 0) {
@ -2025,6 +2110,10 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
} }
case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND: case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND:
LOG(WARN) << opt << ": deprecated. Use "
<< SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead.";
// fall through
case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND:
return parse_uint(&mod_config()->conn.downstream.connections_per_frontend, return parse_uint(&mod_config()->conn.downstream.connections_per_frontend,
opt, optarg); opt, optarg);
case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT: case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT:
@ -2077,8 +2166,8 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER: case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER:
return parse_uint(&mod_config()->http2.downstream.connections_per_worker, LOG(WARN) << opt << ": deprecated.";
opt, optarg); return 0;
case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE: case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE:
mod_config()->tls.ocsp.fetch_ocsp_response_file = optarg; mod_config()->tls.ocsp.fetch_ocsp_response_file = optarg;
@ -2278,7 +2367,11 @@ int parse_config(const char *opt, const char *optarg,
return 0; return 0;
case SHRPX_OPTID_BACKEND_HTTP1_TLS: case SHRPX_OPTID_BACKEND_HTTP1_TLS:
mod_config()->conn.downstream.http1_tls = util::strieq(optarg, "yes"); LOG(WARN) << opt << ": deprecated. Use " << SHRPX_OPT_BACKEND_TLS
<< " instead.";
// fall through
case SHRPX_OPTID_BACKEND_TLS:
mod_config()->conn.downstream.no_tls = !util::strieq(optarg, "yes");
return 0; return 0;
case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS: case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS:
@ -2314,6 +2407,12 @@ int parse_config(const char *opt, const char *optarg,
case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY: case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY:
return parse_address_family(&mod_config()->conn.downstream.family, opt, return parse_address_family(&mod_config()->conn.downstream.family, opt,
optarg); optarg);
case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2.upstream.max_concurrent_streams, opt,
optarg);
case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS:
return parse_uint(&mod_config()->http2.downstream.max_concurrent_streams,
opt, optarg);
case SHRPX_OPTID_CONF: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";
@ -2493,93 +2592,17 @@ int int_syslog_facility(const char *strfacility) {
return -1; return -1;
} }
namespace { StringRef strproto(shrpx_proto proto) {
size_t match_downstream_addr_group_host( switch (proto) {
const Router &router, const StringRef &host, const StringRef &path, case PROTO_NONE:
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) { return StringRef::from_lit("none");
if (path.empty() || path[0] != '/') { case PROTO_HTTP1:
auto group = router.match(host, StringRef::from_lit("/")); return StringRef::from_lit("http/1.1");
if (group != -1) { case PROTO_HTTP2:
if (LOG_ENABLED(INFO)) { return StringRef::from_lit("h2");
LOG(INFO) << "Found pattern with query " << host case PROTO_MEMCACHED:
<< ", matched pattern=" << groups[group].pattern; return StringRef::from_lit("memcached");
}
return group;
}
return catch_all;
} }
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Perform mapping selection, using host=" << host
<< ", path=" << path;
}
auto group = router.match(host, path);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host << path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
group = router.match("", path);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "None match. Use catch-all pattern";
}
return catch_all;
}
} // namespace
size_t match_downstream_addr_group(
const Router &router, const StringRef &hostport, const StringRef &raw_path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (std::find(std::begin(hostport), std::end(hostport), '/') !=
std::end(hostport)) {
// We use '/' specially, and if '/' is included in host, it breaks
// our code. Select catch-all case.
return catch_all;
}
auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
auto query = std::find(std::begin(raw_path), fragment, '?');
auto path = StringRef{std::begin(raw_path), query};
if (hostport.empty()) {
return match_downstream_addr_group_host(router, hostport, path, groups,
catch_all);
}
std::string host;
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return catch_all;
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return catch_all;
}
host.assign(std::begin(hostport), p + 1);
} else {
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return catch_all;
}
host.assign(std::begin(hostport), p);
}
util::inp_strlower(host);
return match_downstream_addr_group_host(router, StringRef{host}, path, groups,
catch_all);
} }
} // namespace shrpx } // namespace shrpx

View File

@ -61,6 +61,7 @@ namespace shrpx {
struct LogFragment; struct LogFragment;
class ConnectBlocker; class ConnectBlocker;
class Http2Session;
namespace ssl { namespace ssl {
@ -227,10 +228,19 @@ constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE[] =
constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY[] = constexpr char SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY[] =
"tls-ticket-key-memcached-address-family"; "tls-ticket-key-memcached-address-family";
constexpr char SHRPX_OPT_BACKEND_ADDRESS_FAMILY[] = "backend-address-family"; constexpr char SHRPX_OPT_BACKEND_ADDRESS_FAMILY[] = "backend-address-family";
constexpr char SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS[] =
"frontend-http2-max-concurrent-streams";
constexpr char SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS[] =
"backend-http2-max-concurrent-streams";
constexpr char SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND[] =
"backend-connections-per-frontend";
constexpr char SHRPX_OPT_BACKEND_TLS[] = "backend-tls";
constexpr char SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST[] =
"backend-connections-per-host";
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP }; enum shrpx_proto { PROTO_NONE, PROTO_HTTP1, PROTO_HTTP2, PROTO_MEMCACHED };
enum shrpx_forwarded_param { enum shrpx_forwarded_param {
FORWARDED_NONE = 0, FORWARDED_NONE = 0,
@ -283,27 +293,26 @@ struct TLSSessionCache {
ev_tstamp last_updated; ev_tstamp last_updated;
}; };
struct DownstreamAddr { struct DownstreamAddrConfig {
Address addr; Address addr;
// backend address. If |host_unix| is true, this is UNIX domain // backend address. If |host_unix| is true, this is UNIX domain
// socket path. // socket path.
ImmutableString host; ImmutableString host;
ImmutableString hostport; ImmutableString hostport;
ConnectBlocker *connect_blocker;
// Client side TLS session cache
TLSSessionCache tls_session_cache;
// backend port. 0 if |host_unix| is true. // backend port. 0 if |host_unix| is true.
uint16_t port; uint16_t port;
// true if |host| contains UNIX domain socket path. // true if |host| contains UNIX domain socket path.
bool host_unix; bool host_unix;
}; };
struct DownstreamAddrGroup { struct DownstreamAddrGroupConfig {
DownstreamAddrGroup(const StringRef &pattern) DownstreamAddrGroupConfig(const StringRef &pattern)
: pattern(pattern.c_str(), pattern.size()) {} : pattern(pattern.c_str(), pattern.size()) {}
ImmutableString pattern; ImmutableString pattern;
std::vector<DownstreamAddr> addrs; std::vector<DownstreamAddrConfig> addrs;
// Application protocol used in this group
shrpx_proto proto;
}; };
struct TicketKey { struct TicketKey {
@ -481,19 +490,19 @@ struct Http2Config {
nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
size_t window_bits; size_t window_bits;
size_t connection_window_bits; size_t connection_window_bits;
size_t max_concurrent_streams;
} upstream; } upstream;
struct { struct {
nghttp2_option *option; nghttp2_option *option;
nghttp2_session_callbacks *callbacks; nghttp2_session_callbacks *callbacks;
size_t window_bits; size_t window_bits;
size_t connection_window_bits; size_t connection_window_bits;
size_t connections_per_worker; size_t max_concurrent_streams;
} downstream; } downstream;
struct { struct {
ev_tstamp stream_read; ev_tstamp stream_read;
ev_tstamp stream_write; ev_tstamp stream_write;
} timeout; } timeout;
size_t max_concurrent_streams;
bool no_cookie_crumbling; bool no_cookie_crumbling;
bool no_server_push; bool no_server_push;
}; };
@ -552,15 +561,13 @@ struct ConnectionConfig {
ev_tstamp write; ev_tstamp write;
ev_tstamp idle_read; ev_tstamp idle_read;
} timeout; } timeout;
std::vector<DownstreamAddrGroup> addr_groups; std::vector<DownstreamAddrGroupConfig> addr_groups;
// The index of catch-all group in downstream_addr_groups. // The index of catch-all group in downstream_addr_groups.
size_t addr_group_catch_all; size_t addr_group_catch_all;
size_t connections_per_host; size_t connections_per_host;
size_t connections_per_frontend; size_t connections_per_frontend;
size_t request_buffer_size; size_t request_buffer_size;
size_t response_buffer_size; size_t response_buffer_size;
// downstream protocol; this will be determined by given options.
shrpx_proto proto;
// Address family of backend connection. One of either AF_INET, // Address family of backend connection. One of either AF_INET,
// AF_INET6 or AF_UNSPEC. This is ignored if backend connection // AF_INET6 or AF_UNSPEC. This is ignored if backend connection
// is made via Unix domain socket. // is made via Unix domain socket.
@ -595,11 +602,6 @@ struct Config {
bool verbose; bool verbose;
bool daemon; bool daemon;
bool http2_proxy; bool http2_proxy;
bool http2_bridge;
bool client_proxy;
bool client;
// true if --client or --client-proxy are enabled.
bool client_mode;
}; };
const Config *get_config(); const Config *get_config();
@ -646,15 +648,8 @@ std::unique_ptr<TicketKeys>
read_tls_ticket_key_file(const std::vector<std::string> &files, read_tls_ticket_key_file(const std::vector<std::string> &files,
const EVP_CIPHER *cipher, const EVP_MD *hmac); const EVP_CIPHER *cipher, const EVP_MD *hmac);
// Selects group based on request's |hostport| and |path|. |hostport| // Returns string representation of |proto|.
// is the value taken from :authority or host header field, and may StringRef strproto(shrpx_proto proto);
// contain port. The |path| may contain query part. We require the
// catch-all pattern in place, so this function always selects one
// group. The catch-all group index is given in |catch_all|. All
// patterns are given in |groups|.
size_t match_downstream_addr_group(
const Router &router, const StringRef &hostport, const StringRef &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all);
} // namespace shrpx } // namespace shrpx

View File

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

View File

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

View File

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

View File

@ -203,7 +203,7 @@ int ConnectionHandler::create_single_worker() {
nb_.get(), nb_.get(),
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file}, StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, StringRef(), nullptr); StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(session_cache_ssl_ctx); all_ssl_ctx_.push_back(session_cache_ssl_ctx);
} }
@ -253,7 +253,7 @@ int ConnectionHandler::create_worker_thread(size_t num) {
nb_.get(), nb_.get(),
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file}, StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, StringRef{}, nullptr); StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(session_cache_ssl_ctx); all_ssl_ctx_.push_back(session_cache_ssl_ctx);
} }
auto worker = auto worker =
@ -767,7 +767,7 @@ SSL_CTX *ConnectionHandler::create_tls_ticket_key_memcached_ssl_ctx() {
nb_.get(), nb_.get(),
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file}, StringRef{tlsconf.cacert}, StringRef{memcachedconf.cert_file},
StringRef{memcachedconf.private_key_file}, StringRef{}, nullptr); StringRef{memcachedconf.private_key_file}, nullptr);
all_ssl_ctx_.push_back(ssl_ctx); all_ssl_ctx_.push_back(ssl_ctx);

View File

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

View File

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

View File

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

View File

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

View File

@ -77,8 +77,8 @@ void test_downstream_field_store_header(void) {
CU_ASSERT(nullptr == fs.header(http2::HD__METHOD)); CU_ASSERT(nullptr == fs.header(http2::HD__METHOD));
// By name // By name
CU_ASSERT(Header("alpha", "0") == *fs.header("alpha")); CU_ASSERT(Header("alpha", "0") == *fs.header(StringRef::from_lit("alpha")));
CU_ASSERT(nullptr == fs.header("bravo")); CU_ASSERT(nullptr == fs.header(StringRef::from_lit("bravo")));
} }
void test_downstream_crumble_request_cookie(void) { void test_downstream_crumble_request_cookie(void) {

View File

@ -62,9 +62,8 @@ std::string create_via_header_value(int major, int minor) {
} }
std::string create_forwarded(int params, const StringRef &node_by, std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for, const StringRef &node_for, const StringRef &host,
const std::string &host, const StringRef &proto) {
const std::string &proto) {
std::string res; std::string res;
if ((params & FORWARDED_BY) && !node_by.empty()) { if ((params & FORWARDED_BY) && !node_by.empty()) {
// This must be quoted-string unless it is obfuscated version // This must be quoted-string unless it is obfuscated version

View File

@ -43,8 +43,8 @@ std::string create_via_header_value(int major, int minor);
// |params| is bitwise-OR of zero or more of shrpx_forwarded_param // |params| is bitwise-OR of zero or more of shrpx_forwarded_param
// defined in shrpx_config.h. // defined in shrpx_config.h.
std::string create_forwarded(int params, const StringRef &node_by, std::string create_forwarded(int params, const StringRef &node_by,
const std::string &node_for, const StringRef &node_for, const StringRef &host,
const std::string &host, const std::string &proto); const StringRef &proto);
// Adds ANSI color codes to HTTP headers |hdrs|. // Adds ANSI color codes to HTTP headers |hdrs|.
std::string colorizeHeaders(const char *hdrs); std::string colorizeHeaders(const char *hdrs);

View File

@ -37,6 +37,7 @@
#include "shrpx_error.h" #include "shrpx_error.h"
#include "shrpx_http.h" #include "shrpx_http.h"
#include "shrpx_http2_session.h" #include "shrpx_http2_session.h"
#include "shrpx_worker.h"
#include "http2.h" #include "http2.h"
#include "util.h" #include "util.h"
@ -44,10 +45,8 @@ using namespace nghttp2;
namespace shrpx { namespace shrpx {
Http2DownstreamConnection::Http2DownstreamConnection( Http2DownstreamConnection::Http2DownstreamConnection(Http2Session *http2session)
DownstreamConnectionPool *dconn_pool, Http2Session *http2session) : dlnext(nullptr),
: DownstreamConnection(dconn_pool),
dlnext(nullptr),
dlprev(nullptr), dlprev(nullptr),
http2session_(http2session), http2session_(http2session),
sd_(nullptr) {} sd_(nullptr) {}
@ -106,6 +105,11 @@ int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
downstream_ = downstream; downstream_ = downstream;
downstream_->reset_downstream_rtimer(); downstream_->reset_downstream_rtimer();
auto &req = downstream_->request();
// HTTP/2 disables HTTP Upgrade.
req.upgrade_request = false;
return 0; return 0;
} }
@ -265,9 +269,9 @@ int Http2DownstreamConnection::push_request_headers() {
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
auto &http2conf = get_config()->http2; auto &http2conf = get_config()->http2;
auto no_host_rewrite = auto no_host_rewrite = httpconf.no_host_rewrite ||
httpconf.no_host_rewrite || get_config()->http2_proxy || get_config()->http2_proxy ||
get_config()->client_proxy || req.method == HTTP_CONNECT; req.method == HTTP_CONNECT;
// http2session_ has already in CONNECTED state, so we can get // http2session_ has already in CONNECTED state, so we can get
// addr_idx here. // addr_idx here.
@ -303,7 +307,7 @@ int Http2DownstreamConnection::push_request_headers() {
httpconf.add_request_headers.size()); httpconf.add_request_headers.size());
nva.push_back( nva.push_back(
http2::make_nv_lc_nocopy(":method", http2::to_method_string(req.method))); http2::make_nv_ls_nocopy(":method", http2::to_method_string(req.method)));
if (req.method != HTTP_CONNECT) { if (req.method != HTTP_CONNECT) {
assert(!req.scheme.empty()); assert(!req.scheme.empty());
@ -351,14 +355,13 @@ int Http2DownstreamConnection::push_request_headers() {
if (fwdconf.params) { if (fwdconf.params) {
auto params = fwdconf.params; auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy || if (get_config()->http2_proxy || req.method == HTTP_CONNECT) {
req.method == HTTP_CONNECT) {
params &= ~FORWARDED_PROTO; params &= ~FORWARDED_PROTO;
} }
auto value = http::create_forwarded(params, handler->get_forwarded_by(), auto value = http::create_forwarded(
handler->get_forwarded_for(), params, handler->get_forwarded_by(), handler->get_forwarded_for(),
req.authority, req.scheme); StringRef{req.authority}, StringRef{req.scheme});
if (fwd || !value.empty()) { if (fwd || !value.empty()) {
if (fwd) { if (fwd) {
forwarded_value = fwd->value; forwarded_value = fwd->value;
@ -395,8 +398,7 @@ int Http2DownstreamConnection::push_request_headers() {
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
} }
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && req.method != HTTP_CONNECT) {
req.method != HTTP_CONNECT) {
// We use same protocol with :scheme header field // We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme)); nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", req.scheme));
} }
@ -557,10 +559,9 @@ int Http2DownstreamConnection::on_timeout() {
return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR); return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR);
} }
size_t Http2DownstreamConnection::get_group() const { DownstreamAddrGroup *
// HTTP/2 backend connections are managed by Http2Session object, Http2DownstreamConnection::get_downstream_addr_group() const {
// and it stores group index. return http2session_->get_downstream_addr_group();
return http2session_->get_group();
} }
} // namespace shrpx } // namespace shrpx

View File

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

View File

@ -74,6 +74,10 @@ void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
SSLOG(INFO, http2session) << "ping timeout"; SSLOG(INFO, http2session) << "ping timeout";
} }
http2session->disconnect(); http2session->disconnect();
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return; return;
default: default:
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -92,6 +96,9 @@ void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
SSLOG(INFO, http2session) << "SETTINGS timeout"; SSLOG(INFO, http2session) << "SETTINGS timeout";
if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) { if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
http2session->disconnect(); http2session->disconnect();
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return; return;
} }
http2session->signal_write(); http2session->signal_write();
@ -109,6 +116,9 @@ void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
http2session->disconnect(http2session->get_state() == http2session->disconnect(http2session->get_state() ==
Http2Session::CONNECTING); Http2Session::CONNECTING);
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
} }
} // namespace } // namespace
@ -120,6 +130,9 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_read(); rv = http2session->do_read();
if (rv != 0) { if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail()); http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return; return;
} }
http2session->connection_alive(); http2session->connection_alive();
@ -127,6 +140,9 @@ void readcb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_write(); rv = http2session->do_write();
if (rv != 0) { if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail()); http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return; return;
} }
} }
@ -140,6 +156,9 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
rv = http2session->do_write(); rv = http2session->do_write();
if (rv != 0) { if (rv != 0) {
http2session->disconnect(http2session->should_hard_fail()); http2session->disconnect(http2session->should_hard_fail());
if (http2session->get_num_dconns() == 0) {
delete http2session;
}
return; return;
} }
http2session->reset_connection_check_timer_if_not_checking(); http2session->reset_connection_check_timer_if_not_checking();
@ -147,23 +166,23 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) {
} // namespace } // namespace
Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx, Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
Worker *worker, size_t group, size_t idx) Worker *worker, DownstreamAddrGroup *group)
: conn_(loop, -1, nullptr, worker->get_mcpool(), : dlnext(nullptr),
dlprev(nullptr),
conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->conn.downstream.timeout.write, get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, writecb, readcb, get_config()->conn.downstream.timeout.read, {}, {}, writecb, readcb,
timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout), get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP2),
wb_(worker->get_mcpool()), wb_(worker->get_mcpool()),
worker_(worker), worker_(worker),
ssl_ctx_(ssl_ctx), ssl_ctx_(ssl_ctx),
group_(group),
addr_(nullptr), addr_(nullptr),
session_(nullptr), session_(nullptr),
group_(group),
index_(idx),
state_(DISCONNECTED), state_(DISCONNECTED),
connection_check_state_(CONNECTION_CHECK_NONE), connection_check_state_(CONNECTION_CHECK_NONE),
flow_control_(false) { flow_control_(false) {
read_ = write_ = &Http2Session::noop; read_ = write_ = &Http2Session::noop;
on_read_ = &Http2Session::read_noop; on_read_ = &Http2Session::read_noop;
@ -182,7 +201,16 @@ Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx,
settings_timer_.data = this; settings_timer_.data = this;
} }
Http2Session::~Http2Session() { disconnect(); } Http2Session::~Http2Session() {
disconnect();
if (in_freelist()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Removed from http2_freelist";
}
group_->http2_freelist.remove(this);
}
}
int Http2Session::disconnect(bool hard) { int Http2Session::disconnect(bool hard) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
@ -252,8 +280,7 @@ int Http2Session::disconnect(bool hard) {
int Http2Session::initiate_connection() { int Http2Session::initiate_connection() {
int rv = 0; int rv = 0;
auto &groups = worker_->get_downstream_addr_groups(); auto &addrs = group_->addrs;
auto &addrs = groups[group_].addrs;
auto worker_blocker = worker_->get_connect_blocker(); auto worker_blocker = worker_->get_connect_blocker();
if (state_ == DISCONNECTED) { if (state_ == DISCONNECTED) {
@ -265,7 +292,7 @@ int Http2Session::initiate_connection() {
return -1; return -1;
} }
auto &next_downstream = worker_->get_dgrp(group_)->next; auto &next_downstream = group_->next;
auto end = next_downstream; auto end = next_downstream;
for (;;) { for (;;) {
@ -371,6 +398,8 @@ int Http2Session::initiate_connection() {
return -1; return -1;
} }
ssl::setup_downstream_http2_alpn(ssl);
conn_.set_ssl(ssl); conn_.set_ssl(ssl);
} }
@ -384,6 +413,13 @@ int Http2Session::initiate_connection() {
// at the time of this writing). // at the time of this writing).
SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str()); SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name.c_str());
} }
auto tls_session = ssl::reuse_tls_session(addr_);
if (tls_session) {
SSL_set_session(conn_.tls.ssl, tls_session);
SSL_SESSION_free(tls_session);
}
// If state_ == PROXY_CONNECTED, we has connected to the proxy // If state_ == PROXY_CONNECTED, we has connected to the proxy
// using conn_.fd and tunnel has been established. // using conn_.fd and tunnel has been established.
if (state_ == DISCONNECTED) { if (state_ == DISCONNECTED) {
@ -598,6 +634,17 @@ void Http2Session::remove_downstream_connection(
Http2DownstreamConnection *dconn) { Http2DownstreamConnection *dconn) {
dconns_.remove(dconn); dconns_.remove(dconn);
dconn->detach_stream_data(); dconn->detach_stream_data();
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Remove downstream";
}
if (!in_freelist() && !max_concurrency_reached()) {
if (LOG_ENABLED(INFO)) {
SSLOG(INFO, this) << "Append to Http2Session freelist";
}
group_->http2_freelist.append(this);
}
} }
void Http2Session::remove_stream_data(StreamData *sd) { void Http2Session::remove_stream_data(StreamData *sd) {
@ -1408,13 +1455,12 @@ int Http2Session::connection_made() {
std::array<nghttp2_settings_entry, 3> entry; std::array<nghttp2_settings_entry, 3> entry;
size_t nentry = 2; size_t nentry = 2;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = http2conf.max_concurrent_streams; entry[0].value = http2conf.downstream.max_concurrent_streams;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << http2conf.downstream.window_bits) - 1; entry[1].value = (1 << http2conf.downstream.window_bits) - 1;
if (http2conf.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].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
entry[nentry].value = 0; entry[nentry].value = 0;
++nentry; ++nentry;
@ -1800,6 +1846,13 @@ int Http2Session::tls_handshake() {
return -1; return -1;
} }
if (!SSL_session_reused(conn_.tls.ssl)) {
auto tls_session = SSL_get0_session(conn_.tls.ssl);
if (tls_session) {
ssl::try_cache_tls_session(addr_, tls_session, ev_now(conn_.loop));
}
}
read_ = &Http2Session::read_tls; read_ = &Http2Session::read_tls;
write_ = &Http2Session::write_tls; write_ = &Http2Session::write_tls;
@ -1888,11 +1941,7 @@ bool Http2Session::should_hard_fail() const {
} }
} }
const DownstreamAddr *Http2Session::get_addr() const { return addr_; } DownstreamAddr *Http2Session::get_addr() const { return addr_; }
size_t Http2Session::get_group() const { return group_; }
size_t Http2Session::get_index() const { return index_; }
int Http2Session::handle_downstream_push_promise(Downstream *downstream, int Http2Session::handle_downstream_push_promise(Downstream *downstream,
int32_t promised_stream_id) { int32_t promised_stream_id) {
@ -1911,10 +1960,8 @@ int Http2Session::handle_downstream_push_promise(Downstream *downstream,
// promised_downstream->get_stream() still returns 0. // promised_downstream->get_stream() still returns 0.
auto handler = upstream->get_client_handler(); auto handler = upstream->get_client_handler();
auto worker = handler->get_worker();
auto promised_dconn = auto promised_dconn = make_unique<Http2DownstreamConnection>(this);
make_unique<Http2DownstreamConnection>(worker->get_dconn_pool(), this);
promised_dconn->set_client_handler(handler); promised_dconn->set_client_handler(handler);
auto ptr = promised_dconn.get(); auto ptr = promised_dconn.get();
@ -1986,4 +2033,29 @@ int Http2Session::handle_downstream_push_promise_complete(
return 0; return 0;
} }
size_t Http2Session::get_num_dconns() const { return dconns_.size(); }
bool Http2Session::in_freelist() const {
return dlnext != nullptr || dlprev != nullptr ||
group_->http2_freelist.head == this ||
group_->http2_freelist.tail == this;
}
bool Http2Session::max_concurrency_reached(size_t extra) const {
if (!session_) {
return dconns_.size() + extra >= 100;
}
// If session does not allow further requests, it effectively means
// that maximum concurrency is reached.
return !nghttp2_session_check_request_allowed(session_) ||
dconns_.size() + extra >=
nghttp2_session_get_remote_settings(
session_, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
}
DownstreamAddrGroup *Http2Session::get_downstream_addr_group() const {
return group_;
}
} // namespace shrpx } // namespace shrpx

View File

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

View File

@ -316,7 +316,7 @@ int Http2Upstream::on_request_headers(Downstream *downstream,
if (path) { if (path) {
if (method_token == HTTP_OPTIONS && path->value == "*") { if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty. // Server-wide OPTIONS request. Path is empty.
} else if (get_config()->http2_proxy || get_config()->client_proxy) { } else if (get_config()->http2_proxy) {
req.path = http2::value_to_str(path); req.path = http2::value_to_str(path);
} else { } else {
const auto &value = path->value; const auto &value = path->value;
@ -824,9 +824,7 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
downstream_queue_( downstream_queue_(
get_config()->http2_proxy get_config()->http2_proxy
? get_config()->conn.downstream.connections_per_host ? get_config()->conn.downstream.connections_per_host
: get_config()->conn.downstream.proto == PROTO_HTTP : get_config()->conn.downstream.connections_per_frontend,
? get_config()->conn.downstream.connections_per_frontend
: 0,
!get_config()->http2_proxy), !get_config()->http2_proxy),
handler_(handler), handler_(handler),
session_(nullptr), session_(nullptr),
@ -846,7 +844,7 @@ Http2Upstream::Http2Upstream(ClientHandler *handler)
// TODO Maybe call from outside? // TODO Maybe call from outside?
std::array<nghttp2_settings_entry, 2> entry; std::array<nghttp2_settings_entry, 2> entry;
entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = http2conf.max_concurrent_streams; entry[0].value = http2conf.upstream.max_concurrent_streams;
entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
entry[1].value = (1 << http2conf.upstream.window_bits) - 1; entry[1].value = (1 << http2conf.upstream.window_bits) - 1;
@ -1348,8 +1346,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !httpconf.no_location_rewrite) {
!httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header(req.scheme); downstream->rewrite_location_response_header(req.scheme);
} }
@ -1418,7 +1415,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers()); http2::copy_headers_to_nva_nocopy(nva, resp.fs.headers());
if (!get_config()->http2_proxy && !get_config()->client_proxy) { if (!get_config()->http2_proxy) {
nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name)); nva.push_back(http2::make_nv_ls_nocopy("server", httpconf.server_name));
} else { } else {
auto server = resp.fs.header(http2::HD_SERVER); auto server = resp.fs.header(http2::HD_SERVER);
@ -1491,9 +1488,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
if (!http2conf.no_server_push && if (!http2conf.no_server_push &&
nghttp2_session_get_remote_settings(session_, nghttp2_session_get_remote_settings(session_,
NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 && NGHTTP2_SETTINGS_ENABLE_PUSH) == 1 &&
!get_config()->http2_proxy && !get_config()->client_proxy && !get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
(downstream->get_stream_id() % 2) && resp.fs.header(http2::HD_LINK) && resp.fs.header(http2::HD_LINK) && resp.http_status == 200 &&
resp.http_status == 200 &&
(req.method == HTTP_GET || req.method == HTTP_POST)) { (req.method == HTTP_GET || req.method == HTTP_POST)) {
if (prepare_push_promise(downstream) != 0) { if (prepare_push_promise(downstream) != 0) {
@ -1854,7 +1850,7 @@ bool Http2Upstream::push_enabled() const {
return !(get_config()->http2.no_server_push || return !(get_config()->http2.no_server_push ||
nghttp2_session_get_remote_settings( nghttp2_session_get_remote_settings(
session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 || session_, NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 ||
get_config()->http2_proxy || get_config()->client_proxy); get_config()->http2_proxy);
} }
int Http2Upstream::initiate_push(Downstream *downstream, const char *uri, int Http2Upstream::initiate_push(Downstream *downstream, const char *uri,

View File

@ -111,23 +111,22 @@ void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
} }
} // namespace } // namespace
HttpDownstreamConnection::HttpDownstreamConnection( HttpDownstreamConnection::HttpDownstreamConnection(DownstreamAddrGroup *group,
DownstreamConnectionPool *dconn_pool, size_t group, struct ev_loop *loop, struct ev_loop *loop,
Worker *worker) Worker *worker)
: DownstreamConnection(dconn_pool), : conn_(loop, -1, nullptr, worker->get_mcpool(),
conn_(loop, -1, nullptr, worker->get_mcpool(),
get_config()->conn.downstream.timeout.write, get_config()->conn.downstream.timeout.write,
get_config()->conn.downstream.timeout.read, {}, {}, connectcb, get_config()->conn.downstream.timeout.read, {}, {}, connectcb,
readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold, readcb, timeoutcb, this, get_config()->tls.dyn_rec.warmup_threshold,
get_config()->tls.dyn_rec.idle_timeout), get_config()->tls.dyn_rec.idle_timeout, PROTO_HTTP1),
do_read_(&HttpDownstreamConnection::noop), do_read_(&HttpDownstreamConnection::noop),
do_write_(&HttpDownstreamConnection::noop), do_write_(&HttpDownstreamConnection::noop),
worker_(worker), worker_(worker),
ssl_ctx_(worker->get_cl_ssl_ctx()), ssl_ctx_(worker->get_cl_ssl_ctx()),
group_(group),
addr_(nullptr), addr_(nullptr),
ioctrl_(&conn_.rlimit), ioctrl_(&conn_.rlimit),
response_htp_{0}, response_htp_{0} {}
group_(group) {}
HttpDownstreamConnection::~HttpDownstreamConnection() {} HttpDownstreamConnection::~HttpDownstreamConnection() {}
@ -154,13 +153,14 @@ int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
return -1; return -1;
} }
ssl::setup_downstream_http1_alpn(ssl);
conn_.set_ssl(ssl); conn_.set_ssl(ssl);
} }
auto &next_downstream = worker_->get_dgrp(group_)->next; auto &addrs = group_->addrs;
auto &next_downstream = group_->next;
auto end = next_downstream; auto end = next_downstream;
auto &groups = worker_->get_downstream_addr_groups();
auto &addrs = groups[group_].addrs;
for (;;) { for (;;) {
auto &addr = addrs[next_downstream]; auto &addr = addrs[next_downstream];
@ -279,9 +279,8 @@ int HttpDownstreamConnection::push_request_headers() {
// For HTTP/1.0 request, there is no authority in request. In that // For HTTP/1.0 request, there is no authority in request. In that
// case, we use backend server's host nonetheless. // case, we use backend server's host nonetheless.
auto authority = StringRef(downstream_hostport); auto authority = StringRef(downstream_hostport);
auto no_host_rewrite = httpconf.no_host_rewrite || auto no_host_rewrite =
get_config()->http2_proxy || httpconf.no_host_rewrite || get_config()->http2_proxy || connect_method;
get_config()->client_proxy || connect_method;
if (no_host_rewrite && !req.authority.empty()) { if (no_host_rewrite && !req.authority.empty()) {
authority = StringRef(req.authority); authority = StringRef(req.authority);
@ -293,12 +292,12 @@ int HttpDownstreamConnection::push_request_headers() {
// Assume that method and request path do not contain \r\n. // Assume that method and request path do not contain \r\n.
auto meth = http2::to_method_string(req.method); auto meth = http2::to_method_string(req.method);
buf->append(meth, strlen(meth)); buf->append(meth);
buf->append(" "); buf->append(" ");
if (connect_method) { if (connect_method) {
buf->append(authority); buf->append(authority);
} else if (get_config()->http2_proxy || get_config()->client_proxy) { } else if (get_config()->http2_proxy) {
// Construct absolute-form request target because we are going to // Construct absolute-form request target because we are going to
// send a request to a HTTP/1 proxy. // send a request to a HTTP/1 proxy.
assert(!req.scheme.empty()); assert(!req.scheme.empty());
@ -363,14 +362,13 @@ int HttpDownstreamConnection::push_request_headers() {
if (fwdconf.params) { if (fwdconf.params) {
auto params = fwdconf.params; auto params = fwdconf.params;
if (get_config()->http2_proxy || get_config()->client_proxy || if (get_config()->http2_proxy || connect_method) {
connect_method) {
params &= ~FORWARDED_PROTO; params &= ~FORWARDED_PROTO;
} }
auto value = http::create_forwarded(params, handler->get_forwarded_by(), auto value = http::create_forwarded(
handler->get_forwarded_for(), params, handler->get_forwarded_by(), handler->get_forwarded_for(),
req.authority, req.scheme); StringRef{req.authority}, StringRef{req.scheme});
if (fwd || !value.empty()) { if (fwd || !value.empty()) {
buf->append("Forwarded: "); buf->append("Forwarded: ");
if (fwd) { if (fwd) {
@ -407,8 +405,7 @@ int HttpDownstreamConnection::push_request_headers() {
buf->append((*xff).value); buf->append((*xff).value);
buf->append("\r\n"); buf->append("\r\n");
} }
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !connect_method) {
!connect_method) {
buf->append("X-Forwarded-Proto: "); buf->append("X-Forwarded-Proto: ");
assert(!req.scheme.empty()); assert(!req.scheme.empty());
buf->append(req.scheme); buf->append(req.scheme);
@ -508,8 +505,8 @@ void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection EOF"; DCLOG(INFO, dconn) << "Idle connection EOF";
} }
auto dconn_pool = dconn->get_dconn_pool(); auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool->remove_downstream_connection(dconn); dconn_pool.remove_downstream_connection(dconn);
// dconn was deleted // dconn was deleted
} }
} // namespace } // namespace
@ -521,8 +518,8 @@ void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
if (LOG_ENABLED(INFO)) { if (LOG_ENABLED(INFO)) {
DCLOG(INFO, dconn) << "Idle connection timeout"; DCLOG(INFO, dconn) << "Idle connection timeout";
} }
auto dconn_pool = dconn->get_dconn_pool(); auto &dconn_pool = dconn->get_downstream_addr_group()->dconn_pool;
dconn_pool->remove_downstream_connection(dconn); dconn_pool.remove_downstream_connection(dconn);
// dconn was deleted // dconn was deleted
} }
} // namespace } // namespace
@ -1033,7 +1030,7 @@ int HttpDownstreamConnection::process_input(const uint8_t *data,
} }
int HttpDownstreamConnection::connected() { int HttpDownstreamConnection::connected() {
auto connect_blocker = addr_->connect_blocker; auto &connect_blocker = addr_->connect_blocker;
if (!util::check_socket_connected(conn_.fd)) { if (!util::check_socket_connected(conn_.fd)) {
conn_.wlimit.stopw(); conn_.wlimit.stopw();
@ -1083,8 +1080,11 @@ void HttpDownstreamConnection::signal_write() {
ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE); ev_feed_event(conn_.loop, &conn_.wev, EV_WRITE);
} }
size_t HttpDownstreamConnection::get_group() const { return group_; }
int HttpDownstreamConnection::noop() { return 0; } int HttpDownstreamConnection::noop() { return 0; }
DownstreamAddrGroup *
HttpDownstreamConnection::get_downstream_addr_group() const {
return group_;
}
} // namespace shrpx } // namespace shrpx

View File

@ -37,11 +37,13 @@ namespace shrpx {
class DownstreamConnectionPool; class DownstreamConnectionPool;
class Worker; class Worker;
struct DownstreamAddrGroup;
struct DownstreamAddr;
class HttpDownstreamConnection : public DownstreamConnection { class HttpDownstreamConnection : public DownstreamConnection {
public: public:
HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool, size_t group, HttpDownstreamConnection(DownstreamAddrGroup *group, struct ev_loop *loop,
struct ev_loop *loop, Worker *worker); Worker *worker);
virtual ~HttpDownstreamConnection(); virtual ~HttpDownstreamConnection();
virtual int attach_downstream(Downstream *downstream); virtual int attach_downstream(Downstream *downstream);
virtual void detach_downstream(Downstream *downstream); virtual void detach_downstream(Downstream *downstream);
@ -58,10 +60,11 @@ public:
virtual int on_write(); virtual int on_write();
virtual void on_upstream_change(Upstream *upstream); virtual void on_upstream_change(Upstream *upstream);
virtual size_t get_group() const;
virtual bool poolable() const { return true; } virtual bool poolable() const { return true; }
virtual DownstreamAddrGroup *get_downstream_addr_group() const;
int read_clear(); int read_clear();
int write_clear(); int write_clear();
int read_tls(); int read_tls();
@ -81,11 +84,11 @@ private:
Worker *worker_; Worker *worker_;
// nullptr if TLS is not used. // nullptr if TLS is not used.
SSL_CTX *ssl_ctx_; SSL_CTX *ssl_ctx_;
DownstreamAddrGroup *group_;
// Address of remote endpoint // Address of remote endpoint
DownstreamAddr *addr_; DownstreamAddr *addr_;
IOControl ioctrl_; IOControl ioctrl_;
http_parser response_htp_; http_parser response_htp_;
size_t group_;
}; };
} // namespace shrpx } // namespace shrpx

View File

@ -40,25 +40,36 @@ namespace shrpx {
void test_shrpx_http_create_forwarded(void) { void test_shrpx_http_create_forwarded(void) {
CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";" CU_ASSERT("by=\"example.com:3000\";for=\"[::1]\";host=\"www.example.com\";"
"proto=https" == "proto=https" ==
http::create_forwarded( http::create_forwarded(FORWARDED_BY | FORWARDED_FOR |
FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO, FORWARDED_HOST | FORWARDED_PROTO,
"example.com:3000", "[::1]", "www.example.com", "https")); StringRef::from_lit("example.com:3000"),
StringRef::from_lit("[::1]"),
StringRef::from_lit("www.example.com"),
StringRef::from_lit("https")));
CU_ASSERT("for=192.168.0.1" == http::create_forwarded(FORWARDED_FOR, "alpha", CU_ASSERT("for=192.168.0.1" ==
"192.168.0.1", "bravo", http::create_forwarded(FORWARDED_FOR, StringRef::from_lit("alpha"),
"charlie")); StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("bravo"),
StringRef::from_lit("charlie")));
CU_ASSERT("by=_hidden;for=\"[::1]\"" == CU_ASSERT("by=_hidden;for=\"[::1]\"" ==
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "_hidden", http::create_forwarded(
"[::1]", "", "")); FORWARDED_BY | FORWARDED_FOR, StringRef::from_lit("_hidden"),
StringRef::from_lit("[::1]"), StringRef::from_lit(""),
StringRef::from_lit("")));
CU_ASSERT("by=\"[::1]\";for=_hidden" == CU_ASSERT("by=\"[::1]\";for=_hidden" ==
http::create_forwarded(FORWARDED_BY | FORWARDED_FOR, "[::1]", http::create_forwarded(
"_hidden", "", "")); FORWARDED_BY | FORWARDED_FOR, StringRef::from_lit("[::1]"),
StringRef::from_lit("_hidden"), StringRef::from_lit(""),
StringRef::from_lit("")));
CU_ASSERT("" == http::create_forwarded(FORWARDED_BY | FORWARDED_FOR | CU_ASSERT("" ==
FORWARDED_HOST | FORWARDED_PROTO, http::create_forwarded(
"", "", "", "")); FORWARDED_BY | FORWARDED_FOR | FORWARDED_HOST | FORWARDED_PROTO,
StringRef::from_lit(""), StringRef::from_lit(""),
StringRef::from_lit(""), StringRef::from_lit("")));
} }
} // namespace shrpx } // namespace shrpx

View File

@ -232,7 +232,7 @@ void rewrite_request_host_path_from_uri(Request &req, const char *uri,
path += '?'; path += '?';
path.append(uri + fdata.off, fdata.len); path.append(uri + fdata.off, fdata.len);
} }
if (get_config()->http2_proxy || get_config()->client_proxy) { if (get_config()->http2_proxy) {
req.path = std::move(path); req.path = std::move(path);
} else { } else {
req.path = http2::rewrite_clean_path(std::begin(path), std::end(path)); req.path = http2::rewrite_clean_path(std::begin(path), std::end(path));
@ -306,7 +306,7 @@ int htp_hdrs_completecb(http_parser *htp) {
} }
// checking UF_HOST could be redundant, but just in case ... // checking UF_HOST could be redundant, but just in case ...
if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) {
if (get_config()->http2_proxy || get_config()->client_proxy) { if (get_config()->http2_proxy) {
// Request URI should be absolute-form for client proxy mode // Request URI should be absolute-form for client proxy mode
return -1; return -1;
} }
@ -929,8 +929,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !httpconf.no_location_rewrite) {
!httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header( downstream->rewrite_location_response_header(
get_client_handler()->get_upstream_scheme()); get_client_handler()->get_upstream_scheme());
} }
@ -999,7 +998,7 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
} }
} }
if (!get_config()->http2_proxy && !get_config()->client_proxy) { if (!get_config()->http2_proxy) {
buf->append("Server: "); buf->append("Server: ");
buf->append(httpconf.server_name); buf->append(httpconf.server_name);
buf->append("\r\n"); buf->append("\r\n");

View File

@ -96,7 +96,7 @@ MemcachedConnection::MemcachedConnection(const Address *addr,
const StringRef &sni_name, const StringRef &sni_name,
MemchunkPool *mcpool) MemchunkPool *mcpool)
: conn_(loop, -1, nullptr, mcpool, write_timeout, read_timeout, {}, {}, : conn_(loop, -1, nullptr, mcpool, write_timeout, read_timeout, {}, {},
connectcb, readcb, timeoutcb, this, 0, 0.), connectcb, readcb, timeoutcb, this, 0, 0., PROTO_MEMCACHED),
do_read_(&MemcachedConnection::noop), do_read_(&MemcachedConnection::noop),
do_write_(&MemcachedConnection::noop), do_write_(&MemcachedConnection::noop),
sni_name_(sni_name.str()), sni_name_(sni_name.str()),

View File

@ -70,7 +70,7 @@ mrb_value request_get_method(mrb_state *mrb, mrb_value self) {
const auto &req = downstream->request(); const auto &req = downstream->request();
auto method = http2::to_method_string(req.method); auto method = http2::to_method_string(req.method);
return mrb_str_new_cstr(mrb, method); return mrb_str_new(mrb, method.c_str(), method.size());
} }
} // namespace } // namespace

View File

@ -262,7 +262,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
} else { } else {
req.scheme = scheme->value; req.scheme = scheme->value;
req.authority = host->value; req.authority = host->value;
if (get_config()->http2_proxy || get_config()->client_proxy) { if (get_config()->http2_proxy) {
req.path = path->value; req.path = path->value;
} else if (method_token == HTTP_OPTIONS && path->value == "*") { } else if (method_token == HTTP_OPTIONS && path->value == "*") {
// Server-wide OPTIONS request. Path is empty. // Server-wide OPTIONS request. Path is empty.
@ -503,9 +503,7 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
downstream_queue_( downstream_queue_(
get_config()->http2_proxy get_config()->http2_proxy
? get_config()->conn.downstream.connections_per_host ? get_config()->conn.downstream.connections_per_host
: get_config()->conn.downstream.proto == PROTO_HTTP : get_config()->conn.downstream.connections_per_frontend,
? get_config()->conn.downstream.connections_per_frontend
: 0,
!get_config()->http2_proxy), !get_config()->http2_proxy),
handler_(handler), handler_(handler),
session_(nullptr) { session_(nullptr) {
@ -547,7 +545,7 @@ SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
// TODO Maybe call from outside? // TODO Maybe call from outside?
std::array<spdylay_settings_entry, 2> entry; std::array<spdylay_settings_entry, 2> entry;
entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS; entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS;
entry[0].value = http2conf.max_concurrent_streams; entry[0].value = http2conf.upstream.max_concurrent_streams;
entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE; entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
@ -1011,8 +1009,7 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
auto &httpconf = get_config()->http; auto &httpconf = get_config()->http;
if (!get_config()->http2_proxy && !get_config()->client_proxy && if (!get_config()->http2_proxy && !httpconf.no_location_rewrite) {
!httpconf.no_location_rewrite) {
downstream->rewrite_location_response_header(req.scheme); downstream->rewrite_location_response_header(req.scheme);
} }
@ -1046,7 +1043,7 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
nv[hdidx++] = hd.value.c_str(); nv[hdidx++] = hd.value.c_str();
} }
if (!get_config()->http2_proxy && !get_config()->client_proxy) { if (!get_config()->http2_proxy) {
nv[hdidx++] = "server"; nv[hdidx++] = "server";
nv[hdidx++] = httpconf.server_name.c_str(); nv[hdidx++] = httpconf.server_name.c_str();
} else { } else {

View File

@ -659,12 +659,28 @@ int select_h1_next_proto_cb(SSL *ssl, unsigned char **out,
} }
} // namespace } // namespace
namespace {
int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen,
void *arg) {
auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
switch (conn->proto) {
case PROTO_HTTP1:
return select_h1_next_proto_cb(ssl, out, outlen, in, inlen, arg);
case PROTO_HTTP2:
return select_h2_next_proto_cb(ssl, out, outlen, in, inlen, arg);
default:
return SSL_TLSEXT_ERR_NOACK;
}
}
} // namespace
SSL_CTX *create_ssl_client_context( SSL_CTX *create_ssl_client_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
neverbleed_t *nb, neverbleed_t *nb,
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
const StringRef &cacert, const StringRef &cert_file, const StringRef &cacert, const StringRef &cert_file,
const StringRef &private_key_file, const StringRef &alpn, const StringRef &private_key_file,
int (*next_proto_select_cb)(SSL *s, unsigned char **out, int (*next_proto_select_cb)(SSL *s, unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg)) { unsigned int inlen, void *arg)) {
@ -742,14 +758,10 @@ SSL_CTX *create_ssl_client_context(
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
} }
// NPN selection callback // NPN selection callback. This is required to set SSL_CTX because
// OpenSSL does not offer SSL_set_next_proto_select_cb.
SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_cb, nullptr); SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_cb, nullptr);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN advertisement
SSL_CTX_set_alpn_protos(ssl_ctx, alpn.byte(), alpn.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
return ssl_ctx; return ssl_ctx;
} }
@ -1303,29 +1315,29 @@ SSL_CTX *setup_downstream_client_ssl_context(
} }
auto &tlsconf = get_config()->tls; auto &tlsconf = get_config()->tls;
auto &downstreamconf = get_config()->conn.downstream;
std::vector<unsigned char> h2alpn;
StringRef alpn;
int (*next_proto_select_cb)(SSL *s, unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg);
if (downstreamconf.proto == PROTO_HTTP2) {
h2alpn = util::get_default_alpn();
alpn = StringRef(h2alpn.data(), h2alpn.size());
next_proto_select_cb = select_h2_next_proto_cb;
} else {
alpn = StringRef::from_lit(NGHTTP2_H1_1_ALPN);
next_proto_select_cb = select_h1_next_proto_cb;
}
return ssl::create_ssl_client_context( return ssl::create_ssl_client_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
nb, nb,
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
StringRef{tlsconf.cacert}, StringRef{tlsconf.client.cert_file}, StringRef{tlsconf.cacert}, StringRef{tlsconf.client.cert_file},
StringRef{tlsconf.client.private_key_file}, alpn, next_proto_select_cb); StringRef{tlsconf.client.private_key_file}, select_next_proto_cb);
}
void setup_downstream_http2_alpn(SSL *ssl) {
auto alpn = util::get_default_alpn();
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN advertisement
SSL_set_alpn_protos(ssl, alpn.data(), alpn.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
}
void setup_downstream_http1_alpn(SSL *ssl) {
auto alpn = StringRef::from_lit(NGHTTP2_H1_1_ALPN);
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
// ALPN advertisement
SSL_set_alpn_protos(ssl, alpn.byte(), alpn.size());
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
} }
CertLookupTree *create_cert_lookup_tree() { CertLookupTree *create_cert_lookup_tree() {

View File

@ -71,13 +71,14 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
// Create client side SSL_CTX // Create client side SSL_CTX. This does not configure ALPN settings.
// |next_proto_select_cb| is for NPN.
SSL_CTX *create_ssl_client_context( SSL_CTX *create_ssl_client_context(
#ifdef HAVE_NEVERBLEED #ifdef HAVE_NEVERBLEED
neverbleed_t *nb, neverbleed_t *nb,
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
const StringRef &cacert, const StringRef &cert_file, const StringRef &cacert, const StringRef &cert_file,
const StringRef &private_key_file, const StringRef &alpn, const StringRef &private_key_file,
int (*next_proto_select_cb)(SSL *s, unsigned char **out, int (*next_proto_select_cb)(SSL *s, unsigned char **out,
unsigned char *outlen, const unsigned char *in, unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg)); unsigned int inlen, void *arg));
@ -201,6 +202,11 @@ SSL_CTX *setup_downstream_client_ssl_context(
#endif // HAVE_NEVERBLEED #endif // HAVE_NEVERBLEED
); );
// Sets ALPN settings in |SSL| suitable for HTTP/2 use.
void setup_downstream_http2_alpn(SSL *ssl);
// Sets ALPN settings in |SSL| suitable for HTTP/1.1 use.
void setup_downstream_http1_alpn(SSL *ssl);
// Creates CertLookupTree. If frontend is configured not to use TLS, // Creates CertLookupTree. If frontend is configured not to use TLS,
// this function returns nullptr. // this function returns nullptr.
CertLookupTree *create_cert_lookup_tree(); CertLookupTree *create_cert_lookup_tree();

View File

@ -71,15 +71,13 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
ssl::CertLookupTree *cert_tree, ssl::CertLookupTree *cert_tree,
const std::shared_ptr<TicketKeys> &ticket_keys) const std::shared_ptr<TicketKeys> &ticket_keys)
: randgen_(rd()), : randgen_(rd()),
dconn_pool_(get_config()->conn.downstream.addr_groups.size()), worker_stat_{},
worker_stat_(get_config()->conn.downstream.addr_groups.size()),
dgrps_(get_config()->conn.downstream.addr_groups.size()),
loop_(loop), loop_(loop),
sv_ssl_ctx_(sv_ssl_ctx), sv_ssl_ctx_(sv_ssl_ctx),
cl_ssl_ctx_(cl_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
cert_tree_(cert_tree), cert_tree_(cert_tree),
ticket_keys_(ticket_keys), ticket_keys_(ticket_keys),
downstream_addr_groups_(get_config()->conn.downstream.addr_groups), downstream_addr_groups_(get_config()->conn.downstream.addr_groups.size()),
connect_blocker_(make_unique<ConnectBlocker>(randgen_, loop_)), connect_blocker_(make_unique<ConnectBlocker>(randgen_, loop_)),
graceful_shutdown_(false) { graceful_shutdown_(false) {
ev_async_init(&w_, eventcb); ev_async_init(&w_, eventcb);
@ -100,25 +98,25 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
auto &downstreamconf = get_config()->conn.downstream; auto &downstreamconf = get_config()->conn.downstream;
if (downstreamconf.proto == PROTO_HTTP2) { for (size_t i = 0; i < downstreamconf.addr_groups.size(); ++i) {
auto n = get_config()->http2.downstream.connections_per_worker; auto &src = downstreamconf.addr_groups[i];
size_t group = 0; auto &dst = downstream_addr_groups_[i];
for (auto &dgrp : dgrps_) {
auto m = n;
if (m == 0) {
m = downstreamconf.addr_groups[group].addrs.size();
}
for (size_t idx = 0; idx < m; ++idx) {
dgrp.http2sessions.push_back(
make_unique<Http2Session>(loop_, cl_ssl_ctx, this, group, idx));
}
++group;
}
}
for (auto &group : downstream_addr_groups_) { dst.pattern = src.pattern;
for (auto &addr : group.addrs) { dst.addrs.resize(src.addrs.size());
addr.connect_blocker = new ConnectBlocker(randgen_, loop_); dst.proto = src.proto;
for (size_t j = 0; j < src.addrs.size(); ++j) {
auto &src_addr = src.addrs[j];
auto &dst_addr = dst.addrs[j];
dst_addr.addr = src_addr.addr;
dst_addr.host = src_addr.host;
dst_addr.hostport = src_addr.hostport;
dst_addr.port = src_addr.port;
dst_addr.host_unix = src_addr.host_unix;
dst_addr.connect_blocker = make_unique<ConnectBlocker>(randgen_, loop_);
} }
} }
} }
@ -126,12 +124,6 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
Worker::~Worker() { Worker::~Worker() {
ev_async_stop(loop_, &w_); ev_async_stop(loop_, &w_);
ev_timer_stop(loop_, &mcpool_clear_timer_); ev_timer_stop(loop_, &mcpool_clear_timer_);
for (auto &group : downstream_addr_groups_) {
for (auto &addr : group.addrs) {
delete addr.connect_blocker;
}
}
} }
void Worker::schedule_clear_mcpool() { void Worker::schedule_clear_mcpool() {
@ -253,24 +245,6 @@ void Worker::set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys) {
WorkerStat *Worker::get_worker_stat() { return &worker_stat_; } WorkerStat *Worker::get_worker_stat() { return &worker_stat_; }
DownstreamConnectionPool *Worker::get_dconn_pool() { return &dconn_pool_; }
Http2Session *Worker::next_http2_session(size_t group) {
auto &dgrp = dgrps_[group];
auto &http2sessions = dgrp.http2sessions;
if (http2sessions.empty()) {
return nullptr;
}
auto res = http2sessions[dgrp.next_http2session].get();
++dgrp.next_http2session;
if (dgrp.next_http2session >= http2sessions.size()) {
dgrp.next_http2session = 0;
}
return res;
}
struct ev_loop *Worker::get_loop() const { struct ev_loop *Worker::get_loop() const {
return loop_; return loop_;
} }
@ -285,11 +259,6 @@ bool Worker::get_graceful_shutdown() const { return graceful_shutdown_; }
MemchunkPool *Worker::get_mcpool() { return &mcpool_; } MemchunkPool *Worker::get_mcpool() { return &mcpool_; }
DownstreamGroup *Worker::get_dgrp(size_t group) {
assert(group < dgrps_.size());
return &dgrps_[group];
}
MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() { MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() {
return session_cache_memcached_dispatcher_.get(); return session_cache_memcached_dispatcher_.get();
} }
@ -319,4 +288,93 @@ ConnectBlocker *Worker::get_connect_blocker() const {
return connect_blocker_.get(); return connect_blocker_.get();
} }
namespace {
size_t match_downstream_addr_group_host(
const Router &router, const StringRef &host, const StringRef &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (path.empty() || path[0] != '/') {
auto group = router.match(host, StringRef::from_lit("/"));
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
return catch_all;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Perform mapping selection, using host=" << host
<< ", path=" << path;
}
auto group = router.match(host, path);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << host << path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
group = router.match(StringRef::from_lit(""), path);
if (group != -1) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "Found pattern with query " << path
<< ", matched pattern=" << groups[group].pattern;
}
return group;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "None match. Use catch-all pattern";
}
return catch_all;
}
} // namespace
size_t match_downstream_addr_group(
const Router &router, const StringRef &hostport, const StringRef &raw_path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all) {
if (std::find(std::begin(hostport), std::end(hostport), '/') !=
std::end(hostport)) {
// We use '/' specially, and if '/' is included in host, it breaks
// our code. Select catch-all case.
return catch_all;
}
auto fragment = std::find(std::begin(raw_path), std::end(raw_path), '#');
auto query = std::find(std::begin(raw_path), fragment, '?');
auto path = StringRef{std::begin(raw_path), query};
if (hostport.empty()) {
return match_downstream_addr_group_host(router, hostport, path, groups,
catch_all);
}
std::string host;
if (hostport[0] == '[') {
// assume this is IPv6 numeric address
auto p = std::find(std::begin(hostport), std::end(hostport), ']');
if (p == std::end(hostport)) {
return catch_all;
}
if (p + 1 < std::end(hostport) && *(p + 1) != ':') {
return catch_all;
}
host.assign(std::begin(hostport), p + 1);
} else {
auto p = std::find(std::begin(hostport), std::end(hostport), ':');
if (p == std::begin(hostport)) {
return catch_all;
}
host.assign(std::begin(hostport), p);
}
util::inp_strlower(host);
return match_downstream_addr_group_host(router, StringRef{host}, path, groups,
catch_all);
}
} // namespace shrpx } // namespace shrpx

View File

@ -67,20 +67,41 @@ namespace ssl {
class CertLookupTree; class CertLookupTree;
} // namespace ssl } // namespace ssl
struct DownstreamGroup { struct DownstreamAddr {
DownstreamGroup() : next_http2session(0), next(0) {} Address addr;
// backend address. If |host_unix| is true, this is UNIX domain
// socket path.
ImmutableString host;
ImmutableString hostport;
// backend port. 0 if |host_unix| is true.
uint16_t port;
// true if |host| contains UNIX domain socket path.
bool host_unix;
std::vector<std::unique_ptr<Http2Session>> http2sessions; std::unique_ptr<ConnectBlocker> connect_blocker;
// Next index in http2sessions. // Client side TLS session cache
size_t next_http2session; TLSSessionCache tls_session_cache;
// Next downstream address index corresponding to };
// Config::downstream_addr_groups[].
struct DownstreamAddrGroup {
ImmutableString pattern;
std::vector<DownstreamAddr> addrs;
// Application protocol used in this group
shrpx_proto proto;
// List of Http2Session which is not fully utilized (i.e., the
// server advertized maximum concurrency is not reached). We will
// coalesce as much stream as possible in one Http2Session to fully
// utilize TCP connection.
//
// TODO Verify that this approach performs better in performance
// wise.
DList<Http2Session> http2_freelist;
DownstreamConnectionPool dconn_pool;
// Next downstream address index in addrs.
size_t next; size_t next;
}; };
struct WorkerStat { struct WorkerStat {
WorkerStat(size_t num_groups) : num_connections(0) {}
size_t num_connections; size_t num_connections;
}; };
@ -121,8 +142,6 @@ public:
void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys); void set_ticket_keys(std::shared_ptr<TicketKeys> ticket_keys);
WorkerStat *get_worker_stat(); WorkerStat *get_worker_stat();
DownstreamConnectionPool *get_dconn_pool();
Http2Session *next_http2_session(size_t group);
struct ev_loop *get_loop() const; struct ev_loop *get_loop() const;
SSL_CTX *get_sv_ssl_ctx() const; SSL_CTX *get_sv_ssl_ctx() const;
SSL_CTX *get_cl_ssl_ctx() const; SSL_CTX *get_cl_ssl_ctx() const;
@ -133,8 +152,6 @@ public:
MemchunkPool *get_mcpool(); MemchunkPool *get_mcpool();
void schedule_clear_mcpool(); void schedule_clear_mcpool();
DownstreamGroup *get_dgrp(size_t group);
MemcachedDispatcher *get_session_cache_memcached_dispatcher(); MemcachedDispatcher *get_session_cache_memcached_dispatcher();
std::mt19937 &get_randgen(); std::mt19937 &get_randgen();
@ -159,9 +176,7 @@ private:
ev_async w_; ev_async w_;
ev_timer mcpool_clear_timer_; ev_timer mcpool_clear_timer_;
MemchunkPool mcpool_; MemchunkPool mcpool_;
DownstreamConnectionPool dconn_pool_;
WorkerStat worker_stat_; WorkerStat worker_stat_;
std::vector<DownstreamGroup> dgrps_;
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_; std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
#ifdef HAVE_MRUBY #ifdef HAVE_MRUBY
@ -184,6 +199,16 @@ private:
bool graceful_shutdown_; bool graceful_shutdown_;
}; };
// Selects group based on request's |hostport| and |path|. |hostport|
// is the value taken from :authority or host header field, and may
// contain port. The |path| may contain query part. We require the
// catch-all pattern in place, so this function always selects one
// group. The catch-all group index is given in |catch_all|. All
// patterns are given in |groups|.
size_t match_downstream_addr_group(
const Router &router, const StringRef &hostport, const StringRef &path,
const std::vector<DownstreamAddrGroup> &groups, size_t catch_all);
} // namespace shrpx } // namespace shrpx
#endif // SHRPX_WORKER_H #endif // SHRPX_WORKER_H

179
src/shrpx_worker_test.cc Normal file
View File

@ -0,0 +1,179 @@
/*
* 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_worker_test.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif // HAVE_UNISTD_H
#include <cstdlib>
#include <CUnit/CUnit.h>
#include "shrpx_worker.h"
#include "shrpx_connect_blocker.h"
namespace shrpx {
void test_shrpx_worker_match_downstream_addr_group(void) {
auto groups = std::vector<DownstreamAddrGroup>();
for (auto &s : {"nghttp2.org/", "nghttp2.org/alpha/bravo/",
"nghttp2.org/alpha/charlie", "nghttp2.org/delta%3A",
"www.nghttp2.org/", "[::1]/", "nghttp2.org/alpha/bravo/delta",
// Check that match is done in the single node
"example.com/alpha/bravo", "192.168.0.1/alpha/"}) {
groups.push_back(DownstreamAddrGroup{ImmutableString(s)});
}
Router router;
for (size_t i = 0; i < groups.size(); ++i) {
auto &g = groups[i];
router.add_route(StringRef{g.pattern}, i);
}
CU_ASSERT(0 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/"), groups, 255));
// port is removed
CU_ASSERT(0 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org:8080"),
StringRef::from_lit("/"), groups, 255));
// host is case-insensitive
CU_ASSERT(4 == match_downstream_addr_group(
router, StringRef::from_lit("WWW.nghttp2.org"),
StringRef::from_lit("/alpha"), groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/bravo/"), groups, 255));
// /alpha/bravo also matches /alpha/bravo/
CU_ASSERT(1 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/bravo"), groups, 255));
// path part is case-sensitive
CU_ASSERT(0 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/Alpha/bravo"), groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/bravo/charlie"), groups, 255));
CU_ASSERT(2 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/charlie"), groups, 255));
// pattern which does not end with '/' must match its entirely. So
// this matches to group 0, not group 2.
CU_ASSERT(0 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/charlie/"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("example.org"),
StringRef::from_lit("/"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(router, StringRef::from_lit(""),
StringRef::from_lit("/"), groups,
255));
CU_ASSERT(255 == match_downstream_addr_group(router, StringRef::from_lit(""),
StringRef::from_lit("alpha"),
groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, StringRef::from_lit("foo/bar"),
StringRef::from_lit("/"), groups, 255));
// If path is StringRef::from_lit("*", only match with host + "/").
CU_ASSERT(0 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("*"), groups, 255));
CU_ASSERT(5 ==
match_downstream_addr_group(router, StringRef::from_lit("[::1]"),
StringRef::from_lit("/"), groups, 255));
CU_ASSERT(5 == match_downstream_addr_group(
router, StringRef::from_lit("[::1]:8080"),
StringRef::from_lit("/"), groups, 255));
CU_ASSERT(255 ==
match_downstream_addr_group(router, StringRef::from_lit("[::1"),
StringRef::from_lit("/"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("[::1]8000"),
StringRef::from_lit("/"), groups, 255));
// Check the case where adding route extends tree
CU_ASSERT(6 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/bravo/delta"), groups, 255));
CU_ASSERT(1 == match_downstream_addr_group(
router, StringRef::from_lit("nghttp2.org"),
StringRef::from_lit("/alpha/bravo/delta/"), groups, 255));
// Check the case where query is done in a single node
CU_ASSERT(7 == match_downstream_addr_group(
router, StringRef::from_lit("example.com"),
StringRef::from_lit("/alpha/bravo"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("example.com"),
StringRef::from_lit("/alpha/bravo/"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("example.com"),
StringRef::from_lit("/alpha"), groups, 255));
// Check the case where quey is done in a single node
CU_ASSERT(8 == match_downstream_addr_group(
router, StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("/alpha"), groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(
router, StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("/alpha/"), groups, 255));
CU_ASSERT(8 == match_downstream_addr_group(
router, StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("/alpha/bravo"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("/alph"), groups, 255));
CU_ASSERT(255 == match_downstream_addr_group(
router, StringRef::from_lit("192.168.0.1"),
StringRef::from_lit("/"), groups, 255));
router.dump();
}
} // namespace shrpx

38
src/shrpx_worker_test.h Normal file
View File

@ -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_WORKER_TEST_H
#define SHRPX_WORKER_TEST_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H
namespace shrpx {
void test_shrpx_worker_match_downstream_addr_group(void);
} // namespace shrpx
#endif // SHRPX_WORKER_TEST_H

View File

@ -99,14 +99,14 @@ template <typename T, typename F> bool test_flags(T t, F flags) {
// T *dlnext, which point to previous element and next element in the // T *dlnext, which point to previous element and next element in the
// list respectively. // list respectively.
template <typename T> struct DList { template <typename T> struct DList {
DList() : head(nullptr), tail(nullptr) {} DList() : head(nullptr), tail(nullptr), n(0) {}
DList(const DList &) = delete; DList(const DList &) = delete;
DList &operator=(const DList &) = delete; DList &operator=(const DList &) = delete;
DList(DList &&other) : head(other.head), tail(other.tail) { DList(DList &&other) : head(other.head), tail(other.tail), n(other.n) {
other.head = other.tail = nullptr; other.head = other.tail = nullptr;
other.n = 0;
} }
DList &operator=(DList &&other) { DList &operator=(DList &&other) {
@ -115,11 +115,16 @@ template <typename T> struct DList {
} }
head = other.head; head = other.head;
tail = other.tail; tail = other.tail;
n = other.n;
other.head = other.tail = nullptr; other.head = other.tail = nullptr;
other.n = 0;
return *this; return *this;
} }
void append(T *t) { void append(T *t) {
++n;
if (tail) { if (tail) {
tail->dlnext = t; tail->dlnext = t;
t->dlprev = tail; t->dlprev = tail;
@ -130,6 +135,7 @@ template <typename T> struct DList {
} }
void remove(T *t) { void remove(T *t) {
--n;
auto p = t->dlprev; auto p = t->dlprev;
auto n = t->dlnext; auto n = t->dlnext;
if (p) { if (p) {
@ -149,7 +155,10 @@ template <typename T> struct DList {
bool empty() const { return head == nullptr; } bool empty() const { return head == nullptr; }
size_t size() const { return n; }
T *head, *tail; T *head, *tail;
size_t n;
}; };
template <typename T> void dlist_delete_all(DList<T> &dl) { template <typename T> void dlist_delete_all(DList<T> &dl) {
@ -391,7 +400,7 @@ public:
explicit StringRef(const std::string &s) : base(s.c_str()), len(s.size()) {} explicit StringRef(const std::string &s) : base(s.c_str()), len(s.size()) {}
explicit StringRef(const ImmutableString &s) explicit StringRef(const ImmutableString &s)
: base(s.c_str()), len(s.size()) {} : base(s.c_str()), len(s.size()) {}
StringRef(const char *s) : base(s), len(strlen(s)) {} explicit StringRef(const char *s) : base(s), len(strlen(s)) {}
template <typename CharT> template <typename CharT>
constexpr StringRef(const CharT *s, size_t n) constexpr StringRef(const CharT *s, size_t n)
: base(reinterpret_cast<const char *>(s)), len(n) {} : base(reinterpret_cast<const char *>(s)), len(n) {}

View File

@ -857,6 +857,27 @@ std::vector<unsigned char> get_default_alpn() {
return res; return res;
} }
std::vector<StringRef> split_str(const StringRef &s, char delim) {
size_t len = 1;
auto last = std::end(s);
for (auto first = std::begin(s), d = first;
(d = std::find(first, last, delim)) != last; ++len, first = d + 1)
;
auto list = std::vector<StringRef>(len);
len = 0;
for (auto first = std::begin(s);; ++len) {
auto stop = std::find(first, last, delim);
list[len] = StringRef{first, stop};
if (stop == last) {
break;
}
first = stop + 1;
}
return list;
}
std::vector<Range<const char *>> split_config_str_list(const char *s, std::vector<Range<const char *>> split_config_str_list(const char *s,
char delim) { char delim) {
size_t len = 1; size_t len = 1;

View File

@ -225,6 +225,11 @@ bool istarts_with_l(const std::string &a, const CharT(&b)[N]) {
return istarts_with(std::begin(a), std::end(a), b, b + N - 1); return istarts_with(std::begin(a), std::end(a), b, b + N - 1);
} }
template <typename CharT, size_t N>
bool istarts_with_l(const StringRef &a, const CharT(&b)[N]) {
return istarts_with(std::begin(a), std::end(a), b, b + N - 1);
}
template <typename InputIterator1, typename InputIterator2> template <typename InputIterator1, typename InputIterator2>
bool ends_with(InputIterator1 first1, InputIterator1 last1, bool ends_with(InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2) { InputIterator2 first2, InputIterator2 last2) {
@ -543,6 +548,11 @@ std::vector<std::string> parse_config_str_list(const char *s, char delim = ',');
std::vector<Range<const char *>> split_config_str_list(const char *s, std::vector<Range<const char *>> split_config_str_list(const char *s,
char delim); char delim);
// Parses delimited strings in |s| and returns Substrings in |s|
// delimited by |delim|. The any white spaces around substring are
// treated as a part of substring.
std::vector<StringRef> split_str(const StringRef &s, char delim);
// Returns given time |tp| in Common Log format (e.g., // Returns given time |tp| in Common Log format (e.g.,
// 03/Jul/2014:00:19:38 +0900) // 03/Jul/2014:00:19:38 +0900)
// Expected type of |tp| is std::chrono::timepoint // Expected type of |tp| is std::chrono::timepoint

View File

@ -456,14 +456,19 @@ void test_util_parse_config_str_list(void) {
} }
void test_util_make_http_hostport(void) { void test_util_make_http_hostport(void) {
CU_ASSERT("localhost" == util::make_http_hostport("localhost", 80)); CU_ASSERT("localhost" ==
CU_ASSERT("[::1]" == util::make_http_hostport("::1", 443)); util::make_http_hostport(StringRef::from_lit("localhost"), 80));
CU_ASSERT("localhost:3000" == util::make_http_hostport("localhost", 3000)); CU_ASSERT("[::1]" ==
util::make_http_hostport(StringRef::from_lit("::1"), 443));
CU_ASSERT("localhost:3000" ==
util::make_http_hostport(StringRef::from_lit("localhost"), 3000));
} }
void test_util_make_hostport(void) { void test_util_make_hostport(void) {
CU_ASSERT("localhost:80" == util::make_hostport("localhost", 80)); CU_ASSERT("localhost:80" ==
CU_ASSERT("[::1]:443" == util::make_hostport("::1", 443)); util::make_hostport(StringRef::from_lit("localhost"), 80));
CU_ASSERT("[::1]:443" ==
util::make_hostport(StringRef::from_lit("::1"), 443));
} }
} // namespace shrpx } // namespace shrpx