Skip to content

Commit a6f1579

Browse files
committed
Add initial-pipeline-transform option to aleph.http/server-start
This allows :manual-ssl? to be used in combination with HTTP/2, as well.
1 parent d9004d6 commit a6f1579

File tree

3 files changed

+79
-41
lines changed

3 files changed

+79
-41
lines changed

src/aleph/http.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
| `bootstrap-transform` | A function that takes an `io.netty.bootstrap.ServerBootstrap` object, which represents the server, and modifies it. |
5555
| `http-versions` | An optional vector of allowable HTTP versions to negotiate via ALPN, in preference order. Defaults to `[:http1]`. |
5656
| `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.
57-
| `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. |
57+
| `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 (usually via `:initial-pipeline-transform`) to select the SSL context based on the client's indicated host name. |
5858
| `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. |
5959
| `shutdown-executor?` | If `true`, the executor will be shut down when `.close()` is called on the server, defaults to `true` . |
6060
| `request-buffer-size` | The maximum body size, in bytes, that the server will allow to accumulate before placing on the body stream, defaults to `16384` . This does *not* represent the maximum size request the server can handle (which is unbounded), and is only a means of maximizing performance. |
@@ -69,6 +69,7 @@
6969
| `continue-handler` | Optional handler which is invoked when header sends \"Except: 100-continue\" header to test whether the request should be accepted or rejected. Handler should return `true`, `false`, ring responseo to be used as a reject response or deferred that yields one of those. |
7070
| `continue-executor` | Optional `java.util.concurrent.Executor` which is used to handle requests passed to :continue-handler. 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. |
7171
| `shutdown-timeout` | Interval in seconds within which in-flight requests must be processed, defaults to 15 seconds. A value of `0` bypasses waiting entirely. |
72+
| `initial-pipeline-transform` | A function that takes an `io.netty.channel.ChannelPipeline` object, which represents the connection before any default handlers are installed (and thus, before HTTP version negotiation), and modifies it. |
7273
7374
HTTP/1-specific options
7475
| Param key | Description |

src/aleph/http/server.clj

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
LastHttpContent)
4747
(io.netty.handler.ssl
4848
ApplicationProtocolNames
49-
SslContext)
49+
SslContext
50+
SslHandler)
5051
(io.netty.handler.stream
5152
ChunkedWriteHandler)
5253
(io.netty.util AsciiString)
@@ -633,40 +634,48 @@
633634

