negotiate.ex (3792B)
1 defmodule Mint.Negotiate do 2 @moduledoc false 3 4 import Mint.Core.Util 5 6 alias Mint.{ 7 HTTP1, 8 HTTP2, 9 TransportError 10 } 11 12 @default_protocols [:http1, :http2] 13 @transport_opts [alpn_advertised_protocols: ["http/1.1", "h2"]] 14 15 def connect(scheme, address, port, opts \\ []) do 16 {protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols) 17 18 case Enum.sort(protocols) do 19 [:http1] -> 20 HTTP1.connect(scheme, address, port, opts) 21 22 [:http2] -> 23 HTTP2.connect(scheme, address, port, opts) 24 25 [:http1, :http2] -> 26 transport_connect(scheme, address, port, opts) 27 end 28 end 29 30 def upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) do 31 {protocols, opts} = Keyword.pop(opts, :protocols, @default_protocols) 32 33 case Enum.sort(protocols) do 34 [:http1] -> 35 HTTP1.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) 36 37 [:http2] -> 38 HTTP2.upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) 39 40 [:http1, :http2] -> 41 transport_upgrade(proxy_scheme, transport_state, scheme, hostname, port, opts) 42 end 43 end 44 45 def initiate(transport, transport_state, hostname, port, opts), 46 do: alpn_negotiate(transport, transport_state, hostname, port, opts) 47 48 defp transport_connect(:http, address, port, opts) do 49 # HTTP1 upgrade is not supported 50 HTTP1.connect(:http, address, port, opts) 51 end 52 53 defp transport_connect(:https, address, port, opts) do 54 connect_negotiate(:https, address, port, opts) 55 end 56 57 defp connect_negotiate(scheme, address, port, opts) do 58 transport = scheme_to_transport(scheme) 59 hostname = Mint.Core.Util.hostname(opts, address) 60 61 transport_opts = 62 opts 63 |> Keyword.get(:transport_opts, []) 64 |> Keyword.merge(@transport_opts) 65 |> Keyword.put(:hostname, hostname) 66 67 with {:ok, transport_state} <- transport.connect(address, port, transport_opts) do 68 alpn_negotiate(scheme, transport_state, hostname, port, opts) 69 end 70 end 71 72 defp transport_upgrade( 73 proxy_scheme, 74 transport_state, 75 :http, 76 hostname, 77 port, 78 opts 79 ) do 80 # HTTP1 upgrade is not supported 81 HTTP1.upgrade(proxy_scheme, transport_state, :http, hostname, port, opts) 82 end 83 84 defp transport_upgrade( 85 proxy_scheme, 86 transport_state, 87 :https, 88 hostname, 89 port, 90 opts 91 ) do 92 connect_upgrade(proxy_scheme, transport_state, :https, hostname, port, opts) 93 end 94 95 defp connect_upgrade(proxy_scheme, transport_state, new_scheme, hostname, port, opts) do 96 transport = scheme_to_transport(new_scheme) 97 98 transport_opts = 99 opts 100 |> Keyword.get(:transport_opts, []) 101 |> Keyword.merge(@transport_opts) 102 103 case transport.upgrade(transport_state, proxy_scheme, hostname, port, transport_opts) do 104 {:ok, transport_state} -> 105 alpn_negotiate(new_scheme, transport_state, hostname, port, opts) 106 107 {:error, reason} -> 108 {:error, %TransportError{reason: reason}} 109 end 110 end 111 112 defp alpn_negotiate(scheme, socket, hostname, port, opts) do 113 transport = scheme_to_transport(scheme) 114 115 case transport.negotiated_protocol(socket) do 116 {:ok, "http/1.1"} -> 117 HTTP1.initiate(scheme, socket, hostname, port, opts) 118 119 {:ok, "h2"} -> 120 HTTP2.initiate(scheme, socket, hostname, port, opts) 121 122 {:error, %TransportError{reason: :protocol_not_negotiated}} -> 123 # Assume HTTP1 if ALPN is not supported 124 HTTP1.initiate(scheme, socket, hostname, port, opts) 125 126 {:ok, protocol} -> 127 {:error, %TransportError{reason: {:bad_alpn_protocol, protocol}}} 128 129 {:error, %TransportError{} = error} -> 130 {:error, error} 131 end 132 end 133 end