diff --git a/src/aleph/http.clj b/src/aleph/http.clj index 8231dc76..68a35da4 100644 --- a/src/aleph/http.clj +++ b/src/aleph/http.clj @@ -52,7 +52,7 @@ | `socket-address` | A `java.net.SocketAddress` specifying both the port and interface to bind to. | | `bootstrap-transform` | A function that takes an `io.netty.bootstrap.ServerBootstrap` object, which represents the server, and modifies it. | | `http-versions` | An optional vector of allowable HTTP versions to negotiate via ALPN, in preference order. Defaults to `[:http1]`. | - | `ssl-context` | An `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-server-context` for more details) if an SSL connection is desired | + | `ssl-context` | An `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-server-context` for more details) if an SSL connection is desired. When passing an `io.netty.handler.ssl.SslContext` object, it must have an ALPN config matching the `http-versions` option (see `aleph.netty/ssl-server-context` and `aleph.netty/application-protocol-config`). If only HTTP/1.1 is desired, ALPN config is optional. | `manual-ssl?` | Set to `true` to indicate that SSL is active, but the caller is managing it (this implies `:ssl-context` is nil). For example, this can be used if you want to use configure SNI (perhaps in `:pipeline-transform` ) to select the SSL context based on the client's indicated host name. | | `executor` | A `java.util.concurrent.Executor` which is used to handle individual requests. To avoid this indirection you may specify `:none`, but in this case extreme care must be taken to avoid blocking operations on the handler's thread. | | `shutdown-executor?` | If `true`, the executor will be shut down when `.close()` is called on the server, defaults to `true` . | @@ -133,6 +133,14 @@ (defn connection-pool "Returns a connection pool which can be used as an argument in `request`. + To enable HTTP/2, add `:http2` to the `http-versions` option. If you supply an + `io.netty.handler.ssl.SslContext` instance for `:ssl-context`, you have to set it up with ALPN + support for HTTP/2, as well. See `aleph.netty/ssl-client-context` and + `aleph.netty/application-protocol-config` for more details. If you supply an SSL options map + without an `:application-protocol-config` key instead, the necessary ALPN configuration will be + set up automatically. (You can also set `force-h2c?` to force HTTP/2 cleartext, but this is + strongly discouraged.) + Param key | Description | --- | --- | `connections-per-host` | the maximum number of simultaneous connections to any host @@ -150,7 +158,7 @@ Param key | Description | --- | --- - | `ssl-context` | an `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-client-context` for more details), only required if a custom context is required + | `ssl-context` | an `io.netty.handler.ssl.SslContext` object or a map of SSL context options (see `aleph.netty/ssl-client-context` for more details), only required if a custom context is desired. When passing an `io.netty.handler.ssl.SslContext` object, it must have an ALPN config matching the `http-versions` option (see `aleph.netty/ssl-client-context` and `aleph.netty/application-protocol-config`). If only HTTP/1.1 is desired, ALPN config is optional. | `ssl-endpoint-id-alg` | the name of the algorithm to use for SSL endpoint identification (see https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#endpoint-identification-algorithms), defaults to \"HTTPS\". Only used for HTTPS connections. Pass `nil` to disable endpoint identification. | `local-address` | an optional `java.net.SocketAddress` describing which local interface should be used | `bootstrap-transform` | a function that takes an `io.netty.bootstrap.Bootstrap` object and modifies it. diff --git a/src/aleph/netty.clj b/src/aleph/netty.clj index 73fc1b6e..49336df4 100644 --- a/src/aleph/netty.clj +++ b/src/aleph/netty.clj @@ -938,7 +938,17 @@ "Creates a default config for Application-Layer Protocol Negotiation (ALPN), which TLS uses to negotiate which HTTP version to use during the handshake. - Takes a vector of HTTP versions, in order of preference. E.g., `[:http2 :http1]`" + Takes a vector of HTTP versions, in order of preference. E.g., `[:http2 :http1]` + + Note that the returned config uses `SelectorFailureBehavior.NO_ADVERTISE`[1] and + `SelectedListenerFailureBehavior.ACCEPT`[2] since these are the only failure behaviors + supported by all SSL providers. See their documentation for details. One important + consequence of this is that it's not possible to completely opt out of HTTP/1.1 by way of + only specifying `[:http2]`. If you want this behavior and you know your SSL provider supports + it, you can construct an `ApplicationProtocolConfig` with the desired settings yourself. + + 1: https://netty.io/4.1/api/io/netty/handler/ssl/ApplicationProtocolConfig.SelectorFailureBehavior.html#NO_ADVERTISE + 2: https://netty.io/4.1/api/io/netty/handler/ssl/ApplicationProtocolConfig.SelectedListenerFailureBehavior.html#ACCEPT" ^ApplicationProtocolConfig [protocols] (ApplicationProtocolConfig. diff --git a/test/aleph/http_test.clj b/test/aleph/http_test.clj index 17afb727..309fceca 100644 --- a/test/aleph/http_test.clj +++ b/test/aleph/http_test.clj @@ -1619,6 +1619,27 @@ (is (instance? IllegalArgumentException result)) (is (= "force-h2c? may only be true when HTTP/2 is enabled." (ex-message result)))))))) +(deftest http2-only-client-connecting-to-http1-only-server + (testing "No ALPN config, desiring only HTTP/2 but the server only allows HTTP/1" + (with-http1-server echo-handler http1-ssl-server-options + (with-redefs [*use-tls-requests* true] + (let [result (try-request-with-pool + {:connection-options + {:http-versions [:http2] + :ssl-context test-ssl/client-ssl-context-opts}})] + (is (= :success result) "succeeds due to the default failure behaviors (see docstring of `application-protocol-config`)")))))) + + +(deftest http1-only-client-connecting-to-http2-only-server + (testing "No ALPN config, desiring only HTTP/1.1 but the server only allows HTTP/2" + (with-http2-server echo-handler {} + (with-redefs [*use-tls-requests* true] + (let [result (try-request-with-pool + {:connection-options + {:http-versions [:http1] + :ssl-context test-ssl/client-ssl-context-opts}})] + (is (= :success result) "succeeds due to the default failure behaviors (see docstring of `application-protocol-config`)")))))) + (deftest test-in-flight-request-cancellation (let [conn-established (promise)