Skip to content
Open
46 changes: 27 additions & 19 deletions src/aleph/http.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
[aleph.http.websocket.common :as ws.common]
[aleph.http.websocket.server :as ws.server]
[aleph.netty :as netty]
[aleph.util :as util]
[clojure.string :as str]
[clojure.tools.logging :as log]
[manifold.deferred :as d]
Expand Down Expand Up @@ -100,20 +101,22 @@
will be errors, and a new connection must be created."
[^URI uri options middleware on-closed]
(let [scheme (.getScheme uri)
ssl? (= "https" scheme)]
(-> (client/http-connection
(InetSocketAddress/createUnresolved
(.getHost uri)
(int
(or
(when (pos? (.getPort uri)) (.getPort uri))
(if ssl? 443 80))))
ssl?
(if on-closed
(assoc options :on-closed on-closed)
options))

(d/chain' middleware))))
ssl? (= "https" scheme)
conn (client/http-connection
(InetSocketAddress/createUnresolved
(.getHost uri)
(int
(or
(when (pos? (.getPort uri)) (.getPort uri))
(if ssl? 443 80))))
ssl?
(if on-closed
(assoc options :on-closed on-closed)
options))]
(-> (d/chain' conn middleware)
(util/propagate-error conn
(fn [e]
(log/trace e "Terminated creation of HTTP connection"))))))

(def ^:private connection-stats-callbacks (atom #{}))

Expand Down Expand Up @@ -389,6 +392,12 @@
;; function.
(reset! dispose-conn! (fn [] (flow/dispose pool k conn)))

;; allow cancellation during connection establishment
(util/propagate-error result
(first conn)
(fn [e]
(log/trace e "Aborted connection acquisition")))

(if (realized? result)
;; to account for race condition between setting `dispose-conn!`
;; and putting `result` into error state for cancellation
Expand Down Expand Up @@ -456,11 +465,10 @@
(middleware/handle-redirects request req))))))))))))
req))]
(d/connect response result)
(d/catch' result
(fn [e]
(log/trace e "Request failed. Disposing of connection...")
(@dispose-conn!)
(d/error-deferred e)))
(util/on-error result
(fn [e]
(log/trace e "Request failed. Disposing of connection...")
(@dispose-conn!)))
result)))

(defn cancel-request!
Expand Down
239 changes: 126 additions & 113 deletions src/aleph/http/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[aleph.http.multipart :as multipart]
[aleph.http.websocket.client :as ws.client]
[aleph.netty :as netty]
[aleph.util :as util]
[clj-commons.byte-streams :as bs]
[clojure.tools.logging :as log]
[manifold.deferred :as d]
Expand Down Expand Up @@ -814,119 +815,131 @@
:logger logger
:pipeline-transform pipeline-transform))

ch-d (netty/create-client-chan
{:pipeline-builder pipeline-builder
:bootstrap-transform bootstrap-transform
:remote-address remote-address
:local-address local-address
:transport (netty/determine-transport transport epoll?)
:name-resolver name-resolver
:connect-timeout connect-timeout})]

(attach-on-close-handler ch-d on-closed)

(d/chain' ch-d
(fn setup-client
[^Channel ch]
(log/debug "Channel:" ch)

;; We know the SSL handshake must be complete because create-client wraps the
;; future with maybe-ssl-handshake-future, so we can get the negotiated
;; protocol, falling back to HTTP/1.1 by default.
(let [pipeline (.pipeline ch)
protocol (cond
ssl?
(or (-> pipeline
^SslHandler (.get ^Class SslHandler)
(.applicationProtocol))
ApplicationProtocolNames/HTTP_1_1) ; Not using ALPN, HTTP/2 isn't allowed

force-h2c?
(do
(log/info "Forcing HTTP/2 over cleartext. Be sure to do this only with servers you control.")
ApplicationProtocolNames/HTTP_2)

:else
ApplicationProtocolNames/HTTP_1_1) ; Not using SSL, HTTP/2 isn't allowed unless h2c requested
setup-opts (assoc opts
:authority authority
:ch ch
:server? false
:keep-alive? keep-alive?
:keep-alive?' keep-alive?'
:logger logger
:non-tun-proxy? non-tun-proxy?
:pipeline pipeline
:pipeline-transform pipeline-transform
:raw-stream? raw-stream?
:remote-address remote-address
:response-buffer-size response-buffer-size
:ssl-context ssl-context
:ssl? ssl?)]

(log/debug (str "Using HTTP protocol: " protocol)
{:authority authority
:ssl? ssl?
:force-h2c? force-h2c?})

;; can't use ApnHandler, because we need to coordinate with Manifold code
(let [http-req-handler
(cond (.equals ApplicationProtocolNames/HTTP_1_1 protocol)
(setup-http1-client setup-opts)

(.equals ApplicationProtocolNames/HTTP_2 protocol)
(do
(http2/setup-conn-pipeline setup-opts)
(http2-req-handler setup-opts))

:else
(do
(let [msg (str "Unknown protocol: " protocol)
e (IllegalStateException. msg)]
(log/error e msg)
(netty/close ch)
(throw e))))]

;; Both Netty and Aleph are set up, unpause the pipeline
(when (.get pipeline "pause-handler")
(log/debug "Unpausing pipeline")
(.remove pipeline "pause-handler"))

(fn http-req-fn
[req]
(log/trace "http-req-fn fired")
(log/debug "client request:" (pr-str req))

;; If :aleph/close is set in the req, closes the channel and
;; returns a deferred containing the result.
(if (or (contains? req :aleph/close)
(contains? req ::close))
(-> ch (netty/close) (netty/wrap-future))

(let [t0 (System/nanoTime)
;; I suspect the below is an error for http1
;; since the shared handler might not match.
;; Should work for HTTP2, though
raw-stream? (get req :raw-stream? raw-stream?)]

(if (or (not (.isActive ch))
(not (.isOpen ch)))

(d/error-deferred
(ex-info "Channel is inactive/closed."
{:req req
:ch ch
:open? (.isOpen ch)
:active? (.isActive ch)}))

(-> (http-req-handler req)
(d/chain' (rsp-handler
{:ch ch
:keep-alive? keep-alive? ; why not keep-alive?'
:raw-stream? raw-stream?
:req req
:response-buffer-size response-buffer-size
:t0 t0})))))))))))))
ch-d (doto (netty/create-client-chan
{:pipeline-builder pipeline-builder
:bootstrap-transform bootstrap-transform
:remote-address remote-address
:local-address local-address
:transport (netty/determine-transport transport epoll?)
:name-resolver name-resolver
:connect-timeout connect-timeout})
(attach-on-close-handler on-closed))