634635
(defn make-pipeline-builder
635636
"Returns a function that initializes a new server channel's pipeline."
636-
[handler {:keys [ssl? ^SslContext ssl-context use-h2c?] :as opts}]
637+
[handler {:keys [ssl?
638+
^SslContext ssl-context
639+
use-h2c?
640+
initial-pipeline-transform]
641+
:or {initial-pipeline-transform identity}
642+
:as opts}]
637643
(fn pipeline-builder*
638644
[^ChannelPipeline pipeline]
639645
(log/trace "pipeline-builder*" pipeline opts)
640646
(let [setup-opts (assoc opts
641647
:handler handler
642648
:server? true
643649
:pipeline pipeline)]
644-
(cond (and ssl? ssl-context)
645-
(let [ssl-handler (netty/ssl-handler (.channel pipeline) ssl-context)]
646-
(log/debug "Setting up secure HTTP server pipeline.")
647-
(log/debug "ALPN HTTP versions:" (mapv str (.nextProtocols ssl-context)))
648-
649-
(-> pipeline
650-
(.addLast "ssl-handler" ssl-handler)
651-
(.addLast "apn-handler"
652-
(ApnHandler.
653-
(fn setup-secure-pipeline
654-
[^ChannelPipeline pipeline protocol]
655-
(log/trace "setup-secure-pipeline: chosen protocol:" protocol)
656-
(when (nil? (.applicationProtocol ssl-handler))
657-
(log/debug (str "ALPN not used. Protocol " protocol " chosen by fallback.")))
658-
(cond (.equals ApplicationProtocolNames/HTTP_1_1 protocol)
659-
(setup-http1-pipeline setup-opts)
660-
661-
(.equals ApplicationProtocolNames/HTTP_2 protocol)
662-
(http2/setup-conn-pipeline setup-opts)
663-
664-
:else
665-
(let [msg (str "Unknown protocol: " protocol)
666-
e (IllegalStateException. msg)]
667-
(log/error e msg)
668-
(throw e))))
669-
apn-fallback-protocol)))
650+
(initial-pipeline-transform pipeline)
651+
(cond ssl?
652+
(do
653+
;; might be nil in manual-ssl? mode
654+
(when ssl-context
655+
(log/debug "Setting up secure HTTP server pipeline.")
656+
(log/debug "ALPN HTTP versions:" (mapv str (.nextProtocols ssl-context)))
657+
(.addLast pipeline "ssl-handler" (netty/ssl-handler (.channel pipeline) ssl-context)))
658+
(.addLast pipeline
659+
"apn-handler"
660+
(ApnHandler.
661+
(fn setup-secure-pipeline
662+
[^ChannelPipeline pipeline protocol]
663+
(log/trace "setup-secure-pipeline: chosen protocol:" protocol)
664+
(let [^SslHandler ssl-handler (.get pipeline SslHandler)]
665+
(when (nil? (.applicationProtocol ssl-handler))
666+
(log/debug (str "ALPN not used. Protocol " protocol " chosen by fallback.")))
667+
(cond (.equals ApplicationProtocolNames/HTTP_1_1 protocol)
668+
(setup-http1-pipeline setup-opts)
669+
670+
(.equals ApplicationProtocolNames/HTTP_2 protocol)
671+
(http2/setup-conn-pipeline setup-opts)
672+
673+
:else
674+
(let [msg (str "Unknown protocol: " protocol)
675+
e (IllegalStateException. msg)]
676+
(log/error e msg)
677+
(throw e)))))
678+
apn-fallback-protocol))
670679
pipeline)
671680

672681
use-h2c?
@@ -750,28 +759,32 @@
750759
opts (assoc opts :ssl-context ssl-context)
751760
http1-pipeline-transform (common/validate-http1-pipeline-transform opts)
752761
executor (setup-executor executor)
753-
continue-executor (setup-continue-executor executor continue-executor)
754-
pipeline-builder (make-pipeline-builder
755-
handler
756-
(assoc opts
757-
:executor executor
758-
:ssl? (or manual-ssl? (boolean ssl-context))
759-
:http1-pipeline-transform http1-pipeline-transform
760-
:continue-executor continue-executor))]
762+
continue-executor (setup-continue-executor executor continue-executor)]
761763

762764
(if (some #{:http2} http-versions)
763765
(when (and (not ssl-context)
764-
(not use-h2c?))
765-
(throw (IllegalArgumentException. "HTTP/2 requires ssl-context to be given or use-h2c? to be true.")))
766+
(not use-h2c?)
767+
(not manual-ssl?))
768+
(throw (IllegalArgumentException. "HTTP/2 requires passing an ssl-context or manual-ssl? true. Alternatively, pass use-h2c? true to disable TLS.")))
766769
(when use-h2c?
767770
(throw (IllegalArgumentException. "use-h2c? may only be true when HTTP/2 is enabled."))))
768771

769772
(when (and ssl-context
770773
use-h2c?)
771774
(throw (IllegalArgumentException. "use-h2c? must not be true when ssl-context is given.")))
772775

776+
(when (and ssl-context
777+
manual-ssl?)
778+
(throw (IllegalArgumentException. "manual-ssl? must not be true when ssl-context is given.")))
779+
773780
(netty/start-server
774-
{:pipeline-builder pipeline-builder
781+
{:pipeline-builder (make-pipeline-builder
782+
handler
783+
(assoc opts
784+
:executor executor
785+
:ssl? (or manual-ssl? (boolean ssl-context))
786+
:http1-pipeline-transform http1-pipeline-transform
787+
:continue-executor continue-executor))
775788
:bootstrap-transform bootstrap-transform
776789
:socket-address (if socket-address
777790
socket-address

test/aleph/http_test.clj

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
(:require
33
[aleph.flow :as flow]
44
[aleph.http :as http]
5+
[aleph.http.common :as common]
56
[aleph.http.core :as http.core]
67
[aleph.netty :as netty]
78
[aleph.resource-leak-detector]
@@ -518,6 +519,29 @@
518519
(is (= 200 (:status @(http-get "/"))))
519520
(is (some? @ssl-session))))))
520521

522+
523+
(defn test-manual-ssl-for-version [http-version]
524+
(testing (str "manual ssl with " http-version)
525+
(with-redefs [*use-tls-requests* true]
526+
(with-server (http/start-server string-handler
527+
(assoc http-server-options
528+
:manual-ssl? true
529+
:http-versions [http-version]
530+
:initial-pipeline-transform (fn [pipeline]
531+
(let [ssl-handler (netty/ssl-handler (.channel pipeline)
532+
(-> test-ssl/server-ssl-context-opts
533+
(common/ensure-consistent-alpn-config [http-version])
534+
(netty/coerce-ssl-server-context)))]
535+
(.addLast pipeline
536+
"ssl-handler"
537+
ssl-handler)))))
538+
(binding [*connection-options* {:http-versions [http-version]}]
539+
(is (= 200 (:status @(http-get "/")))))))))
540+
541+
(deftest test-manual-ssl
542+
(test-manual-ssl-for-version :http1)
543+
(test-manual-ssl-for-version :http2))
544+
521545
(deftest test-invalid-body
522546
(let [client-url "/"]
523547
(with-handler echo-handler
@@ -1449,7 +1473,7 @@
14491473
(let [result (try-start-server
14501474
{:http-versions [:http2]})]
14511475
(is (instance? IllegalArgumentException result))
1452-
(is (= "HTTP/2 requires ssl-context to be given or use-h2c? to be true." (ex-message result)))))
1476+
(is (= "HTTP/2 requires passing an ssl-context or manual-ssl? true. Alternatively, pass use-h2c? true to disable TLS." (ex-message result)))))
14531477

14541478
(testing "HTTP/2 without ssl-context but with h2c"
14551479
(let [result (try-start-server

0 commit comments

Comments
 (0)