diff --git a/README.rst b/README.rst index c1fa5884..4e48058d 100644 --- a/README.rst +++ b/README.rst @@ -4,10 +4,10 @@ nghttp2 - HTTP/2 C Library This is an implementation of the Hypertext Transfer Protocol version 2 in C. -The framing layer of HTTP/2 is implemented as a reusable C -library. On top of that, we have implemented an HTTP/2 client, server -and proxy. We have also developed load test and benchmarking tools for -HTTP/2 and SPDY. +The framing layer of HTTP/2 is implemented as a reusable C library. +On top of that, we have implemented an HTTP/2 client, server and +proxy. We have also developed load test and benchmarking tools for +HTTP/2. An HPACK encoder and decoder are available as a public API. @@ -34,8 +34,8 @@ implementation. * https://nghttp2.org/ (TLS + ALPN/NPN) - This endpoint supports ``h2``, ``h2-16``, ``h2-14``, ``spdy/3.1`` - and ``http/1.1`` via ALPN/NPN and requires TLSv1.2 for HTTP/2 + This endpoint supports ``h2``, ``h2-16``, ``h2-14``, and + ``http/1.1`` via ALPN/NPN and requires TLSv1.2 for HTTP/2 connection. * http://nghttp2.org/ (HTTP Upgrade and HTTP/2 Direct) @@ -76,14 +76,6 @@ ALPN support requires OpenSSL >= 1.0.2 (released 22 January 2015). LibreSSL >= 2.2.0 can be used instead of OpenSSL, but OpenSSL has more features than LibreSSL at the time of this writing. -To enable the SPDY protocol in the application program ``nghttpx`` and -``h2load``, the following package is required: - -* spdylay >= 1.3.2 - -We no longer recommend to build nghttp2 with SPDY protocol support -enabled. SPDY support will be removed soon. - To enable ``-a`` option (getting linked assets from the downloaded resource) in ``nghttp``, the following package is required: @@ -130,13 +122,9 @@ and above, run the following to install the required packages: sudo apt-get install g++ make binutils autoconf automake autotools-dev libtool pkg-config \ zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev \ - libc-ares-dev libjemalloc-dev libsystemd-dev libspdylay-dev \ + libc-ares-dev libjemalloc-dev libsystemd-dev \ cython python3-dev python-setuptools -Since Ubuntu 15.10, spdylay has been available as a package named -`libspdylay-dev`. For the earlier Ubuntu release, you need to build -it yourself: http://tatsuhiro-t.github.io/spdylay/ - To enable mruby support for nghttpx, `mruby `_ is required. We need to build mruby with C++ ABI explicitly turned on, and probably need other @@ -332,7 +320,6 @@ its testing framework. We depend on the following libraries: * golang.org/x/net/http2 * golang.org/x/net/websocket * https://github.com/tatsuhiro-t/go-nghttp2 -* https://github.com/tatsuhiro-t/spdy To download the above packages, after settings ``GOPATH``, run the following command under ``integration-tests`` directory: @@ -350,11 +337,6 @@ To run the tests, run the following command under Inside the tests, we use port 3009 to run the test subject server. -.. note:: - - github.com/tatsuhiro-t/spdy is a copy used to be available at - golang.org/x/net/spdy, but it is now gone. - Migration from v0.7.15 or earlier --------------------------------- @@ -755,7 +737,7 @@ information. Here is sample output from ``nghttpd``: nghttpx - proxy +++++++++++++++ -``nghttpx`` is a multi-threaded reverse proxy for HTTP/2, SPDY and +``nghttpx`` is a multi-threaded reverse proxy for HTTP/2, and HTTP/1.1, and powers http://nghttp2.org and supports HTTP/2 server push. @@ -770,31 +752,30 @@ to know how to migrate from earlier releases. ``nghttpx`` implements `important performance-oriented features `_ in TLS, such as session IDs, session tickets (with automatic key rotation), OCSP -stapling, dynamic record sizing, ALPN/NPN, forward secrecy and SPDY & -HTTP/2. ``nghttpx`` also offers the functionality to share session -cache and ticket keys among multiple ``nghttpx`` instances via -memcached. +stapling, dynamic record sizing, ALPN/NPN, forward secrecy and HTTP/2. +``nghttpx`` also offers the functionality to share session cache and +ticket keys among multiple ``nghttpx`` instances via memcached. ``nghttpx`` has 2 operation modes: -================== ====================== ================ ============= -Mode option Frontend Backend Note -================== ====================== ================ ============= -default mode HTTP/2, SPDY, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy -``--http2-proxy`` HTTP/2, SPDY, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy -================== ====================== ================ ============= +================== ================ ================ ============= +Mode option Frontend Backend Note +================== ================ ================ ============= +default mode HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Reverse proxy +``--http2-proxy`` HTTP/2, HTTP/1.1 HTTP/1.1, HTTP/2 Forward proxy +================== ================ ================ ============= The interesting mode at the moment is the default mode. It works like -a reverse proxy and listens for HTTP/2, SPDY and HTTP/1.1 and can be +a reverse proxy and listens for HTTP/2, and HTTP/1.1 and can be deployed as a SSL/TLS terminator for existing web server. In all modes, the frontend connections are encrypted by SSL/TLS by default. To disable encryption, use the ``no-tls`` keyword in -``--frontend`` option. If encryption is disabled, SPDY is disabled in -the frontend and incoming HTTP/1.1 connections can be upgraded to -HTTP/2 through HTTP Upgrade. On the other hard, backend connections -are not encrypted by default. To encrypt backend connections, use -``tls`` keyword in ``--backend`` option. +``--frontend`` option. If encryption is disabled, incoming HTTP/1.1 +connections can be upgraded to HTTP/2 through HTTP Upgrade. On the +other hard, backend connections are not encrypted by default. To +encrypt backend connections, use ``tls`` keyword in ``--backend`` +option. ``nghttpx`` supports a configuration file. See the ``--conf`` option and sample configuration file ``nghttpx.conf.sample``. @@ -804,16 +785,16 @@ server: .. code-block:: text - Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server - [reverse proxy] + Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1, HTTP/2) --> Web Server + [reverse proxy] With the ``--http2-proxy`` option, it works as forward proxy, and it -is so called secure HTTP/2 proxy (aka SPDY proxy): +is so called secure HTTP/2 proxy: .. code-block:: text - Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy - [secure proxy] (e.g., Squid, ATS) + Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy + [secure proxy] (e.g., Squid, ATS) The ``Client`` in the above example needs to be configured to use ``nghttpx`` as secure proxy. @@ -845,7 +826,7 @@ proxy through an HTTP proxy: .. code-block:: text - Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/2) -- + Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) -- --===================---> HTTP/2 Proxy (HTTP proxy tunnel) (e.g., nghttpx -s) @@ -853,9 +834,8 @@ proxy through an HTTP proxy: Benchmarking tool ----------------- -The ``h2load`` program is a benchmarking tool for HTTP/2 and SPDY. -The SPDY support is enabled if the program was built with the spdylay -library. The UI of ``h2load`` is heavily inspired by ``weighttp`` +The ``h2load`` program is a benchmarking tool for HTTP/2. The UI of +``h2load`` is heavily inspired by ``weighttp`` (https://github.com/lighttpd/weighttp). The typical usage is as follows: diff --git a/configure.ac b/configure.ac index 84970d26..c34a8cf7 100644 --- a/configure.ac +++ b/configure.ac @@ -117,11 +117,6 @@ AC_ARG_WITH([jemalloc], [Use jemalloc [default=check]])], [request_jemalloc=$withval], [request_jemalloc=check]) -AC_ARG_WITH([spdylay], - [AS_HELP_STRING([--with-spdylay], - [(Deprecated) Use spdylay [default=no]])], - [request_spdylay=$withval], [request_spdylay=no]) - AC_ARG_WITH([systemd], [AS_HELP_STRING([--with-systemd], [Enable systemd support in nghttpx [default=check]])], @@ -458,26 +453,6 @@ if test "x${request_jemalloc}" = "xyes" && AC_MSG_ERROR([jemalloc was requested (--with-jemalloc) but not found]) fi -# spdylay (for src/nghttpx and src/h2load) -have_spdylay=no -if test "x${request_spdylay}" != "xno"; then - PKG_CHECK_MODULES([LIBSPDYLAY], [libspdylay >= 1.3.2], - [have_spdylay=yes], [have_spdylay=no]) - if test "x${have_spdylay}" = "xyes"; then - AC_DEFINE([HAVE_SPDYLAY], [1], [Define to 1 if you have `spdylay` library.]) - else - AC_MSG_NOTICE($LIBSPDYLAY_PKG_ERRORS) - AC_MSG_NOTICE([The SPDY support in nghttpx and h2load will be disabled.]) - fi -fi - -if test "x${request_spdylay}" = "xyes" && - test "x${have_spdylay}" != "xyes"; then - AC_MSG_ERROR([spdylay was requested (--with-spdylay) but not found]) -fi - -AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ]) - # Check Boost Asio library have_asio_lib=no @@ -928,7 +903,6 @@ AC_MSG_NOTICE([summary of build options: Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') Libc-ares ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}') Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}') - Spdylay: ${have_spdylay} (CFLAGS='${LIBSPDYLAY_CFLAGS}' LIBS='${LIBSPDYLAY_LIBS}') Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}') Jemalloc: ${have_jemalloc} (LIBS='${JEMALLOC_LIBS}') Zlib: ${have_zlib} (CFLAGS='${ZLIB_CFLAGS}' LIBS='${ZLIB_LIBS}') @@ -950,7 +924,3 @@ AC_MSG_NOTICE([summary of build options: Python bindings:${enable_python_bindings} Threading: ${enable_threads} ]) - -if test "x${have_spdylay}" == "xyes"; then - AC_MSG_WARN([spdylay support was deprecated, and will be removed in v1.29.0.]) -fi diff --git a/doc/h2load.h2r b/doc/h2load.h2r index f06f7e01..238a5497 100644 --- a/doc/h2load.h2r +++ b/doc/h2load.h2r @@ -41,8 +41,7 @@ traffic used for header fields after decompression. The ``space savings`` is calculated by (1 - ``headers`` / ``decompressed(headers)``) * 100. For HTTP/1.1, this is usually 0.00%, since it does not have - header compression. For HTTP/2 and SPDY, it shows some insightful - numbers. + header compression. For HTTP/2, it shows some insightful numbers. data The number of response body bytes received from the server. @@ -110,7 +109,7 @@ h2load sets large flow control window by default, and effectively disables flow control to avoid under utilization of server performance. To set smaller flow control window, use :option:`-w` and :option:`-W` options. For example, use ``-w16 -W16`` to set default -window size described in HTTP/2 and SPDY protocol specification. +window size described in HTTP/2 protocol specification. SEE ALSO -------- diff --git a/doc/sources/h2load-howto.rst b/doc/sources/h2load-howto.rst index 9ab34112..133f9002 100644 --- a/doc/sources/h2load-howto.rst +++ b/doc/sources/h2load-howto.rst @@ -3,10 +3,8 @@ h2load - HTTP/2 benchmarking tool - HOW-TO ========================================== -:doc:`h2load.1` is benchmarking tool for HTTP/2 and HTTP/1.1. If -built with spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it -also supports SPDY protocol. It supports SSL/TLS and clear text for -all supported protocols. +:doc:`h2load.1` is benchmarking tool for HTTP/2 and HTTP/1.1. It +supports SSL/TLS and clear text for all supported protocols. Compiling from source --------------------- @@ -86,20 +84,18 @@ seconds warming up period: Flow Control ------------ -HTTP/2 and SPDY/3 or later employ flow control and it may affect -benchmarking results. By default, h2load uses large enough flow -control window, which effectively disables flow control. To adjust -receiver flow control window size, there are following options: +HTTP/2 has flow control and it may affect benchmarking results. By +default, h2load uses large enough flow control window, which +effectively disables flow control. To adjust receiver flow control +window size, there are following options: :option:`-w` Sets the stream level initial window size to - (2**)-1. For SPDY, 2** is used instead. + (2**)-1. :option:`-W` Sets the connection level initial window size to - (2**)-1. For SPDY, if is strictly less - than 16, this option is ignored. Otherwise - 2** is used for SPDY. + (2**)-1. Multi-Threading --------------- diff --git a/doc/sources/nghttpx-howto.rst b/doc/sources/nghttpx-howto.rst index d78ad9ab..6acb369c 100644 --- a/doc/sources/nghttpx-howto.rst +++ b/doc/sources/nghttpx-howto.rst @@ -4,10 +4,10 @@ nghttpx - HTTP/2 proxy - HOW-TO =============================== :doc:`nghttpx.1` is a proxy translating protocols between HTTP/2 and -other protocols (e.g., HTTP/1, SPDY). It operates in several modes -and each mode may require additional programs to work with. This -article describes each operation mode and explains the intended -use-cases. It also covers some useful options later. +other protocols (e.g., HTTP/1). It operates in several modes and each +mode may require additional programs to work with. This article +describes each operation mode and explains the intended use-cases. It +also covers some useful options later. Default mode ------------ @@ -15,9 +15,7 @@ Default mode If nghttpx is invoked without :option:`--http2-proxy`, it operates in default mode. In this mode, it works as reverse proxy (gateway) for both HTTP/2 and HTTP/1 clients to backend servers. This is also known -as "HTTP/2 router". If nghttpx is linked with spdylay library and -frontend connection is SSL/TLS, the frontend also supports SPDY -protocol. +as "HTTP/2 router". By default, frontend connection is encrypted using SSL/TLS. So server's private key and certificate must be supplied to the command @@ -25,11 +23,10 @@ line (or through configuration file). In this case, the frontend protocol selection will be done via ALPN or NPN. To turn off encryption on frontend connection, use ``no-tls`` keyword -in :option:`--frontend` option. In this case, SPDY protocol is not -available even if spdylay library is linked to nghttpx. HTTP/2 and -HTTP/1 are available on the frontend, and an HTTP/1 connection can be -upgraded to HTTP/2 using HTTP Upgrade. Starting HTTP/2 connection by -sending HTTP/2 connection preface is also supported. +in :option:`--frontend` option. HTTP/2 and HTTP/1 are available on +the frontend, and an HTTP/1 connection can be upgraded to HTTP/2 using +HTTP Upgrade. Starting HTTP/2 connection by sending HTTP/2 connection +preface is also supported. nghttpx can listen on multiple frontend addresses. This is achieved by using multiple :option:`--frontend` options. For each frontend @@ -71,9 +68,8 @@ mode`_. The difference is that this mode acts like a forward proxy and assumes the backend is an HTTP proxy server (e.g., Squid, Apache Traffic Server). HTTP/1 requests must include an absolute URI in request line. -By default, the frontend connection is encrypted. So this mode is also -called secure proxy. If nghttpx is linked with spdylay, it supports -SPDY protocols and it works as so called SPDY proxy. +By default, the frontend connection is encrypted. So this mode is +also called secure proxy. To turn off encryption on the frontend connection, use ``no-tls`` keyword in :option:`--frontend` option. @@ -102,8 +98,8 @@ like this: At the time of this writing, Firefox 41 and Chromium v46 can use nghttpx as HTTP/2 proxy. -To make Firefox or Chromium use nghttpx as HTTP/2 or SPDY proxy, user -has to create proxy.pac script file like this: +To make Firefox or Chromium use nghttpx as HTTP/2 proxy, user has to +create proxy.pac script file like this: .. code-block:: javascript diff --git a/integration-tests/Makefile.am b/integration-tests/Makefile.am index 53931da8..1c5fe9d6 100644 --- a/integration-tests/Makefile.am +++ b/integration-tests/Makefile.am @@ -24,7 +24,6 @@ GO_FILES = \ nghttpx_http1_test.go \ nghttpx_http2_test.go \ - nghttpx_spdy_test.go \ server_tester.go EXTRA_DIST = \ @@ -43,7 +42,6 @@ EXTRA_DIST = \ itprep: go get -d -v golang.org/x/net/http2 go get -d -v github.com/tatsuhiro-t/go-nghttp2 - go get -d -v github.com/tatsuhiro-t/spdy go get -d -v golang.org/x/net/websocket it: diff --git a/integration-tests/nghttpx_spdy_test.go b/integration-tests/nghttpx_spdy_test.go deleted file mode 100644 index dfe35e0a..00000000 --- a/integration-tests/nghttpx_spdy_test.go +++ /dev/null @@ -1,664 +0,0 @@ -package nghttp2 - -import ( - "encoding/json" - "github.com/tatsuhiro-t/spdy" - "golang.org/x/net/http2/hpack" - "net/http" - "testing" -) - -// TestS3H1PlainGET tests whether simple SPDY GET request works. -func TestS3H1PlainGET(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1PlainGET", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - want := 200 - if got := res.status; got != want { - t.Errorf("status = %v; want %v", got, want) - } -} - -// TestS3H1BadRequestCL tests that server rejects request whose -// content-length header field value does not match its request body -// size. -func TestS3H1BadRequestCL(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler) - defer st.Close() - - // we set content-length: 1024, but the actual request body is - // 3 bytes. - res, err := st.spdy(requestParam{ - name: "TestS3H1BadRequestCL", - method: "POST", - header: []hpack.HeaderField{ - pair("content-length", "1024"), - }, - body: []byte("foo"), - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - want := spdy.ProtocolError - if got := res.spdyRstErrCode; got != want { - t.Errorf("res.spdyRstErrCode = %v; want %v", got, want) - } -} - -// TestS3H1MultipleRequestCL tests that server rejects request with -// multiple Content-Length request header fields. -func TestS3H1MultipleRequestCL(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward bad request") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1MultipleRequestCL", - header: []hpack.HeaderField{ - pair("content-length", "1"), - pair("content-length", "1"), - }, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - want := 400 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) - } -} - -// TestS3H1InvalidRequestCL tests that server rejects request with -// Content-Length which cannot be parsed as a number. -func TestS3H1InvalidRequestCL(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward bad request") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1InvalidRequestCL", - header: []hpack.HeaderField{ - pair("content-length", ""), - }, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - want := 400 - if got := res.status; got != want { - t.Errorf("status: %v; want %v", got, want) - } -} - -// TestS3H1GenerateVia tests that server generates Via header field to and -// from backend server. -func TestS3H1GenerateVia(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1GenerateVia", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } -} - -// TestS3H1AppendVia tests that server adds value to existing Via -// header field to and from backend server. -func TestS3H1AppendVia(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } - w.Header().Add("Via", "bar") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1AppendVia", - header: []hpack.HeaderField{ - pair("via", "foo"), - }, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } -} - -// TestS3H1NoVia tests that server does not add value to existing Via -// header field to and from backend server. -func TestS3H1NoVia(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--no-via"}, t, func(w http.ResponseWriter, r *http.Request) { - if got, want := r.Header.Get("Via"), "foo"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } - w.Header().Add("Via", "bar") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1NoVia", - header: []hpack.HeaderField{ - pair("via", "foo"), - }, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.header.Get("Via"), "bar"; got != want { - t.Errorf("Via: %v; want %v", got, want) - } -} - -// TestS3H1HeaderFieldBuffer tests that request with header fields -// larger than configured buffer size is rejected. -func TestS3H1HeaderFieldBuffer(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--request-header-field-buffer=10"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatal("execution path should not be here") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1HeaderFieldBuffer", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.spdyRstErrCode, spdy.InternalError; got != want { - t.Errorf("res.spdyRstErrCode: %v; want %v", got, want) - } -} - -// TestS3H1HeaderFields tests that request with header fields more -// than configured number is rejected. -func TestS3H1HeaderFields(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--max-request-header-fields=1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatal("execution path should not be here") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1HeaderFields", - // we have at least 5 pseudo-header fields sent, and - // that ensures that buffer limit exceeds. - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.spdyRstErrCode, spdy.InternalError; got != want { - t.Errorf("res.spdyRstErrCode: %v; want %v", got, want) - } -} - -// TestS3H1InvalidMethod tests that server rejects invalid method with -// 501. -func TestS3H1InvalidMethod(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1InvalidMethod", - method: "get", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 501; got != want { - t.Errorf("status: %v; want %v", got, want) - } -} - -// TestS3H1BadHost tests that server rejects request including bad -// character in :host header field. -func TestS3H1BadHost(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1BadHost", - authority: `foo\bar`, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 400; got != want { - t.Errorf("status: %v; want %v", got, want) - } -} - -// TestS3H1BadScheme tests that server rejects request including bad -// character in :scheme header field. -func TestS3H1BadScheme(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Errorf("server should not forward this request") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1BadScheme", - scheme: `http*`, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 400; got != want { - t.Errorf("status: %v; want %v", got, want) - } -} - -// TestS3H1ReqPhaseSetHeader tests mruby request phase hook -// modifies request header fields. -func TestS3H1ReqPhaseSetHeader(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-set-header.rb"}, t, func(w http.ResponseWriter, r *http.Request) { - if got, want := r.Header.Get("User-Agent"), "mruby"; got != want { - t.Errorf("User-Agent = %v; want %v", got, want) - } - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1ReqPhaseSetHeader", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 200; got != want { - t.Errorf("status = %v; want %v", got, want) - } -} - -// TestS3H1ReqPhaseReturn tests mruby request phase hook returns -// custom response. -func TestS3H1ReqPhaseReturn(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1ReqPhaseReturn", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 404; got != want { - t.Errorf("status = %v; want %v", got, want) - } - - hdtests := []struct { - k, v string - }{ - {"content-length", "20"}, - {"from", "mruby"}, - } - for _, tt := range hdtests { - if got, want := res.header.Get(tt.k), tt.v; got != want { - t.Errorf("%v = %v; want %v", tt.k, got, want) - } - } - - if got, want := string(res.body), "Hello World from req"; got != want { - t.Errorf("body = %v; want %v", got, want) - } -} - -// TestS3H1RespPhaseSetHeader tests mruby response phase hook modifies -// response header fields. -func TestS3H1RespPhaseSetHeader(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-set-header.rb"}, t, noopHandler) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1RespPhaseSetHeader", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 200; got != want { - t.Errorf("status = %v; want %v", got, want) - } - - if got, want := res.header.Get("alpha"), "bravo"; got != want { - t.Errorf("alpha = %v; want %v", got, want) - } -} - -// TestS3H1RespPhaseReturn tests mruby response phase hook returns -// custom response. -func TestS3H1RespPhaseReturn(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H1RespPhaseReturn", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 404; got != want { - t.Errorf("status = %v; want %v", got, want) - } - - hdtests := []struct { - k, v string - }{ - {"content-length", "21"}, - {"from", "mruby"}, - } - for _, tt := range hdtests { - if got, want := res.header.Get(tt.k), tt.v; got != want { - t.Errorf("%v = %v; want %v", tt.k, got, want) - } - } - - if got, want := string(res.body), "Hello World from resp"; got != want { - t.Errorf("body = %v; want %v", got, want) - } -} - -// // TestS3H2ConnectFailure tests that server handles the situation that -// // connection attempt to HTTP/2 backend failed. -// func TestS3H2ConnectFailure(t *testing.T) { -// st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge"}, t, noopHandler) -// defer st.Close() - -// // simulate backend connect attempt failure -// st.ts.Close() - -// res, err := st.spdy(requestParam{ -// name: "TestS3H2ConnectFailure", -// }) -// if err != nil { -// t.Fatalf("Error st.spdy() = %v", err) -// } -// want := 503 -// if got := res.status; got != want { -// t.Errorf("status: %v; want %v", got, want) -// } -// } - -// TestS3H2ReqPhaseReturn tests mruby request phase hook returns -// custom response. -func TestS3H2ReqPhaseReturn(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H2ReqPhaseReturn", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 404; got != want { - t.Errorf("status = %v; want %v", got, want) - } - - hdtests := []struct { - k, v string - }{ - {"content-length", "20"}, - {"from", "mruby"}, - } - for _, tt := range hdtests { - if got, want := res.header.Get(tt.k), tt.v; got != want { - t.Errorf("%v = %v; want %v", tt.k, got, want) - } - } - - if got, want := string(res.body), "Hello World from req"; got != want { - t.Errorf("body = %v; want %v", got, want) - } -} - -// TestS3H2RespPhaseReturn tests mruby response phase hook returns -// custom response. -func TestS3H2RespPhaseReturn(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge", "--mruby-file=" + testDir + "/resp-return.rb"}, t, noopHandler) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3H2RespPhaseReturn", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - - if got, want := res.status, 404; got != want { - t.Errorf("status = %v; want %v", got, want) - } - - hdtests := []struct { - k, v string - }{ - {"content-length", "21"}, - {"from", "mruby"}, - } - for _, tt := range hdtests { - if got, want := res.header.Get(tt.k), tt.v; got != want { - t.Errorf("%v = %v; want %v", tt.k, got, want) - } - } - - if got, want := string(res.body), "Hello World from resp"; got != want { - t.Errorf("body = %v; want %v", got, want) - } -} - -// TestS3APIBackendconfig exercise backendconfig API endpoint routine -// for successful case. -func TestS3APIBackendconfig(t *testing.T) { - st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }, 3010) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3APIBackendconfig", - path: "/api/v1beta1/backendconfig", - method: "PUT", - body: []byte(`# comment -backend=127.0.0.1,3011 - -`), - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 200; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } - - var apiResp APIResponse - err = json.Unmarshal(res.body, &apiResp) - if err != nil { - t.Fatalf("Error unmarshaling API response: %v", err) - } - if got, want := apiResp.Status, "Success"; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } - if got, want := apiResp.Code, 200; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } -} - -// TestS3APIBackendconfigQuery exercise backendconfig API endpoint -// routine with query. -func TestS3APIBackendconfigQuery(t *testing.T) { - st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }, 3010) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3APIBackendconfigQuery", - path: "/api/v1beta1/backendconfig?foo=bar", - method: "PUT", - body: []byte(`# comment -backend=127.0.0.1,3011 - -`), - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 200; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } - - var apiResp APIResponse - err = json.Unmarshal(res.body, &apiResp) - if err != nil { - t.Fatalf("Error unmarshaling API response: %v", err) - } - if got, want := apiResp.Status, "Success"; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } - if got, want := apiResp.Code, 200; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } -} - -// TestS3APIBackendconfigBadMethod exercise backendconfig API endpoint -// routine with bad method. -func TestS3APIBackendconfigBadMethod(t *testing.T) { - st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }, 3010) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3APIBackendconfigBadMethod", - path: "/api/v1beta1/backendconfig", - method: "GET", - body: []byte(`# comment -backend=127.0.0.1,3011 - -`), - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 405; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } - - var apiResp APIResponse - err = json.Unmarshal(res.body, &apiResp) - if err != nil { - t.Fatalf("Error unmarshaling API response: %v", err) - } - if got, want := apiResp.Status, "Failure"; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } - if got, want := apiResp.Code, 405; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } -} - -// TestS3APINotFound exercise backendconfig API endpoint routine when -// API endpoint is not found. -func TestS3APINotFound(t *testing.T) { - st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3010;api"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }, 3010) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3APINotFound", - path: "/api/notfound", - method: "GET", - body: []byte(`# comment -backend=127.0.0.1,3011 - -`), - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 404; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } - - var apiResp APIResponse - err = json.Unmarshal(res.body, &apiResp) - if err != nil { - t.Fatalf("Error unmarshaling API response: %v", err) - } - if got, want := apiResp.Status, "Failure"; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } - if got, want := apiResp.Code, 404; got != want { - t.Errorf("apiResp.Status: %v; want %v", got, want) - } -} - -// TestS3Healthmon tests health monitor endpoint. -func TestS3Healthmon(t *testing.T) { - st := newServerTesterTLSConnectPort([]string{"--npn-list=spdy/3.1", "-f127.0.0.1,3011;healthmon"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatalf("request should not be forwarded") - }, 3011) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3Healthmon", - path: "/alpha/bravo", - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 200; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } -} - -// TestS3ResponseBeforeRequestEnd tests the situation where response -// ends before request body finishes. -func TestS3ResponseBeforeRequestEnd(t *testing.T) { - st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--mruby-file=" + testDir + "/req-return.rb"}, t, func(w http.ResponseWriter, r *http.Request) { - t.Fatal("request should not be forwarded") - }) - defer st.Close() - - res, err := st.spdy(requestParam{ - name: "TestS3ResponseBeforeRequestEnd", - noEndStream: true, - }) - if err != nil { - t.Fatalf("Error st.spdy() = %v", err) - } - if got, want := res.status, 404; got != want { - t.Errorf("res.status: %v; want %v", got, want) - } -} diff --git a/src/Makefile.am b/src/Makefile.am index ef38cd32..440f6e76 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,7 +40,6 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/lib \ -I$(top_srcdir)/src/includes \ -I$(top_srcdir)/third-party \ - @LIBSPDYLAY_CFLAGS@ \ @LIBXML2_CFLAGS@ \ @LIBEV_CFLAGS@ \ @OPENSSL_CFLAGS@ \ @@ -52,7 +51,6 @@ AM_CPPFLAGS = \ LDADD = $(top_builddir)/lib/libnghttp2.la \ $(top_builddir)/third-party/libhttp-parser.la \ @JEMALLOC_LIBS@ \ - @LIBSPDYLAY_LIBS@ \ @LIBXML2_LIBS@ \ @LIBEV_LIBS@ \ @OPENSSL_LIBS@ \ @@ -97,10 +95,6 @@ h2load_SOURCES = util.cc util.h \ h2load_http2_session.cc h2load_http2_session.h \ h2load_http1_session.cc h2load_http1_session.h -if HAVE_SPDYLAY -h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h -endif # HAVE_SPDYLAY - NGHTTPX_SRCS = \ util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \ app_helper.cc app_helper.h \ @@ -148,10 +142,6 @@ NGHTTPX_SRCS = \ buffer.h memchunk.h template.h allocator.h \ xsi_strerror.c xsi_strerror.h -if HAVE_SPDYLAY -NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h -endif # HAVE_SPDYLAY - if HAVE_MRUBY NGHTTPX_SRCS += \ shrpx_mruby.cc shrpx_mruby.h \ diff --git a/src/h2load.cc b/src/h2load.cc index 66f54b98..5f7789c9 100644 --- a/src/h2load.cc +++ b/src/h2load.cc @@ -46,19 +46,12 @@ #include #include -#ifdef HAVE_SPDYLAY -#include -#endif // HAVE_SPDYLAY - #include #include "http-parser/http_parser.h" #include "h2load_http1_session.h" #include "h2load_http2_session.h" -#ifdef HAVE_SPDYLAY -#include "h2load_spdy_session.h" -#endif // HAVE_SPDYLAY #include "tls.h" #include "http2.h" #include "util.h" @@ -878,14 +871,6 @@ int Client::connection_made() { } else if (util::streq(NGHTTP2_H1_1, proto)) { session = make_unique(this); } -#ifdef HAVE_SPDYLAY - else { - auto spdy_version = spdylay_npn_get_version(next_proto, next_proto_len); - if (spdy_version) { - session = make_unique(this, spdy_version); - } - } -#endif // HAVE_SPDYLAY // Just assign next_proto to selected_proto anyway to show the // negotiation result. @@ -930,20 +915,6 @@ int Client::connection_made() { session = make_unique(this); selected_proto = NGHTTP2_H1_1.str(); break; -#ifdef HAVE_SPDYLAY - case Config::PROTO_SPDY2: - session = make_unique(this, SPDYLAY_PROTO_SPDY2); - selected_proto = "spdy/2"; - break; - case Config::PROTO_SPDY3: - session = make_unique(this, SPDYLAY_PROTO_SPDY3); - selected_proto = "spdy/3"; - break; - case Config::PROTO_SPDY3_1: - session = make_unique(this, SPDYLAY_PROTO_SPDY3_1); - selected_proto = "spdy/3.1"; - break; -#endif // HAVE_SPDYLAY default: // unreachable assert(0); @@ -1788,17 +1759,13 @@ void print_version(std::ostream &out) { namespace { void print_usage(std::ostream &out) { out << R"(Usage: h2load [OPTIONS]... [URI]... -benchmarking tool for HTTP/2 and SPDY server)" +benchmarking tool for HTTP/2 server)" << std::endl; } } // namespace namespace { -constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14" -#ifdef HAVE_SPDYLAY - ",spdy/3.1,spdy/3,spdy/2" -#endif // HAVE_SPDYLAY - ",http/1.1"; +constexpr char DEFAULT_NPN_LIST[] = "h2,h2-16,h2-14,http/1.1"; } // namespace namespace { @@ -1849,14 +1816,11 @@ Options: Default: 1 -w, --window-bits= Sets the stream level initial window size to (2**)-1. - For SPDY, 2** is used instead. Default: )" << config.window_bits << R"( -W, --connection-window-bits= Sets the connection level initial window size to - (2**)-1. For SPDY, if is strictly less than 16, - this option is ignored. Otherwise 2** is used for - SPDY. + (2**)-1. Default: )" << config.connection_window_bits << R"( -H, --header=
Add/Override a header to the requests. @@ -1867,17 +1831,9 @@ Options: << config.ciphers << R"( -p, --no-tls-proto= Specify ALPN identifier of the protocol to be used when - accessing http URI without SSL/TLS.)"; - -#ifdef HAVE_SPDYLAY - out << R"( - Available protocols: spdy/2, spdy/3, spdy/3.1, )"; -#else // !HAVE_SPDYLAY - out << R"( - Available protocols: )"; -#endif // !HAVE_SPDYLAY - out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( and - )" << NGHTTP2_H1_1 << R"( + accessing http URI without SSL/TLS. + Available protocols: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID + << R"( and )" << NGHTTP2_H1_1 << R"( Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"( -d, --data= @@ -2115,14 +2071,6 @@ int main(int argc, char **argv) { config.no_tls_proto = Config::PROTO_HTTP2; } else if (util::strieq(NGHTTP2_H1_1, proto)) { config.no_tls_proto = Config::PROTO_HTTP1_1; -#ifdef HAVE_SPDYLAY - } else if (util::strieq_l("spdy/2", proto)) { - config.no_tls_proto = Config::PROTO_SPDY2; - } else if (util::strieq_l("spdy/3", proto)) { - config.no_tls_proto = Config::PROTO_SPDY3; - } else if (util::strieq_l("spdy/3.1", proto)) { - config.no_tls_proto = Config::PROTO_SPDY3_1; -#endif // HAVE_SPDYLAY } else { std::cerr << "-p: unsupported protocol " << proto << std::endl; exit(EXIT_FAILURE); @@ -2507,7 +2455,6 @@ int main(int argc, char **argv) { config.h1reqs.reserve(reqlines.size()); config.nva.reserve(reqlines.size()); - config.nv.reserve(reqlines.size()); for (auto &req : reqlines) { // For HTTP/1.1 @@ -2557,35 +2504,6 @@ int main(int argc, char **argv) { } config.nva.push_back(std::move(nva)); - - // For spdylay - std::vector cva; - // 3 for :path, :version, and possible content-length, 1 for - // terminal nullptr - cva.reserve(2 * (3 + shared_nva.size()) + 1); - - cva.push_back(":path"); - cva.push_back(req.c_str()); - - for (auto &nv : shared_nva) { - if (nv.name == ":authority") { - cva.push_back(":host"); - } else { - cva.push_back(nv.name.c_str()); - } - cva.push_back(nv.value.c_str()); - } - cva.push_back(":version"); - cva.push_back("HTTP/1.1"); - - if (!content_length_str.empty()) { - cva.push_back("content-length"); - cva.push_back(content_length_str.c_str()); - } - - cva.push_back(nullptr); - - config.nv.push_back(std::move(cva)); } // Don't DOS our server! diff --git a/src/h2load.h b/src/h2load.h index 01a47c1e..4f6739d4 100644 --- a/src/h2load.h +++ b/src/h2load.h @@ -64,7 +64,6 @@ struct Worker; struct Config { std::vector> nva; - std::vector> nv; std::vector h1reqs; std::vector timings; nghttp2::Headers custom_headers; @@ -93,13 +92,7 @@ struct Config { ev_tstamp conn_active_timeout; // amount of time to wait after the last request is made on a connection ev_tstamp conn_inactivity_timeout; - enum { - PROTO_HTTP2, - PROTO_SPDY2, - PROTO_SPDY3, - PROTO_SPDY3_1, - PROTO_HTTP1_1 - } no_tls_proto; + enum { PROTO_HTTP2, PROTO_HTTP1_1 } no_tls_proto; uint32_t header_table_size; uint32_t encoder_header_table_size; // file descriptor for upload data diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc deleted file mode 100644 index 370aeccc..00000000 --- a/src/h2load_spdy_session.cc +++ /dev/null @@ -1,289 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2014 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 "h2load_spdy_session.h" - -#include -#include - -#include "h2load.h" -#include "util.h" - -using namespace nghttp2; - -namespace h2load { - -SpdySession::SpdySession(Client *client, uint16_t spdy_version) - : client_(client), session_(nullptr), spdy_version_(spdy_version) {} - -SpdySession::~SpdySession() { spdylay_session_del(session_); } - -namespace { -void before_ctrl_send_callback(spdylay_session *session, - spdylay_frame_type type, spdylay_frame *frame, - void *user_data) { - auto client = static_cast(user_data); - if (type != SPDYLAY_SYN_STREAM) { - return; - } - client->on_request(frame->syn_stream.stream_id); - auto req_stat = client->get_req_stat(frame->syn_stream.stream_id); - client->record_request_time(req_stat); -} -} // namespace - -namespace { -void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, - spdylay_frame *frame, void *user_data) { - auto client = static_cast(user_data); - if (type != SPDYLAY_SYN_REPLY) { - return; - } - for (auto p = frame->syn_reply.nv; *p; p += 2) { - auto name = *p; - auto value = *(p + 1); - auto namelen = strlen(name); - auto valuelen = strlen(value); - client->on_header(frame->syn_reply.stream_id, - reinterpret_cast(name), namelen, - reinterpret_cast(value), valuelen); - client->worker->stats.bytes_head_decomp += namelen + valuelen; - } - - // Strictly speaking, we have to subtract 2 (unused field) if SPDY - // version is 2. But it is already deprecated, and we don't do - // extra work for it. - client->worker->stats.bytes_head += frame->syn_reply.hd.length - 4; - - if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) { - client->record_ttfb(); - } -} -} // namespace - -namespace { -void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags, - int32_t stream_id, const uint8_t *data, - size_t len, void *user_data) { - auto client = static_cast(user_data); - - client->record_ttfb(); - client->worker->stats.bytes_body += len; - - auto spdy_session = static_cast(client->session.get()); - - spdy_session->handle_window_update(stream_id, len); -} -} // namespace - -namespace { -void on_stream_close_callback(spdylay_session *session, int32_t stream_id, - spdylay_status_code status_code, - void *user_data) { - auto client = static_cast(user_data); - client->on_stream_close(stream_id, status_code == SPDYLAY_OK); -} -} // namespace - -namespace { -ssize_t send_callback(spdylay_session *session, const uint8_t *data, - size_t length, int flags, void *user_data) { - auto client = static_cast(user_data); - auto &wb = client->wb; - - if (wb.rleft() >= BACKOFF_WRITE_BUFFER_THRES) { - return SPDYLAY_ERR_WOULDBLOCK; - } - - return wb.append(data, length); -} -} // namespace - -namespace { -ssize_t file_read_callback(spdylay_session *session, int32_t stream_id, - uint8_t *buf, size_t length, int *eof, - spdylay_data_source *source, void *user_data) { - auto client = static_cast(user_data); - auto config = client->worker->config; - auto req_stat = client->get_req_stat(stream_id); - - ssize_t nread; - while ((nread = pread(config->data_fd, buf, length, req_stat->data_offset)) == - -1 && - errno == EINTR) - ; - - if (nread == -1) { - return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE; - } - - req_stat->data_offset += nread; - - if (nread == 0 || req_stat->data_offset == config->data_length) { - *eof = 1; - } - - return nread; -} -} // namespace - -void SpdySession::on_connect() { - spdylay_session_callbacks callbacks = {0}; - callbacks.send_callback = send_callback; - callbacks.before_ctrl_send_callback = before_ctrl_send_callback; - callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; - callbacks.on_stream_close_callback = on_stream_close_callback; - callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; - - spdylay_session_client_new(&session_, spdy_version_, &callbacks, client_); - - int val = 1; - spdylay_session_set_option(session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE, &val, - sizeof(val)); - - spdylay_settings_entry iv; - iv.settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE; - iv.flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; - iv.value = (1 << client_->worker->config->window_bits); - spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE, &iv, 1); - - auto config = client_->worker->config; - - if (spdy_version_ >= SPDYLAY_PROTO_SPDY3_1 && - config->connection_window_bits > 16) { - auto delta = - (1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE; - spdylay_submit_window_update(session_, 0, delta); - } - - client_->signal_write(); -} - -int SpdySession::submit_request() { - int rv; - auto config = client_->worker->config; - auto &nv = config->nv[client_->reqidx++]; - - if (client_->reqidx == config->nv.size()) { - client_->reqidx = 0; - } - - spdylay_data_provider prd{{0}, file_read_callback}; - - rv = spdylay_submit_request(session_, 0, nv.data(), - config->data_fd == -1 ? nullptr : &prd, nullptr); - - if (rv != 0) { - return -1; - } - - return 0; -} - -int SpdySession::on_read(const uint8_t *data, size_t len) { - auto rv = spdylay_session_mem_recv(session_, data, len); - if (rv < 0) { - return -1; - } - - assert(static_cast(rv) == len); - - if (spdylay_session_want_read(session_) == 0 && - spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) { - return -1; - } - - client_->signal_write(); - - return 0; -} - -int SpdySession::on_write() { - auto rv = spdylay_session_send(session_); - if (rv != 0) { - return -1; - } - - if (spdylay_session_want_read(session_) == 0 && - spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) { - return -1; - } - return 0; -} - -void SpdySession::terminate() { - spdylay_session_fail_session(session_, SPDYLAY_OK); -} - -namespace { -int32_t determine_window_update_transmission(spdylay_session *session, - int32_t stream_id, - size_t window_bits) { - int32_t recv_length; - - if (stream_id == 0) { - recv_length = spdylay_session_get_recv_data_length(session); - } else { - recv_length = - spdylay_session_get_stream_recv_data_length(session, stream_id); - } - - auto window_size = 1 << window_bits; - - if (recv_length != -1 && recv_length >= window_size / 2) { - return recv_length; - } - - return -1; -} -} // namespace - -void SpdySession::handle_window_update(int32_t stream_id, size_t recvlen) { - auto config = client_->worker->config; - size_t connection_window_bits; - - if (config->connection_window_bits > 16) { - connection_window_bits = config->connection_window_bits; - } else { - connection_window_bits = 16; - } - - auto delta = - determine_window_update_transmission(session_, 0, connection_window_bits); - if (delta > 0) { - spdylay_submit_window_update(session_, 0, delta); - } - - delta = determine_window_update_transmission(session_, stream_id, - config->window_bits); - if (delta > 0) { - spdylay_submit_window_update(session_, stream_id, delta); - } -} - -size_t SpdySession::max_concurrent_streams() { - return (size_t)client_->worker->config->max_concurrent_streams; -} - -} // namespace h2load diff --git a/src/h2load_spdy_session.h b/src/h2load_spdy_session.h deleted file mode 100644 index c7721fac..00000000 --- a/src/h2load_spdy_session.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2014 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 H2LOAD_SPDY_SESSION_H -#define H2LOAD_SPDY_SESSION_H - -#include "h2load_session.h" - -#include - -#include "util.h" - -namespace h2load { - -struct Client; - -class SpdySession : public Session { -public: - SpdySession(Client *client, uint16_t spdy_version); - virtual ~SpdySession(); - virtual void on_connect(); - virtual int submit_request(); - virtual int on_read(const uint8_t *data, size_t len); - virtual int on_write(); - virtual void terminate(); - virtual size_t max_concurrent_streams(); - void handle_window_update(int32_t stream_id, size_t recvlen); - -private: - Client *client_; - spdylay_session *session_; - uint16_t spdy_version_; -}; - -} // namespace h2load - -#endif // H2LOAD_SPDY_SESSION_H diff --git a/src/shrpx.cc b/src/shrpx.cc index c6b01acd..e07c3e1d 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1384,11 +1384,8 @@ bool conf_exists(const char *path) { } // namespace namespace { -constexpr auto DEFAULT_NPN_LIST = StringRef::from_lit("h2,h2-16,h2-14," -#ifdef HAVE_SPDYLAY - "spdy/3.1," -#endif // HAVE_SPDYLAY - "http/1.1"); +constexpr auto DEFAULT_NPN_LIST = + StringRef::from_lit("h2,h2-16,h2-14,http/1.1"); } // namespace namespace { @@ -1490,13 +1487,11 @@ void fill_default_config(Config *config) { timeoutconf.settings = 10_s; } - // window size for HTTP/2 and SPDY upstream connection per stream. - // 2**16-1 = 64KiB-1, which is HTTP/2 default. Please note that - // SPDY/3 default is 64KiB. + // window size for HTTP/2 upstream connection per stream. 2**16-1 + // = 64KiB-1, which is HTTP/2 default. upstreamconf.window_size = 64_k - 1; - // HTTP/2 and SPDY/3.1 has connection-level flow control. The - // default window size for HTTP/2 is 64KiB - 1. SPDY/3's default - // is 64KiB + // HTTP/2 has connection-level flow control. The default window + // size for HTTP/2 is 64KiB - 1. upstreamconf.connection_window_size = 64_k - 1; upstreamconf.max_concurrent_streams = 100; @@ -1624,7 +1619,7 @@ void print_version(std::ostream &out) { namespace { void print_usage(std::ostream &out) { out << R"(Usage: nghttpx [OPTIONS]... [ ] -A reverse proxy for HTTP/2, HTTP/1 and SPDY.)" +A reverse proxy for HTTP/2, and HTTP/1.)" << std::endl; } } // namespace @@ -1990,8 +1985,7 @@ Performance: Timeout: --frontend-http2-read-timeout= - Specify read timeout for HTTP/2 and SPDY frontend - connection. + Specify read timeout for HTTP/2 frontend connection. Default: )" << util::duration_str(config->conn.upstream.timeout.http2_read) << R"( --frontend-read-timeout= @@ -2008,13 +2002,13 @@ Timeout: Default: )" << util::duration_str(config->conn.upstream.timeout.idle_read) << R"( --stream-read-timeout= - Specify read timeout for HTTP/2 and SPDY streams. 0 - means no timeout. + Specify read timeout for HTTP/2 streams. 0 means no + timeout. Default: )" << util::duration_str(config->http2.timeout.stream_read) << R"( --stream-write-timeout= - Specify write timeout for HTTP/2 and SPDY streams. 0 - means no timeout. + Specify write timeout for HTTP/2 streams. 0 means no + timeout. Default: )" << util::duration_str(config->http2.timeout.stream_write) << R"( --backend-read-timeout= @@ -2351,10 +2345,10 @@ SSL/TLS: consider to use --client-no-http2-cipher-black-list option. But be aware its implications. -HTTP/2 and SPDY: +HTTP/2: -c, --frontend-http2-max-concurrent-streams= Set the maximum number of the concurrent streams in one - frontend HTTP/2 and SPDY session. + frontend HTTP/2 session. Default: )" << config->http2.upstream.max_concurrent_streams << R"( --backend-http2-max-concurrent-streams= @@ -2365,14 +2359,13 @@ HTTP/2 and SPDY: Default: )" << config->http2.downstream.max_concurrent_streams << R"( --frontend-http2-window-size= - Sets the per-stream initial window size of HTTP/2 and - SPDY frontend connection. + Sets the per-stream initial window size of HTTP/2 + frontend connection. Default: )" << config->http2.upstream.window_size << R"( --frontend-http2-connection-window-size= - Sets the per-connection window size of HTTP/2 and SPDY - frontend connection. For SPDY connection, the value - less than 64KiB is rounded up to 64KiB. + Sets the per-connection window size of HTTP/2 frontend + connection. Default: )" << config->http2.upstream.connection_window_size << R"( --backend-http2-window-size= @@ -2398,8 +2391,7 @@ HTTP/2 and SPDY: It is also supported if both frontend and backend are HTTP/2 in default mode. In this case, server push from backend session is relayed to frontend, and server push - via Link header field is also supported. SPDY frontend - does not support server push. + via Link header field is also supported. --frontend-http2-optimize-write-buffer-size (Experimental) Enable write buffer size optimization in frontend HTTP/2 TLS connection. This optimization aims @@ -2454,7 +2446,7 @@ HTTP/2 and SPDY: Mode: (default mode) - Accept HTTP/2, SPDY and HTTP/1.1 over SSL/TLS. "no-tls" + Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls" parameter is used in --frontend option, accept HTTP/2 and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1 connection can be upgraded to HTTP/2 through HTTP diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index 42639247..cfe795c3 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -51,9 +51,6 @@ #include "shrpx_api_downstream_connection.h" #include "shrpx_health_monitor_downstream_connection.h" #include "shrpx_log.h" -#ifdef HAVE_SPDYLAY -#include "shrpx_spdy_upstream.h" -#endif // HAVE_SPDYLAY #include "util.h" #include "template.h" #include "tls.h" @@ -608,36 +605,6 @@ int ClientHandler::validate_next_proto() { return 0; } -#ifdef HAVE_SPDYLAY - auto spdy_version = spdylay_npn_get_version(proto.byte(), proto.size()); - if (spdy_version) { - upstream_ = make_unique(spdy_version, this); - - switch (spdy_version) { - case SPDYLAY_PROTO_SPDY2: - alpn_ = StringRef::from_lit("spdy/2"); - break; - case SPDYLAY_PROTO_SPDY3: - alpn_ = StringRef::from_lit("spdy/3"); - break; - case SPDYLAY_PROTO_SPDY3_1: - alpn_ = StringRef::from_lit("spdy/3.1"); - break; - default: - alpn_ = StringRef::from_lit("spdy/unknown"); - } - - // At this point, input buffer is already filled with some bytes. - // The read callback is not called until new data come. So consume - // input buffer here. - if (on_read() != 0) { - return -1; - } - - return 0; - } -#endif // HAVE_SPDYLAY - if (proto == StringRef::from_lit("http/1.1")) { upstream_ = make_unique(this); alpn_ = StringRef::from_lit("http/1.1"); diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 64773260..fb1e75e6 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -2630,8 +2630,7 @@ int parse_config(Config *config, int optid, const StringRef &opt, } // Make 16 bits to the HTTP/2 default 64KiB - 1. This is the same - // behaviour of previous code. For SPDY, we adjust this value in - // SpdyUpstream to look like the SPDY default. + // behaviour of previous code. *resp = (1 << n) - 1; return 0; diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 18a17908..ff1af90c 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -469,7 +469,7 @@ private: Upstream *upstream_; std::unique_ptr dconn_; - // only used by HTTP/2 or SPDY upstream + // only used by HTTP/2 upstream BlockedLink *blocked_link_; // The backend address used to fulfill this request. These are for // logging purpose. @@ -492,7 +492,7 @@ private: int request_state_; // response state int response_state_; - // only used by HTTP/2 or SPDY upstream + // only used by HTTP/2 upstream int dispatch_state_; // true if the connection is upgraded (HTTP Upgrade or CONNECT), // excluding upgrade to HTTP/2. diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 84552fce..61c97caa 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -270,8 +270,8 @@ int Http2Session::disconnect(bool hard) { // When deleting Http2DownstreamConnection, it calls this object's // remove_downstream_connection(). The multiple // Http2DownstreamConnection objects belong to the same - // ClientHandler object if upstream is h2 or SPDY. So be careful - // when you delete ClientHandler here. + // ClientHandler object if upstream is h2. So be careful when you + // delete ClientHandler here. // // We allow creating new pending Http2DownstreamConnection with this // object. Upstream::on_downstream_reset() may add @@ -1520,7 +1520,7 @@ int on_frame_not_send_callback(nghttp2_session *session, if (upstream->on_downstream_reset(downstream, false)) { // This should be done for h1 upstream only. Deleting - // ClientHandler for h2 or SPDY upstream may lead to crash. + // ClientHandler for h2 upstream may lead to crash. delete upstream->get_client_handler(); } diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc deleted file mode 100644 index 904d0839..00000000 --- a/src/shrpx_spdy_upstream.cc +++ /dev/null @@ -1,1429 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2012 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_spdy_upstream.h" - -#include -#include -#include -#include - -#include - -#include "shrpx_client_handler.h" -#include "shrpx_downstream.h" -#include "shrpx_downstream_connection.h" -#include "shrpx_config.h" -#include "shrpx_http.h" -#ifdef HAVE_MRUBY -#include "shrpx_mruby.h" -#endif // HAVE_MRUBY -#include "shrpx_worker.h" -#include "shrpx_http2_session.h" -#include "shrpx_log.h" -#include "http2.h" -#include "util.h" -#include "template.h" - -using namespace nghttp2; - -namespace shrpx { - -namespace { -constexpr size_t MAX_BUFFER_SIZE = 32_k; -} // namespace - -namespace { -int32_t get_connection_window_size() { - return std::max(get_config()->http2.upstream.connection_window_size, - static_cast(64_k)); -} -} // namespace - -namespace { -int32_t get_window_size() { - auto n = get_config()->http2.upstream.window_size; - - // 65535 is the default window size of HTTP/2. OTOH, the default - // window size of SPDY is 65536. The configuration defaults to - // HTTP/2, so if we have 65535, we use 65536 for SPDY. - if (n == 65535) { - return 64_k; - } - - return n; -} -} // namespace - -namespace { -ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len, - int flags, void *user_data) { - auto upstream = static_cast(user_data); - auto wb = upstream->get_response_buf(); - - if (wb->rleft() >= MAX_BUFFER_SIZE) { - return SPDYLAY_ERR_WOULDBLOCK; - } - - wb->append(data, len); - - return len; -} -} // namespace - -namespace { -ssize_t recv_callback(spdylay_session *session, uint8_t *buf, size_t len, - int flags, void *user_data) { - auto upstream = static_cast(user_data); - auto handler = upstream->get_client_handler(); - auto rb = handler->get_rb(); - auto rlimit = handler->get_rlimit(); - - if (rb->rleft() == 0) { - return SPDYLAY_ERR_WOULDBLOCK; - } - - auto nread = std::min(rb->rleft(), len); - - memcpy(buf, rb->pos(), nread); - rb->drain(nread); - rlimit->startw(); - - return nread; -} -} // namespace - -namespace { -void on_stream_close_callback(spdylay_session *session, int32_t stream_id, - spdylay_status_code status_code, - void *user_data) { - auto upstream = static_cast(user_data); - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Stream stream_id=" << stream_id - << " is being closed"; - } - auto downstream = static_cast( - spdylay_session_get_stream_user_data(session, stream_id)); - if (!downstream) { - return; - } - - auto &req = downstream->request(); - - upstream->consume(stream_id, req.unconsumed_body_length); - - req.unconsumed_body_length = 0; - - if (downstream->get_request_state() == Downstream::CONNECT_FAIL) { - upstream->remove_downstream(downstream); - // downstream was deleted - - return; - } - - if (downstream->can_detach_downstream_connection()) { - // Keep-alive - downstream->detach_downstream_connection(); - } - - downstream->set_request_state(Downstream::STREAM_CLOSED); - - // At this point, downstream read may be paused. - - // If shrpx_downstream::push_request_headers() failed, the - // error is handled here. - upstream->remove_downstream(downstream); - // downstream was deleted - - // How to test this case? Request sufficient large download - // and make client send RST_STREAM after it gets first DATA - // frame chunk. -} -} // namespace - -namespace { -void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type, - spdylay_frame *frame, void *user_data) { - auto upstream = static_cast(user_data); - auto config = get_config(); - - switch (type) { - case SPDYLAY_SYN_STREAM: { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Received upstream SYN_STREAM stream_id=" - << frame->syn_stream.stream_id; - } - - auto downstream = - upstream->add_pending_downstream(frame->syn_stream.stream_id); - - auto &req = downstream->request(); - - auto &balloc = downstream->get_block_allocator(); - - auto lgconf = log_config(); - lgconf->update_tstamp(std::chrono::system_clock::now()); - req.tstamp = lgconf->tstamp; - - downstream->reset_upstream_rtimer(); - - auto nv = frame->syn_stream.nv; - - if (LOG_ENABLED(INFO)) { - std::stringstream ss; - for (size_t i = 0; nv[i]; i += 2) { - ss << TTY_HTTP_HD << nv[i] << TTY_RST << ": " << nv[i + 1] << "\n"; - } - ULOG(INFO, upstream) << "HTTP request headers. stream_id=" - << downstream->get_stream_id() << "\n" - << ss.str(); - } - - size_t num_headers = 0; - size_t header_buffer = 0; - for (size_t i = 0; nv[i]; i += 2) { - ++num_headers; - // shut up scan-build - assert(nv[i + 1]); - header_buffer += strlen(nv[i]) + strlen(nv[i + 1]); - } - - auto &httpconf = config->http; - - // spdy does not define usage of trailer fields, and we ignores - // them. - if (header_buffer > httpconf.request_header_field_buffer || - num_headers > httpconf.max_request_header_fields) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - return; - } - - for (size_t i = 0; nv[i]; i += 2) { - auto name = StringRef{nv[i]}; - auto value = StringRef{nv[i + 1]}; - auto token = http2::lookup_token(name.byte(), name.size()); - req.fs.add_header_token(make_string_ref(balloc, StringRef{name}), - make_string_ref(balloc, StringRef{value}), false, - token); - } - - if (req.fs.parse_content_length() != 0) { - if (upstream->error_reply(downstream, 400) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - } - return; - } - - auto path = req.fs.header(http2::HD__PATH); - auto scheme = req.fs.header(http2::HD__SCHEME); - auto host = req.fs.header(http2::HD__HOST); - auto method = req.fs.header(http2::HD__METHOD); - - if (!method) { - upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - return; - } - - auto method_token = http2::lookup_method_token(method->value); - if (method_token == -1) { - if (upstream->error_reply(downstream, 501) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - } - return; - } - - auto is_connect = method_token == HTTP_CONNECT; - if (!path || !host || !http2::non_empty_value(host) || - !http2::non_empty_value(path) || - (!is_connect && (!scheme || !http2::non_empty_value(scheme)))) { - upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - return; - } - - auto authority = is_connect ? path : host; - - if (std::find_if(std::begin(authority->value), std::end(authority->value), - [](char c) { return c == '"' || c == '\\'; }) != - std::end(authority->value)) { - if (upstream->error_reply(downstream, 400) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - } - return; - } - - if (scheme) { - for (auto c : scheme->value) { - if (!(util::is_alpha(c) || util::is_digit(c) || c == '+' || c == '-' || - c == '.')) { - if (upstream->error_reply(downstream, 400) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - } - return; - } - } - } - - // For other than CONNECT method, path must start with "/", except - // for OPTIONS method, which can take "*" as path. - if (!is_connect && path->value[0] != '/' && - (method_token != HTTP_OPTIONS || path->value != "*")) { - upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - return; - } - - req.method = method_token; - if (is_connect) { - req.authority = path->value; - } else { - req.scheme = scheme->value; - req.authority = host->value; - - auto handler = upstream->get_client_handler(); - auto faddr = handler->get_upstream_addr(); - - if (config->http2_proxy && !faddr->alt_mode) { - req.path = path->value; - } else if (method_token == HTTP_OPTIONS && - path->value == StringRef::from_lit("*")) { - // Server-wide OPTIONS request. Path is empty. - } else { - req.path = http2::rewrite_clean_path(balloc, path->value); - } - } - - if (!(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN)) { - req.http2_expect_body = true; - } else if (req.fs.content_length == -1) { - req.fs.content_length = 0; - } - - downstream->inspect_http2_request(); - - downstream->set_request_state(Downstream::HEADER_COMPLETE); - -#ifdef HAVE_MRUBY - auto handler = upstream->get_client_handler(); - auto worker = handler->get_worker(); - auto mruby_ctx = worker->get_mruby_context(); - - if (mruby_ctx->run_on_request_proc(downstream) != 0) { - if (upstream->error_reply(downstream, 500) != 0) { - ULOG(FATAL, upstream) << "error_reply failed"; - return; - } - return; - } -#endif // HAVE_MRUBY - - if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) { - if (!downstream->validate_request_recv_body_length()) { - upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - return; - } - - downstream->disable_upstream_rtimer(); - downstream->set_request_state(Downstream::MSG_COMPLETE); - } - - if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { - return; - } - - upstream->start_downstream(downstream); - - break; - } - default: - break; - } -} -} // namespace - -void SpdyUpstream::start_downstream(Downstream *downstream) { - if (downstream_queue_.can_activate(downstream->request().authority)) { - initiate_downstream(downstream); - return; - } - - downstream_queue_.mark_blocked(downstream); -} - -void SpdyUpstream::initiate_downstream(Downstream *downstream) { - int rv; - - auto dconn = handler_->get_downstream_connection(rv, downstream); - - if (!dconn || - (rv = downstream->attach_downstream_connection(std::move(dconn))) != 0) { - // If downstream connection fails, issue RST_STREAM. - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - downstream->set_request_state(Downstream::CONNECT_FAIL); - - downstream_queue_.mark_failure(downstream); - - return; - } - rv = downstream->push_request_headers(); - if (rv != 0) { - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - - downstream_queue_.mark_failure(downstream); - - return; - } - - downstream_queue_.mark_active(downstream); - - auto &req = downstream->request(); - if (!req.http2_expect_body) { - if (downstream->end_upload_data() != 0) { - if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - } - } -} - -namespace { -void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags, - int32_t stream_id, const uint8_t *data, - size_t len, void *user_data) { - auto upstream = static_cast(user_data); - auto downstream = static_cast( - spdylay_session_get_stream_user_data(session, stream_id)); - - if (!downstream) { - upstream->consume(stream_id, len); - - return; - } - - downstream->reset_upstream_rtimer(); - - if (downstream->push_upload_data_chunk(data, len) != 0) { - if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - - upstream->consume(stream_id, len); - - return; - } - - if (!upstream->get_flow_control()) { - return; - } - - // If connection-level window control is not enabled (e.g, - // spdy/3), spdylay_session_get_recv_data_length() is always - // returns 0. - if (spdylay_session_get_recv_data_length(session) > - std::max(SPDYLAY_INITIAL_WINDOW_SIZE, get_connection_window_size())) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Flow control error on connection: " - << "recv_window_size=" - << spdylay_session_get_recv_data_length(session) - << ", window_size=" << get_connection_window_size(); - } - spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR); - return; - } - if (spdylay_session_get_stream_recv_data_length(session, stream_id) > - std::max(SPDYLAY_INITIAL_WINDOW_SIZE, get_window_size())) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Flow control error: recv_window_size=" - << spdylay_session_get_stream_recv_data_length( - session, stream_id) - << ", initial_window_size=" << get_window_size(); - } - upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR); - return; - } -} -} // namespace - -namespace { -void on_data_recv_callback(spdylay_session *session, uint8_t flags, - int32_t stream_id, int32_t length, void *user_data) { - auto upstream = static_cast(user_data); - auto downstream = static_cast( - spdylay_session_get_stream_user_data(session, stream_id)); - - if (downstream && (flags & SPDYLAY_DATA_FLAG_FIN)) { - if (!downstream->validate_request_recv_body_length()) { - upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - return; - } - - downstream->disable_upstream_rtimer(); - if (downstream->end_upload_data() != 0) { - if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - } - downstream->set_request_state(Downstream::MSG_COMPLETE); - } -} -} // namespace - -namespace { -void on_ctrl_not_send_callback(spdylay_session *session, - spdylay_frame_type type, spdylay_frame *frame, - int error_code, void *user_data) { - auto upstream = static_cast(user_data); - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Failed to send control frame type=" << type - << ", error_code=" << error_code << ":" - << spdylay_strerror(error_code); - } - if (type == SPDYLAY_SYN_REPLY && error_code != SPDYLAY_ERR_STREAM_CLOSED && - error_code != SPDYLAY_ERR_STREAM_CLOSING) { - // To avoid stream hanging around, issue RST_STREAM. - auto stream_id = frame->syn_reply.stream_id; - // TODO Could be always nullptr - auto downstream = static_cast( - spdylay_session_get_stream_user_data(session, stream_id)); - if (downstream) { - upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - } -} -} // namespace - -namespace { -void on_ctrl_recv_parse_error_callback(spdylay_session *session, - spdylay_frame_type type, - const uint8_t *head, size_t headlen, - const uint8_t *payload, - size_t payloadlen, int error_code, - void *user_data) { - auto upstream = static_cast(user_data); - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Failed to parse received control frame. type=" - << type << ", error_code=" << error_code << ":" - << spdylay_strerror(error_code); - } -} -} // namespace - -namespace { -void on_unknown_ctrl_recv_callback(spdylay_session *session, - const uint8_t *head, size_t headlen, - const uint8_t *payload, size_t payloadlen, - void *user_data) { - auto upstream = static_cast(user_data); - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) << "Received unknown control frame."; - } -} -} // namespace - -namespace { -// Infer upstream RST_STREAM status code from downstream HTTP/2 -// error code. -uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) { - // Only propagate *_REFUSED_STREAM so that upstream client can - // resend request. - if (downstream_error_code == NGHTTP2_REFUSED_STREAM) { - return SPDYLAY_REFUSED_STREAM; - } else { - return SPDYLAY_INTERNAL_ERROR; - } -} -} // namespace - -namespace { -size_t downstream_queue_size(Worker *worker) { - auto &downstreamconf = *worker->get_downstream_config(); - - if (get_config()->http2_proxy) { - return downstreamconf.connections_per_host; - } - - return downstreamconf.connections_per_frontend; -} -} // namespace - -SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler) - : wb_(handler->get_worker()->get_mcpool()), - downstream_queue_(downstream_queue_size(handler->get_worker()), - !get_config()->http2_proxy), - handler_(handler), - session_(nullptr) { - spdylay_session_callbacks callbacks{}; - callbacks.send_callback = send_callback; - callbacks.recv_callback = recv_callback; - callbacks.on_stream_close_callback = on_stream_close_callback; - callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback; - callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback; - callbacks.on_data_recv_callback = on_data_recv_callback; - callbacks.on_ctrl_not_send_callback = on_ctrl_not_send_callback; - callbacks.on_ctrl_recv_parse_error_callback = - on_ctrl_recv_parse_error_callback; - callbacks.on_unknown_ctrl_recv_callback = on_unknown_ctrl_recv_callback; - - int rv; - rv = spdylay_session_server_new(&session_, version, &callbacks, this); - assert(rv == 0); - - uint32_t max_buffer = 64_k; - rv = spdylay_session_set_option(session_, - SPDYLAY_OPT_MAX_RECV_CTRL_FRAME_BUFFER, - &max_buffer, sizeof(max_buffer)); - assert(rv == 0); - - auto config = get_config(); - auto &http2conf = config->http2; - - auto faddr = handler_->get_upstream_addr(); - - // We use automatic WINDOW_UPDATE for API endpoints. Since SPDY is - // going to be deprecated in the future, and the default stream - // window is large enough for API request body (64KiB), we don't - // expand window size depending on the options. - int32_t initial_window_size; - if (version >= SPDYLAY_PROTO_SPDY3 && !faddr->alt_mode) { - int val = 1; - flow_control_ = true; - initial_window_size = get_window_size(); - rv = spdylay_session_set_option( - session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE2, &val, sizeof(val)); - assert(rv == 0); - } else { - flow_control_ = false; - initial_window_size = 0; - } - // TODO Maybe call from outside? - std::array entry; - size_t num_entry = 1; - entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS; - entry[0].value = http2conf.upstream.max_concurrent_streams; - entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; - - if (flow_control_) { - ++num_entry; - entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE; - entry[1].value = initial_window_size; - entry[1].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; - } - - rv = spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE, - entry.data(), num_entry); - assert(rv == 0); - - auto connection_window_size = get_connection_window_size(); - - if (flow_control_ && version >= SPDYLAY_PROTO_SPDY3_1 && - connection_window_size > static_cast(64_k)) { - int32_t delta = connection_window_size - SPDYLAY_INITIAL_WINDOW_SIZE; - rv = spdylay_submit_window_update(session_, 0, delta); - assert(rv == 0); - } - - handler_->reset_upstream_read_timeout( - config->conn.upstream.timeout.http2_read); - - handler_->signal_write(); -} - -SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); } - -int SpdyUpstream::on_read() { - int rv = 0; - - rv = spdylay_session_recv(session_); - if (rv < 0) { - if (rv != SPDYLAY_ERR_EOF) { - ULOG(ERROR, this) << "spdylay_session_recv() returned error: " - << spdylay_strerror(rv); - } - return rv; - } - - handler_->signal_write(); - - return 0; -} - -// After this function call, downstream may be deleted. -int SpdyUpstream::on_write() { - int rv = 0; - - if (wb_.rleft() >= MAX_BUFFER_SIZE) { - return 0; - } - - rv = spdylay_session_send(session_); - if (rv != 0) { - ULOG(ERROR, this) << "spdylay_session_send() returned error: " - << spdylay_strerror(rv); - return rv; - } - - if (spdylay_session_want_read(session_) == 0 && - spdylay_session_want_write(session_) == 0 && wb_.rleft() == 0) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, this) << "No more read/write for this SPDY session"; - } - return -1; - } - return 0; -} - -ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; } - -int SpdyUpstream::downstream_read(DownstreamConnection *dconn) { - auto downstream = dconn->get_downstream(); - - if (downstream->get_response_state() == Downstream::MSG_RESET) { - // The downstream stream was reset (canceled). In this case, - // RST_STREAM to the upstream and delete downstream connection - // here. Deleting downstream will be taken place at - // on_stream_close_callback. - rst_stream(downstream, - infer_upstream_rst_stream_status_code( - downstream->get_response_rst_stream_error_code())); - downstream->pop_downstream_connection(); - dconn = nullptr; - } else if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) { - if (error_reply(downstream, 502) != 0) { - return -1; - } - downstream->pop_downstream_connection(); - // dconn was deleted - dconn = nullptr; - } else { - auto rv = downstream->on_read(); - if (rv == SHRPX_ERR_EOF) { - return downstream_eof(dconn); - } - if (rv == SHRPX_ERR_DCONN_CANCELED) { - downstream->pop_downstream_connection(); - handler_->signal_write(); - return 0; - } - if (rv != 0) { - if (rv != SHRPX_ERR_NETWORK) { - if (LOG_ENABLED(INFO)) { - DCLOG(INFO, dconn) << "HTTP parser failure"; - } - } - return downstream_error(dconn, Downstream::EVENT_ERROR); - } - if (downstream->can_detach_downstream_connection()) { - // Keep-alive - downstream->detach_downstream_connection(); - } - } - - handler_->signal_write(); - // At this point, downstream may be deleted. - - return 0; -} - -int SpdyUpstream::downstream_write(DownstreamConnection *dconn) { - int rv; - rv = dconn->on_write(); - if (rv == SHRPX_ERR_NETWORK) { - return downstream_error(dconn, Downstream::EVENT_ERROR); - } - if (rv != 0) { - return rv; - } - return 0; -} - -int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) { - auto downstream = dconn->get_downstream(); - - if (LOG_ENABLED(INFO)) { - DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id(); - } - - // Delete downstream connection. If we don't delete it here, it will - // be pooled in on_stream_close_callback. - downstream->pop_downstream_connection(); - // dconn was deleted - dconn = nullptr; - // downstream wil be deleted in on_stream_close_callback. - if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - // Server may indicate the end of the request by EOF - if (LOG_ENABLED(INFO)) { - ULOG(INFO, this) << "Downstream body was ended by EOF"; - } - downstream->set_response_state(Downstream::MSG_COMPLETE); - - // For tunneled connection, MSG_COMPLETE signals - // downstream_data_read_callback to send RST_STREAM after pending - // response body is sent. This is needed to ensure that RST_STREAM - // is sent after all pending data are sent. - on_downstream_body_complete(downstream); - } else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) { - // If stream was not closed, then we set MSG_COMPLETE and let - // on_stream_close_callback delete downstream. - if (error_reply(downstream, 502) != 0) { - return -1; - } - } - handler_->signal_write(); - // At this point, downstream may be deleted. - return 0; -} - -int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) { - auto downstream = dconn->get_downstream(); - - if (LOG_ENABLED(INFO)) { - if (events & Downstream::EVENT_ERROR) { - DCLOG(INFO, dconn) << "Downstream network/general error"; - } else { - DCLOG(INFO, dconn) << "Timeout"; - } - if (downstream->get_upgraded()) { - DCLOG(INFO, dconn) << "Note: this is tunnel connection"; - } - } - - // Delete downstream connection. If we don't delete it here, it will - // be pooled in on_stream_close_callback. - downstream->pop_downstream_connection(); - // dconn was deleted - dconn = nullptr; - - if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { - // For SSL tunneling, we issue RST_STREAM. For other types of - // stream, we don't have to do anything since response was - // complete. - if (downstream->get_upgraded()) { - // We want "NO_ERROR" error code but SPDY does not have such - // code for RST_STREAM. - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - } else { - if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - if (downstream->get_upgraded()) { - on_downstream_body_complete(downstream); - } else { - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - } else { - unsigned int status; - if (events & Downstream::EVENT_TIMEOUT) { - status = 504; - } else { - status = 502; - } - if (error_reply(downstream, status) != 0) { - return -1; - } - } - downstream->set_response_state(Downstream::MSG_COMPLETE); - } - handler_->signal_write(); - // At this point, downstream may be deleted. - return 0; -} - -int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id(); - } - int rv; - rv = spdylay_submit_rst_stream(session_, downstream->get_stream_id(), - status_code); - if (rv < SPDYLAY_ERR_FATAL) { - ULOG(FATAL, this) << "spdylay_submit_rst_stream() failed: " - << spdylay_strerror(rv); - return -1; - } - return 0; -} - -namespace { -ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id, - uint8_t *buf, size_t length, int *eof, - spdylay_data_source *source, void *user_data) { - auto downstream = static_cast(source->ptr); - auto upstream = static_cast(downstream->get_upstream()); - auto body = downstream->get_response_buf(); - assert(body); - - auto nread = body->remove(buf, length); - auto body_empty = body->rleft() == 0; - - if (nread == 0 && - downstream->get_response_state() == Downstream::MSG_COMPLETE) { - if (!downstream->get_upgraded()) { - *eof = 1; - } else { - // For tunneling, issue RST_STREAM to finish the stream. - if (LOG_ENABLED(INFO)) { - ULOG(INFO, upstream) - << "RST_STREAM to tunneled stream stream_id=" << stream_id; - } - upstream->rst_stream( - downstream, infer_upstream_rst_stream_status_code( - downstream->get_response_rst_stream_error_code())); - } - } - - if (body_empty) { - downstream->disable_upstream_wtimer(); - } else { - downstream->reset_upstream_wtimer(); - } - - if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) { - return SPDYLAY_ERR_CALLBACK_FAILURE; - } - - if (nread == 0 && *eof != 1) { - return SPDYLAY_ERR_DEFERRED; - } - - if (nread > 0) { - downstream->response_sent_body_length += nread; - } - - return nread; -} -} // namespace - -int SpdyUpstream::send_reply(Downstream *downstream, const uint8_t *body, - size_t bodylen) { - int rv; - - spdylay_data_provider data_prd, *data_prd_ptr = nullptr; - if (bodylen) { - data_prd.source.ptr = downstream; - data_prd.read_callback = spdy_data_read_callback; - data_prd_ptr = &data_prd; - } - - const auto &resp = downstream->response(); - auto &balloc = downstream->get_block_allocator(); - - auto status_line = http2::stringify_status(balloc, resp.http_status); - - const auto &headers = resp.fs.headers(); - - auto config = get_config(); - auto &httpconf = config->http; - - auto nva = std::vector(); - // 6 for :status, :version and server. 1 for last terminal nullptr. - nva.reserve(6 + headers.size() * 2 + - httpconf.add_response_headers.size() * 2 + 1); - - nva.push_back(":status"); - nva.push_back(status_line.c_str()); - nva.push_back(":version"); - nva.push_back("HTTP/1.1"); - - for (auto &kv : headers) { - if (kv.name.empty() || kv.name[0] == ':') { - continue; - } - switch (kv.token) { - case http2::HD_CONNECTION: - case http2::HD_KEEP_ALIVE: - case http2::HD_PROXY_CONNECTION: - case http2::HD_TRANSFER_ENCODING: - continue; - } - nva.push_back(kv.name.c_str()); - nva.push_back(kv.value.c_str()); - } - - if (!resp.fs.header(http2::HD_SERVER)) { - nva.push_back("server"); - nva.push_back(config->http.server_name.c_str()); - } - - for (auto &p : httpconf.add_response_headers) { - nva.push_back(p.name.c_str()); - nva.push_back(p.value.c_str()); - } - - nva.push_back(nullptr); - - rv = spdylay_submit_response(session_, downstream->get_stream_id(), - nva.data(), data_prd_ptr); - if (rv < SPDYLAY_ERR_FATAL) { - ULOG(FATAL, this) << "spdylay_submit_response() failed: " - << spdylay_strerror(rv); - return -1; - } - - auto buf = downstream->get_response_buf(); - - buf->append(body, bodylen); - - downstream->set_response_state(Downstream::MSG_COMPLETE); - - if (data_prd_ptr) { - downstream->reset_upstream_wtimer(); - } - - return 0; -} - -int SpdyUpstream::error_reply(Downstream *downstream, - unsigned int status_code) { - int rv; - auto &resp = downstream->response(); - auto &balloc = downstream->get_block_allocator(); - - auto html = http::create_error_html(balloc, status_code); - resp.http_status = status_code; - auto body = downstream->get_response_buf(); - body->append(html); - downstream->set_response_state(Downstream::MSG_COMPLETE); - - spdylay_data_provider data_prd; - data_prd.source.ptr = downstream; - data_prd.read_callback = spdy_data_read_callback; - - auto lgconf = log_config(); - lgconf->update_tstamp(std::chrono::system_clock::now()); - - auto content_length = util::make_string_ref_uint(balloc, html.size()); - auto status_line = http2::stringify_status(balloc, status_code); - - const char *nv[] = {":status", status_line.c_str(), - ":version", "http/1.1", - "content-type", "text/html; charset=UTF-8", - "server", get_config()->http.server_name.c_str(), - "content-length", content_length.c_str(), - "date", lgconf->tstamp->time_http.c_str(), - nullptr}; - - rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv, - &data_prd); - if (rv < SPDYLAY_ERR_FATAL) { - ULOG(FATAL, this) << "spdylay_submit_response() failed: " - << spdylay_strerror(rv); - return -1; - } - - downstream->reset_upstream_wtimer(); - - return 0; -} - -Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id) { - auto downstream = - make_unique(this, handler_->get_mcpool(), stream_id); - spdylay_session_set_stream_user_data(session_, stream_id, downstream.get()); - auto res = downstream.get(); - - downstream_queue_.add_pending(std::move(downstream)); - - handler_->stop_read_timer(); - - return res; -} - -void SpdyUpstream::remove_downstream(Downstream *downstream) { - if (downstream->accesslog_ready()) { - handler_->write_accesslog(downstream); - } - - spdylay_session_set_stream_user_data(session_, downstream->get_stream_id(), - nullptr); - - auto next_downstream = downstream_queue_.remove_and_get_blocked(downstream); - - if (next_downstream) { - initiate_downstream(next_downstream); - } - - if (downstream_queue_.get_downstreams() == nullptr) { - handler_->repeat_read_timer(); - } -} - -// WARNING: Never call directly or indirectly spdylay_session_send or -// spdylay_session_recv. These calls may delete downstream. -int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { - auto &resp = downstream->response(); - - if (downstream->get_non_final_response()) { - // SPDY does not support non-final response. We could send it - // with HEADERS and final response in SYN_REPLY, but it is not - // official way. - resp.fs.clear_headers(); - - return 0; - } - - const auto &req = downstream->request(); - auto &balloc = downstream->get_block_allocator(); - -#ifdef HAVE_MRUBY - auto worker = handler_->get_worker(); - auto mruby_ctx = worker->get_mruby_context(); - - if (mruby_ctx->run_on_response_proc(downstream) != 0) { - if (error_reply(downstream, 500) != 0) { - return -1; - } - // Returning -1 will signal deletion of dconn. - return -1; - } - - if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { - return -1; - } -#endif // HAVE_MRUBY - - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response header completed"; - } - - auto config = get_config(); - auto &httpconf = config->http; - - if (!config->http2_proxy && !httpconf.no_location_rewrite) { - downstream->rewrite_location_response_header(req.scheme); - } - - // 8 means server, :status, :version and possible via header field. - auto nv = - make_unique(resp.fs.headers().size() * 2 + 8 + - httpconf.add_response_headers.size() * 2 + 1); - - size_t hdidx = 0; - std::string via_value; - auto status_line = http2::stringify_status(balloc, resp.http_status); - - nv[hdidx++] = ":status"; - nv[hdidx++] = status_line.c_str(); - nv[hdidx++] = ":version"; - nv[hdidx++] = "HTTP/1.1"; - for (auto &hd : resp.fs.headers()) { - if (hd.name.empty() || hd.name.c_str()[0] == ':') { - continue; - } - switch (hd.token) { - case http2::HD_CONNECTION: - case http2::HD_KEEP_ALIVE: - case http2::HD_PROXY_CONNECTION: - case http2::HD_TRANSFER_ENCODING: - case http2::HD_VIA: - case http2::HD_SERVER: - continue; - } - - nv[hdidx++] = hd.name.c_str(); - nv[hdidx++] = hd.value.c_str(); - } - - if (!get_config()->http2_proxy && !httpconf.no_server_rewrite) { - nv[hdidx++] = "server"; - nv[hdidx++] = httpconf.server_name.c_str(); - } else { - auto server = resp.fs.header(http2::HD_SERVER); - if (server) { - nv[hdidx++] = "server"; - nv[hdidx++] = server->value.c_str(); - } - } - - auto via = resp.fs.header(http2::HD_VIA); - if (httpconf.no_via) { - if (via) { - nv[hdidx++] = "via"; - nv[hdidx++] = via->value.c_str(); - } - } else { - if (via) { - via_value = via->value.str(); - via_value += ", "; - } - std::array viabuf; - auto end = http::create_via_header_value(std::begin(viabuf), - resp.http_major, resp.http_minor); - via_value.append(std::begin(viabuf), end); - nv[hdidx++] = "via"; - nv[hdidx++] = via_value.c_str(); - } - - for (auto &p : httpconf.add_response_headers) { - nv[hdidx++] = p.name.c_str(); - nv[hdidx++] = p.value.c_str(); - } - - nv[hdidx++] = 0; - if (LOG_ENABLED(INFO)) { - std::stringstream ss; - for (size_t i = 0; nv[i]; i += 2) { - ss << TTY_HTTP_HD << nv[i] << TTY_RST << ": " << nv[i + 1] << "\n"; - } - ULOG(INFO, this) << "HTTP response headers. stream_id=" - << downstream->get_stream_id() << "\n" - << ss.str(); - } - spdylay_data_provider data_prd; - data_prd.source.ptr = downstream; - data_prd.read_callback = spdy_data_read_callback; - - int rv; - rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv.get(), - &data_prd); - if (rv != 0) { - ULOG(FATAL, this) << "spdylay_submit_response() failed"; - return -1; - } - - downstream->reset_upstream_wtimer(); - - return 0; -} - -// WARNING: Never call directly or indirectly spdylay_session_send or -// spdylay_session_recv. These calls may delete downstream. -int SpdyUpstream::on_downstream_body(Downstream *downstream, - const uint8_t *data, size_t len, - bool flush) { - auto body = downstream->get_response_buf(); - body->append(data, len); - - if (flush) { - spdylay_session_resume_data(session_, downstream->get_stream_id()); - - downstream->ensure_upstream_wtimer(); - } - - return 0; -} - -// WARNING: Never call directly or indirectly spdylay_session_send or -// spdylay_session_recv. These calls may delete downstream. -int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) { - if (LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response completed"; - } - - auto &resp = downstream->response(); - - if (!downstream->validate_response_recv_body_length()) { - rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR); - resp.connection_close = true; - return 0; - } - - spdylay_session_resume_data(session_, downstream->get_stream_id()); - downstream->ensure_upstream_wtimer(); - - return 0; -} - -bool SpdyUpstream::get_flow_control() const { return flow_control_; } - -void SpdyUpstream::pause_read(IOCtrlReason reason) {} - -int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream, - size_t consumed) { - if (get_flow_control()) { - if (consume(downstream->get_stream_id(), consumed) != 0) { - return -1; - } - - auto &req = downstream->request(); - - req.consume(consumed); - } - - handler_->signal_write(); - return 0; -} - -int SpdyUpstream::on_downstream_abort_request(Downstream *downstream, - unsigned int status_code) { - int rv; - - rv = error_reply(downstream, status_code); - - if (rv != 0) { - return -1; - } - - handler_->signal_write(); - return 0; -} - -int SpdyUpstream::on_downstream_abort_request_with_https_redirect( - Downstream *downstream) { - // This should not be called since SPDY is only available with TLS. - assert(0); - return 0; -} - -int SpdyUpstream::consume(int32_t stream_id, size_t len) { - int rv; - - if (!get_flow_control()) { - return 0; - } - - rv = spdylay_session_consume(session_, stream_id, len); - - if (rv != 0) { - ULOG(WARN, this) << "spdylay_session_consume() returned error: " - << spdylay_strerror(rv); - return -1; - } - - return 0; -} - -int SpdyUpstream::on_timeout(Downstream *downstream) { - if (LOG_ENABLED(INFO)) { - ULOG(INFO, this) << "Stream timeout stream_id=" - << downstream->get_stream_id(); - } - - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - - handler_->signal_write(); - - return 0; -} - -void SpdyUpstream::on_handler_delete() { - for (auto d = downstream_queue_.get_downstreams(); d; d = d->dlnext) { - if (d->get_dispatch_state() == Downstream::DISPATCH_ACTIVE && - d->accesslog_ready()) { - handler_->write_accesslog(d); - } - } -} - -int SpdyUpstream::on_downstream_reset(Downstream *downstream, bool no_retry) { - int rv; - - if (downstream->get_dispatch_state() != Downstream::DISPATCH_ACTIVE) { - // This is error condition when we failed push_request_headers() - // in initiate_downstream(). Otherwise, we have - // Downstream::DISPATCH_ACTIVE state, or we did not set - // DownstreamConnection. - downstream->pop_downstream_connection(); - - handler_->signal_write(); - - return 0; - } - - if (!downstream->request_submission_ready()) { - if (downstream->get_response_state() == Downstream::MSG_COMPLETE) { - // We have got all response body already. Send it off. - downstream->pop_downstream_connection(); - return 0; - } - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - downstream->pop_downstream_connection(); - - handler_->signal_write(); - - return 0; - } - - downstream->pop_downstream_connection(); - - downstream->add_retry(); - - std::unique_ptr dconn; - - if (no_retry || downstream->no_more_retry()) { - goto fail; - } - - // downstream connection is clean; we can retry with new - // downstream connection. - - dconn = handler_->get_downstream_connection(rv, downstream); - if (!dconn) { - goto fail; - } - - rv = downstream->attach_downstream_connection(std::move(dconn)); - if (rv != 0) { - goto fail; - } - - rv = downstream->push_request_headers(); - if (rv != 0) { - goto fail; - } - - return 0; - -fail: - if (on_downstream_abort_request(downstream, 502) != 0) { - rst_stream(downstream, SPDYLAY_INTERNAL_ERROR); - } - downstream->pop_downstream_connection(); - - handler_->signal_write(); - - return 0; -} - -int SpdyUpstream::initiate_push(Downstream *downstream, const StringRef &uri) { - return 0; -} - -int SpdyUpstream::response_riovec(struct iovec *iov, int iovcnt) const { - if (iovcnt == 0 || wb_.rleft() == 0) { - return 0; - } - - return wb_.riovec(iov, iovcnt); -} - -void SpdyUpstream::response_drain(size_t n) { wb_.drain(n); } - -bool SpdyUpstream::response_empty() const { return wb_.rleft() == 0; } - -DefaultMemchunks *SpdyUpstream::get_response_buf() { return &wb_; } - -Downstream * -SpdyUpstream::on_downstream_push_promise(Downstream *downstream, - int32_t promised_stream_id) { - return nullptr; -} - -int SpdyUpstream::on_downstream_push_promise_complete( - Downstream *downstream, Downstream *promised_downstream) { - return -1; -} - -bool SpdyUpstream::push_enabled() const { return false; } - -void SpdyUpstream::cancel_premature_downstream( - Downstream *promised_downstream) {} - -} // namespace shrpx diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h deleted file mode 100644 index 826a887e..00000000 --- a/src/shrpx_spdy_upstream.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2012 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_SPDY_UPSTREAM_H -#define SHRPX_SPDY_UPSTREAM_H - -#include "shrpx.h" - -#include - -#include - -#include - -#include "shrpx_upstream.h" -#include "shrpx_downstream_queue.h" -#include "memchunk.h" -#include "buffer.h" - -namespace shrpx { - -class ClientHandler; - -class SpdyUpstream : public Upstream { -public: - SpdyUpstream(uint16_t version, ClientHandler *handler); - virtual ~SpdyUpstream(); - virtual int on_read(); - virtual int on_write(); - virtual int on_timeout(Downstream *downstream); - virtual int on_downstream_abort_request(Downstream *downstream, - unsigned int status_code); - virtual int - on_downstream_abort_request_with_https_redirect(Downstream *downstream); - virtual ClientHandler *get_client_handler() const; - virtual int downstream_read(DownstreamConnection *dconn); - virtual int downstream_write(DownstreamConnection *dconn); - virtual int downstream_eof(DownstreamConnection *dconn); - virtual int downstream_error(DownstreamConnection *dconn, int events); - Downstream *add_pending_downstream(int32_t stream_id); - void remove_downstream(Downstream *downstream); - - int rst_stream(Downstream *downstream, int status_code); - int error_reply(Downstream *downstream, unsigned int status_code); - - virtual void pause_read(IOCtrlReason reason); - virtual int resume_read(IOCtrlReason reason, Downstream *downstream, - size_t consumed); - - virtual int on_downstream_header_complete(Downstream *downstream); - virtual int on_downstream_body(Downstream *downstream, const uint8_t *data, - size_t len, bool flush); - virtual int on_downstream_body_complete(Downstream *downstream); - - virtual void on_handler_delete(); - virtual int on_downstream_reset(Downstream *downstream, bool no_retry); - - virtual int send_reply(Downstream *downstream, const uint8_t *body, - size_t bodylen); - virtual int initiate_push(Downstream *downstream, const StringRef &uri); - virtual int response_riovec(struct iovec *iov, int iovcnt) const; - virtual void response_drain(size_t n); - virtual bool response_empty() const; - - virtual Downstream *on_downstream_push_promise(Downstream *downstream, - int32_t promised_stream_id); - virtual int - on_downstream_push_promise_complete(Downstream *downstream, - Downstream *promised_downstream); - virtual bool push_enabled() const; - virtual void cancel_premature_downstream(Downstream *promised_downstream); - - bool get_flow_control() const; - - int consume(int32_t stream_id, size_t len); - - void start_downstream(Downstream *downstream); - void initiate_downstream(Downstream *downstream); - - DefaultMemchunks *get_response_buf(); - -private: - DefaultMemchunks wb_; - DownstreamQueue downstream_queue_; - ClientHandler *handler_; - spdylay_session *session_; - bool flow_control_; -}; - -} // namespace shrpx - -#endif // SHRPX_SPDY_UPSTREAM_H diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index ae8a8511..b4c11314 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -51,10 +51,6 @@ #include -#ifdef HAVE_SPDYLAY -#include -#endif // HAVE_SPDYLAY - #include "shrpx_log.h" #include "shrpx_client_handler.h" #include "shrpx_config.h"