close-ch! (atom (fn []))
result (d/deferred)

conn (d/chain' ch-d
(fn setup-client
[^Channel ch]
(log/debug "Channel:" ch)
(reset! close-ch! (fn [] @(-> (netty/close ch) (netty/wrap-future))))
(if (realized? result)
;; Account for race condition between setting `close-ch!` and putting
;; `result` into error state for cancellation
(@close-ch!)
;; We know the SSL handshake must be complete because create-client wraps the
;; future with maybe-ssl-handshake-future, so we can get the negotiated
;; protocol, falling back to HTTP/1.1 by default.
(let [pipeline (.pipeline ch)
protocol (cond
ssl?
(or (-> pipeline
^SslHandler (.get ^Class SslHandler)
(.applicationProtocol))
ApplicationProtocolNames/HTTP_1_1) ; Not using ALPN, HTTP/2 isn't allowed

force-h2c?
(do
(log/info "Forcing HTTP/2 over cleartext. Be sure to do this only with servers you control.")
ApplicationProtocolNames/HTTP_2)

:else
ApplicationProtocolNames/HTTP_1_1) ; Not using SSL, HTTP/2 isn't allowed unless h2c requested
setup-opts (assoc opts
:authority authority
:ch ch
:server? false
:keep-alive? keep-alive?
:keep-alive?' keep-alive?'
:logger logger
:non-tun-proxy? non-tun-proxy?
:pipeline pipeline
:pipeline-transform pipeline-transform
:raw-stream? raw-stream?
:remote-address remote-address
:response-buffer-size response-buffer-size
:ssl-context ssl-context
:ssl? ssl?)]

(log/debug (str "Using HTTP protocol: " protocol)
{:authority authority
:ssl? ssl?
:force-h2c? force-h2c?})

;; can't use ApnHandler, because we need to coordinate with Manifold code
(let [http-req-handler
(cond (.equals ApplicationProtocolNames/HTTP_1_1 protocol)
(setup-http1-client setup-opts)

(.equals ApplicationProtocolNames/HTTP_2 protocol)
(do
(http2/setup-conn-pipeline setup-opts)
(http2-req-handler setup-opts))

:else
(do
(let [msg (str "Unknown protocol: " protocol)
e (IllegalStateException. msg)]
(log/error e msg)
(netty/close ch)
(throw e))))]

;; Both Netty and Aleph are set up, unpause the pipeline
(when (.get pipeline "pause-handler")
(log/debug "Unpausing pipeline")
(.remove pipeline "pause-handler"))

(fn http-req-fn
[req]
(log/trace "http-req-fn fired")
(log/debug "client request:" (pr-str req))

;; If :aleph/close is set in the req, closes the channel and
;; returns a deferred containing the result.
(if (or (contains? req :aleph/close)
(contains? req ::close))
(-> ch (netty/close) (netty/wrap-future))

(let [t0 (System/nanoTime)
;; I suspect the below is an error for http1
;; since the shared handler might not match.
;; Should work for HTTP2, though
raw-stream? (get req :raw-stream? raw-stream?)]

(if (or (not (.isActive ch))
(not (.isOpen ch)))

(d/error-deferred
(ex-info "Channel is inactive/closed."
{:req req
:ch ch
:open? (.isOpen ch)
:active? (.isActive ch)}))

(-> (http-req-handler req)
(d/chain' (rsp-handler
{:ch ch
:keep-alive? keep-alive? ; why not keep-alive?'
:raw-stream? raw-stream?
:req req
:response-buffer-size response-buffer-size
:t0 t0}))))))))))))]
(d/connect conn result)
(util/propagate-error result
ch-d
(fn [e]
(log/trace e "Closing HTTP connection channel")
(@close-ch!)))))



Expand Down
Loading