diff options
author | Loïc Hoguin <[email protected]> | 2018-09-20 15:06:42 +0200 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2018-09-20 15:09:10 +0200 |
commit | baf0e420917ca1cb2806f8594a6cdb4710d2793d (patch) | |
tree | c8416139c514688bab05c86be3a5380476c7ca85 /src | |
parent | ea0296d1560557ce45cdfc197a0254fb15bd75f8 (diff) | |
download | gun-baf0e420917ca1cb2806f8594a6cdb4710d2793d.tar.gz gun-baf0e420917ca1cb2806f8594a6cdb4710d2793d.tar.bz2 gun-baf0e420917ca1cb2806f8594a6cdb4710d2793d.zip |
Use ALPN when proxying TLS connections using CONNECT
This fixes HTTP/2 over TLS connections.
The protocol destination option has been deprecated in favor
of a protocols option.
Diffstat (limited to 'src')
-rw-r--r-- | src/gun.erl | 44 | ||||
-rw-r--r-- | src/gun_http.erl | 31 |
2 files changed, 52 insertions, 23 deletions
diff --git a/src/gun.erl b/src/gun.erl index af576fb..9c92281 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -120,7 +120,8 @@ port := inet:port_number(), username => iodata(), password => iodata(), - protocol => http | http2, + protocol => http | http2, %% @todo Remove in Gun 2.0. + protocols => [http | http2], transport => tcp | tls, tls_opts => [ssl:connect_option()], tls_handshake_timeout => timeout() @@ -652,14 +653,9 @@ default_transport(443) -> tls; default_transport(_) -> tcp. transport_connect(State=#state{host=Host, port=Port, opts=Opts, transport=Transport=gun_tls}, Retries) -> - Protocols = [case P of - http -> <<"http/1.1">>; - http2 -> <<"h2">> - end || P <- maps:get(protocols, Opts, [http2, http])], - TransportOpts = [binary, {active, false}, - {alpn_advertised_protocols, Protocols}, - {client_preferred_next_protocols, {client, Protocols, <<"http/1.1">>}} - |maps:get(transport_opts, Opts, [])], + TransportOpts = [binary, {active, false}|ensure_alpn( + maps:get(protocols, Opts, [http2, http]), + maps:get(transport_opts, Opts, []))], case Transport:connect(Host, Port, TransportOpts, maps:get(connect_timeout, Opts, infinity)) of {ok, Socket} -> {Protocol, ProtoOptsKey} = case ssl:negotiated_protocol(Socket) of @@ -684,6 +680,16 @@ transport_connect(State=#state{host=Host, port=Port, opts=Opts, transport=Transp retry(State#state{last_error=Reason}, Retries) end. +ensure_alpn(Protocols0, TransportOpts) -> + Protocols = [case P of + http -> <<"http/1.1">>; + http2 -> <<"h2">> + end || P <- Protocols0], + [ + {alpn_advertised_protocols, Protocols}, + {client_preferred_next_protocols, {client, Protocols, <<"http/1.1">>}} + |TransportOpts]. + up(State=#state{owner=Owner, opts=Opts, transport=Transport}, Socket, Protocol, ProtoOptsKey) -> ProtoOpts = maps:get(ProtoOptsKey, Opts, #{}), ProtoState = Protocol:init(Owner, Socket, Transport, ProtoOpts), @@ -778,7 +784,25 @@ loop(State=#state{parent=Parent, owner=Owner, owner_ref=OwnerRef, ProtoState2 = Protocol:data(ProtoState, StreamRef, ReplyTo, IsFin, Data), loop(State#state{protocol_state=ProtoState2}); - {connect, ReplyTo, StreamRef, Destination, Headers} -> + {connect, ReplyTo, StreamRef, Destination0, Headers} -> + %% The protocol option has been deprecated in favor of the protocols option. + %% Nobody probably ended up using it, but let's not break the interface. + Destination1 = case Destination0 of + #{protocols := _} -> + Destination0; + #{protocol := DestProto} -> + Destination0#{protocols => [DestProto]}; + _ -> + Destination0 + end, + Destination = case Destination1 of + #{transport := tls} -> + Destination1#{tls_opts => ensure_alpn( + maps:get(protocols, Destination1, [http]), + maps:get(tls_opts, Destination1, []))}; + _ -> + Destination1 + end, ProtoState2 = Protocol:connect(ProtoState, StreamRef, ReplyTo, Destination, Headers), loop(State#state{protocol_state=ProtoState2}); {cancel, ReplyTo, StreamRef} -> diff --git a/src/gun_http.erl b/src/gun_http.erl index ee9d04f..c2b0ed6 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -221,29 +221,34 @@ handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion, State2 = end_stream(State#http_state{streams=[Stream|Tail]}), NewHost = maps:get(host, Destination), NewPort = maps:get(port, Destination), - DestProtocol = maps:get(protocol, Destination, http), case Destination of #{transport := tls} -> TLSOpts = maps:get(tls_opts, Destination, []), TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity), case gun_tls:connect(Socket, TLSOpts, TLSTimeout) of - {ok, TLSSocket} when DestProtocol =:= http2 -> - [{switch_transport, gun_tls, TLSSocket}, - {switch_protocol, gun_http2, State2}, - {origin, <<"https">>, NewHost, NewPort}]; {ok, TLSSocket} -> - [{state, State2#http_state{socket=TLSSocket, transport=gun_tls}}, - {switch_transport, gun_tls, TLSSocket}, - {origin, <<"https">>, NewHost, NewPort}]; + case ssl:negotiated_protocol(TLSSocket) of + {ok, <<"h2">>} -> + [{switch_transport, gun_tls, TLSSocket}, + {switch_protocol, gun_http2, State2}, + {origin, <<"https">>, NewHost, NewPort}]; + _ -> + [{state, State2#http_state{socket=TLSSocket, transport=gun_tls}}, + {switch_transport, gun_tls, TLSSocket}, + {origin, <<"https">>, NewHost, NewPort}] + end; Error -> Error end; - _ when DestProtocol =:= http2 -> - [{switch_protocol, gun_http2, State2}, - {origin, <<"http">>, NewHost, NewPort}]; _ -> - [{state, State2}, - {origin, <<"http">>, NewHost, NewPort}] + case maps:get(protocols, Destination, [http]) of + [http] -> + [{state, State2}, + {origin, <<"http">>, NewHost, NewPort}]; + [http2] -> + [{switch_protocol, gun_http2, State2}, + {origin, <<"http">>, NewHost, NewPort}] + end end; {_, _} when Status >= 100, Status =< 199 -> ReplyTo ! {gun_inform, self(), stream_ref(StreamRef), Status, Headers}, |