changed
CHANGELOG.md
|
@@ -1,5 +1,22 @@
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+ ## v1.6.0
|
4
|
+
|
5
|
+ ### New features
|
6
|
+
|
7
|
+ * Add `:case_sensitive_headers` option to `Mint.HTTP1.connect/4`.
|
8
|
+ * Add `:inet4` option to `Mint.HTTP.connect/4`.
|
9
|
+
|
10
|
+ ### Bug fixes and improvements
|
11
|
+
|
12
|
+ * Require Elixir 1.11+.
|
13
|
+ * Add `match_fun` clause to deal with IP addresses in TLS handshake.
|
14
|
+ * Optimize creation of HTTP/2 requests.
|
15
|
+ * Fix a compilation warning (unused `set_flag/2` function).
|
16
|
+ * Improve performance of downcasing headers.
|
17
|
+ * Deprecate `:read_write` option in `Mint.HTTP.open?/2`.
|
18
|
+ * Improve performance of checking for the CAStore library.
|
19
|
+
|
3
20
|
## v1.5.2
|
4
21
|
|
5
22
|
### Bug fixes and improvements
|
changed
hex_metadata.config
|
@@ -1,8 +1,8 @@
|
1
1
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-mint/mint">>}]}.
|
2
2
|
{<<"name">>,<<"mint">>}.
|
3
|
- {<<"version">>,<<"1.5.2">>}.
|
3
|
+ {<<"version">>,<<"1.6.0">>}.
|
4
4
|
{<<"description">>,<<"Small and composable HTTP client.">>}.
|
5
|
- {<<"elixir">>,<<"~> 1.10">>}.
|
5
|
+ {<<"elixir">>,<<"~> 1.11">>}.
|
6
6
|
{<<"app">>,<<"mint">>}.
|
7
7
|
{<<"licenses">>,[<<"Apache-2.0">>]}.
|
8
8
|
{<<"requirements">>,
|
|
@@ -14,7 +14,7 @@
|
14
14
|
[{<<"name">>,<<"hpax">>},
|
15
15
|
{<<"app">>,<<"hpax">>},
|
16
16
|
{<<"optional">>,false},
|
17
|
- {<<"requirement">>,<<"~> 0.1.1">>},
|
17
|
+ {<<"requirement">>,<<"~> 0.1.1 or ~> 0.2.0">>},
|
18
18
|
{<<"repository">>,<<"hexpm">>}]]}.
|
19
19
|
{<<"files">>,
|
20
20
|
[<<"lib">>,<<"lib/mint">>,<<"lib/mint/http1">>,
|
|
@@ -23,8 +23,9 @@
|
23
23
|
<<"lib/mint/tunnel_proxy.ex">>,<<"lib/mint/core">>,
|
24
24
|
<<"lib/mint/core/util.ex">>,<<"lib/mint/core/transport">>,
|
25
25
|
<<"lib/mint/core/transport/ssl.ex">>,<<"lib/mint/core/transport/tcp.ex">>,
|
26
|
- <<"lib/mint/core/transport.ex">>,<<"lib/mint/core/conn.ex">>,
|
27
|
- <<"lib/mint/types.ex">>,<<"lib/mint/http_error.ex">>,<<"lib/mint/http2">>,
|
26
|
+ <<"lib/mint/core/transport.ex">>,<<"lib/mint/core/headers.ex">>,
|
27
|
+ <<"lib/mint/core/conn.ex">>,<<"lib/mint/types.ex">>,
|
28
|
+ <<"lib/mint/http_error.ex">>,<<"lib/mint/http2">>,
|
28
29
|
<<"lib/mint/http2/frame.ex">>,<<"lib/mint/negotiate.ex">>,
|
29
30
|
<<"lib/mint/http1.ex">>,<<"lib/mint/application.ex">>,
|
30
31
|
<<"lib/mint/http.ex">>,<<"lib/mint/unsafe_proxy.ex">>,
|
changed
lib/mint/core/conn.ex
|
@@ -13,7 +13,7 @@ defmodule Mint.Core.Conn do
|
13
13
|
keyword()
|
14
14
|
) :: {:ok, conn()} | {:error, Types.error()}
|
15
15
|
|
16
|
- @callback open?(conn(), :read | :write | :read_write) :: boolean()
|
16
|
+ @callback open?(conn(), :read | :write) :: boolean()
|
17
17
|
|
18
18
|
@callback close(conn()) :: {:ok, conn()}
|
added
lib/mint/core/headers.ex
|
@@ -0,0 +1,136 @@
|
1
|
+ defmodule Mint.Core.Headers do
|
2
|
+ @moduledoc false
|
3
|
+
|
4
|
+ @type canonical() ::
|
5
|
+ {original_name :: String.t(), canonical_name :: String.t(), value :: String.t()}
|
6
|
+ @type raw() :: {original_name :: String.t(), value :: String.t()}
|
7
|
+
|
8
|
+ @unallowed_trailers MapSet.new([
|
9
|
+ "content-encoding",
|
10
|
+ "content-length",
|
11
|
+ "content-range",
|
12
|
+ "content-type",
|
13
|
+ "trailer",
|
14
|
+ "transfer-encoding",
|
15
|
+
|
16
|
+ # Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1)
|
17
|
+ "cache-control",
|
18
|
+ "expect",
|
19
|
+ "host",
|
20
|
+ "max-forwards",
|
21
|
+ "pragma",
|
22
|
+ "range",
|
23
|
+ "te",
|
24
|
+
|
25
|
+ # Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2)
|
26
|
+ "if-match",
|
27
|
+ "if-none-match",
|
28
|
+ "if-modified-since",
|
29
|
+ "if-unmodified-since",
|
30
|
+ "if-range",
|
31
|
+
|
32
|
+ # Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3)
|
33
|
+ "authorization",
|
34
|
+ "proxy-authenticate",
|
35
|
+ "proxy-authorization",
|
36
|
+ "www-authenticate",
|
37
|
+
|
38
|
+ # Cookie management (https://tools.ietf.org/html/rfc6265)
|
39
|
+ "cookie",
|
40
|
+ "set-cookie",
|
41
|
+
|
42
|
+ # Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1)
|
43
|
+ "age",
|
44
|
+ "cache-control",
|
45
|
+ "expires",
|
46
|
+ "date",
|
47
|
+ "location",
|
48
|
+ "retry-after",
|
49
|
+ "vary",
|
50
|
+ "warning"
|
51
|
+ ])
|
52
|
+
|
53
|
+ @spec from_raw([raw()]) :: [canonical()]
|
54
|
+ def from_raw(headers) do
|
55
|
+ Enum.map(headers, fn {name, value} -> {name, lower_raw(name), value} end)
|
56
|
+ end
|
57
|
+
|
58
|
+ @spec to_raw([canonical()], boolean()) :: [raw()]
|
59
|
+ def to_raw(headers, _case_sensitive = true) do
|
60
|
+ Enum.map(headers, fn {name, _canonical_name, value} -> {name, value} end)
|
61
|
+ end
|
62
|
+
|
63
|
+ def to_raw(headers, _case_sensitive = false) do
|
64
|
+ Enum.map(headers, fn {_name, canonical_name, value} ->
|
65
|
+ {canonical_name, value}
|
66
|
+ end)
|
67
|
+ end
|
68
|
+
|
69
|
+ @spec find([canonical()], String.t()) :: {String.t(), String.t()} | nil
|
70
|
+ def find(headers, name) do
|
71
|
+ case List.keyfind(headers, name, 1) do
|
72
|
+ nil -> nil
|
73
|
+ {name, _canonical_name, value} -> {name, value}
|
74
|
+ end
|
75
|
+ end
|
76
|
+
|
77
|
+ @spec replace([canonical()], String.t(), String.t(), String.t()) ::
|
78
|
+ [canonical()]
|
79
|
+ def replace(headers, new_name, canonical_name, value) do
|
80
|
+ List.keyreplace(headers, canonical_name, 1, {new_name, canonical_name, value})
|
81
|
+ end
|
82
|
+
|
83
|
+ @spec has?([canonical()], String.t()) :: boolean()
|
84
|
+ def has?(headers, name) do
|
85
|
+ List.keymember?(headers, name, 1)
|
86
|
+ end
|
87
|
+
|
88
|
+ @spec put_new([canonical()], String.t(), String.t(), String.t() | nil) ::
|
89
|
+ [canonical()]
|
90
|
+ def put_new(headers, _name, _canonical_name, nil) do
|
91
|
+ headers
|
92
|
+ end
|
93
|
+
|
94
|
+ def put_new(headers, name, canonical_name, value) do
|
95
|
+ if List.keymember?(headers, canonical_name, 1) do
|
96
|
+ headers
|
97
|
+ else
|
98
|
+ [{name, canonical_name, value} | headers]
|
99
|
+ end
|
100
|
+ end
|
101
|
+
|
102
|
+ @spec put_new([canonical()], String.t(), String.t(), (-> String.t())) ::
|
103
|
+ [canonical()]
|
104
|
+ def put_new_lazy(headers, name, canonical_name, fun) do
|
105
|
+ if List.keymember?(headers, canonical_name, 1) do
|
106
|
+ headers
|
107
|
+ else
|
108
|
+ [{name, canonical_name, fun.()} | headers]
|
109
|
+ end
|
110
|
+ end
|
111
|
+
|
112
|
+ @spec find_unallowed_trailer([canonical()]) :: String.t() | nil
|
113
|
+ def find_unallowed_trailer(headers) do
|
114
|
+ Enum.find_value(headers, fn
|
115
|
+ {raw_name, canonical_name, _value} ->
|
116
|
+ if canonical_name in @unallowed_trailers do
|
117
|
+ raw_name
|
118
|
+ end
|
119
|
+ end)
|
120
|
+ end
|
121
|
+
|
122
|
+ @spec remove_unallowed_trailer([raw()]) :: [raw()]
|
123
|
+ def remove_unallowed_trailer(headers) do
|
124
|
+ Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailers end)
|
125
|
+ end
|
126
|
+
|
127
|
+ @spec lower_raw(String.t()) :: String.t()
|
128
|
+ def lower_raw(name) do
|
129
|
+ String.downcase(name, :ascii)
|
130
|
+ end
|
131
|
+
|
132
|
+ @spec lower_raws([raw()]) :: [raw()]
|
133
|
+ def lower_raws(headers) do
|
134
|
+ Enum.map(headers, fn {name, value} -> {lower_raw(name), value} end)
|
135
|
+ end
|
136
|
+ end
|
changed
lib/mint/core/transport/ssl.ex
|
@@ -323,6 +323,7 @@ defmodule Mint.Core.Transport.SSL do
|
323
323
|
|
324
324
|
defp connect(address, hostname, port, opts) do
|
325
325
|
timeout = Keyword.get(opts, :timeout, @default_timeout)
|
326
|
+ inet4? = Keyword.get(opts, :inet4, true)
|
326
327
|
inet6? = Keyword.get(opts, :inet6, false)
|
327
328
|
|
328
329
|
opts = ssl_opts(String.to_charlist(hostname), opts)
|
|
@@ -334,8 +335,11 @@ defmodule Mint.Core.Transport.SSL do
|
334
335
|
{:ok, sslsocket} ->
|
335
336
|
{:ok, sslsocket}
|
336
337
|
|
337
|
- _error ->
|
338
|
+ _error when inet4? ->
|
338
339
|
wrap_err(:ssl.connect(address, port, opts, timeout))
|
340
|
+
|
341
|
+ error ->
|
342
|
+ wrap_err(error)
|
339
343
|
end
|
340
344
|
else
|
341
345
|
# Use the defaults provided by ssl/gen_tcp.
|
|
@@ -428,7 +432,7 @@ defmodule Mint.Core.Transport.SSL do
|
428
432
|
default_ssl_opts(hostname)
|
429
433
|
|> Keyword.merge(opts)
|
430
434
|
|> Keyword.merge(@transport_opts)
|
431
|
- |> Keyword.drop([:timeout, :inet6])
|
435
|
+ |> Keyword.drop([:timeout, :inet4, :inet6])
|
432
436
|
|> add_verify_opts(hostname)
|
433
437
|
|> remove_incompatible_ssl_opts()
|
434
438
|
|> add_ciphers_opt()
|
|
@@ -509,6 +513,20 @@ defmodule Mint.Core.Transport.SSL do
|
509
513
|
end
|
510
514
|
end
|
511
515
|
|
516
|
+ # Workaround for a bug that was fixed in OTP 27:
|
517
|
+ # Before OTP 27 when connecting to an IP address and the server offers a
|
518
|
+ # certificate with its IP address in the "subject alternate names" extension,
|
519
|
+ # the TLS handshake fails with a `{:bad_cert, :hostname_check_failed}`.
|
520
|
+ # This clause can be removed once we depend on OTP 27+.
|
521
|
+ defp match_fun({:dns_id, hostname}, {:iPAddress, ip}) do
|
522
|
+ with {:ok, ip_tuple} <- :inet.parse_address(hostname),
|
523
|
+ ^ip <- Tuple.to_list(ip_tuple) do
|
524
|
+ true
|
525
|
+ else
|
526
|
+ _ -> :default
|
527
|
+ end
|
528
|
+ end
|
529
|
+
|
512
530
|
defp match_fun(_reference, _presented), do: :default
|
513
531
|
|
514
532
|
defp domain_without_host([]), do: []
|
|
@@ -684,8 +702,12 @@ defmodule Mint.Core.Transport.SSL do
|
684
702
|
defp blocked_cipher?({kex, cipher, _mac, prf}), do: blocked_cipher?({kex, cipher, prf})
|
685
703
|
defp blocked_cipher?({_kex, _cipher, _prf} = suite), do: suite in @blocked_ciphers
|
686
704
|
|
687
|
- defp raise_on_missing_castore! do
|
688
|
- Code.ensure_loaded?(CAStore) ||
|
705
|
+ if Code.ensure_loaded?(CAStore) do
|
706
|
+ defp raise_on_missing_castore! do
|
707
|
+ :ok
|
708
|
+ end
|
709
|
+ else
|
710
|
+ defp raise_on_missing_castore! do
|
689
711
|
raise """
|
690
712
|
default CA trust store not available; please add `:castore` to your project's \
|
691
713
|
dependencies or specify the trust store using the :cacertfile/:cacerts option \
|
|
@@ -696,6 +718,7 @@ defmodule Mint.Core.Transport.SSL do
|
696
718
|
|
697
719
|
See: https://www.erlang.org/blog/my-otp-25-highlights/#ca-certificates-can-be-fetched-from-the-os-standard-place
|
698
720
|
"""
|
721
|
+ end
|
699
722
|
end
|
700
723
|
|
701
724
|
defp wrap_err({:error, reason}), do: {:error, wrap_error(reason)}
|
changed
lib/mint/core/transport/tcp.ex
|
@@ -19,12 +19,13 @@ defmodule Mint.Core.Transport.TCP do
|
19
19
|
opts = Keyword.delete(opts, :hostname)
|
20
20
|
|
21
21
|
timeout = Keyword.get(opts, :timeout, @default_timeout)
|
22
|
+ inet4? = Keyword.get(opts, :inet4, true)
|
22
23
|
inet6? = Keyword.get(opts, :inet6, false)
|
23
24
|
|
24
25
|
opts =
|
25
26
|
opts
|
26
27
|
|> Keyword.merge(@transport_opts)
|
27
|
- |> Keyword.drop([:alpn_advertised_protocols, :timeout, :inet6])
|
28
|
+ |> Keyword.drop([:alpn_advertised_protocols, :timeout, :inet4, :inet6])
|
28
29
|
|
29
30
|
if inet6? do
|
30
31
|
# Try inet6 first, then fall back to the defaults provided by
|
|
@@ -33,8 +34,11 @@ defmodule Mint.Core.Transport.TCP do
|
33
34
|
{:ok, socket} ->
|
34
35
|
{:ok, socket}
|
35
36
|
|
36
|
- _error ->
|
37
|
+ _error when inet4? ->
|
37
38
|
wrap_err(:gen_tcp.connect(address, port, opts, timeout))
|
39
|
+
|
40
|
+ error ->
|
41
|
+ wrap_err(error)
|
38
42
|
end
|
39
43
|
else
|
40
44
|
# Use the defaults provided by gen_tcp.
|
changed
lib/mint/core/util.ex
|
@@ -3,51 +3,6 @@ defmodule Mint.Core.Util do
|
3
3
|
|
4
4
|
alias Mint.Types
|
5
5
|
|
6
|
- @unallowed_trailer_headers MapSet.new([
|
7
|
- "content-encoding",
|
8
|
- "content-length",
|
9
|
- "content-range",
|
10
|
- "content-type",
|
11
|
- "trailer",
|
12
|
- "transfer-encoding",
|
13
|
-
|
14
|
- # Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1)
|
15
|
- "cache-control",
|
16
|
- "expect",
|
17
|
- "host",
|
18
|
- "max-forwards",
|
19
|
- "pragma",
|
20
|
- "range",
|
21
|
- "te",
|
22
|
-
|
23
|
- # Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2)
|
24
|
- "if-match",
|
25
|
- "if-none-match",
|
26
|
- "if-modified-since",
|
27
|
- "if-unmodified-since",
|
28
|
- "if-range",
|
29
|
-
|
30
|
- # Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3)
|
31
|
- "authorization",
|
32
|
- "proxy-authenticate",
|
33
|
- "proxy-authorization",
|
34
|
- "www-authenticate",
|
35
|
-
|
36
|
- # Cookie management (https://tools.ietf.org/html/rfc6265)
|
37
|
- "cookie",
|
38
|
- "set-cookie",
|
39
|
-
|
40
|
- # Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1)
|
41
|
- "age",
|
42
|
- "cache-control",
|
43
|
- "expires",
|
44
|
- "date",
|
45
|
- "location",
|
46
|
- "retry-after",
|
47
|
- "vary",
|
48
|
- "warning"
|
49
|
- ])
|
50
|
-
|
51
6
|
@spec hostname(keyword(), String.t()) :: String.t()
|
52
7
|
def hostname(opts, address) when is_list(opts) do
|
53
8
|
case Keyword.fetch(opts, :hostname) do
|
|
@@ -107,31 +62,10 @@ defmodule Mint.Core.Util do
|
107
62
|
end
|
108
63
|
end
|
109
64
|
|
110
|
- # Lowercases an ASCII string more efficiently than
|
111
|
- # String.downcase/1.
|
112
|
- @spec downcase_ascii(String.t()) :: String.t()
|
113
|
- def downcase_ascii(string) do
|
114
|
- for <<char <- string>>, do: <<downcase_ascii_char(char)>>, into: ""
|
115
|
- end
|
116
|
-
|
117
|
- @spec downcase_ascii_char(byte()) :: byte()
|
118
|
- def downcase_ascii_char(char) when char in ?A..?Z, do: char + 32
|
119
|
- def downcase_ascii_char(char) when char in 0..127, do: char
|
120
|
-
|
121
65
|
# If the buffer is empty, reusing the incoming data saves
|
122
66
|
# a potentially large allocation of memory.
|
123
67
|
# This should be fixed in a subsequent OTP release.
|
124
68
|
@spec maybe_concat(binary(), binary()) :: binary()
|
125
69
|
def maybe_concat(<<>>, data), do: data
|
126
70
|
def maybe_concat(buffer, data) when is_binary(buffer), do: buffer <> data
|
127
|
-
|
128
|
- @spec find_unallowed_trailer_header(Types.headers()) :: {String.t(), String.t()} | nil
|
129
|
- def find_unallowed_trailer_header(headers) do
|
130
|
- Enum.find(headers, fn {name, _value} -> name in @unallowed_trailer_headers end)
|
131
|
- end
|
132
|
-
|
133
|
- @spec remove_unallowed_trailer_headers(Types.headers()) :: Types.headers()
|
134
|
- def remove_unallowed_trailer_headers(headers) do
|
135
|
- Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailer_headers end)
|
136
|
- end
|
137
71
|
end
|
changed
lib/mint/http.ex
|
@@ -121,22 +121,13 @@ defmodule Mint.HTTP do
|
121
121
|
> gets logged by using the `Logger` API and Erlang's `:logger` module.
|
122
122
|
"""
|
123
123
|
|
124
|
- import Mint.Core.Util
|
125
|
-
|
126
124
|
alias Mint.{Types, TunnelProxy, UnsafeProxy}
|
127
|
- alias Mint.Core.Transport
|
125
|
+ alias Mint.Core.{Transport, Util}
|
128
126
|
|
129
127
|
@behaviour Mint.Core.Conn
|
130
128
|
|
131
129
|
@opaque t() :: Mint.HTTP1.t() | Mint.HTTP2.t()
|
132
130
|
|
133
|
- # TODO: Remove once we depend on Elixir 1.11+, which defines is_struct/2
|
134
|
- if not macro_exported?(Kernel, :is_struct, 2) do
|
135
|
- defguardp is_struct(struct, module)
|
136
|
- when is_map(struct) and is_atom(module) and is_map_key(struct, :__struct__) and
|
137
|
- :erlang.map_get(:__struct__, struct) == module
|
138
|
- end
|
139
|
-
|
140
131
|
defguardp is_data_message(message)
|
141
132
|
when elem(message, 0) in [:ssl, :tcp] and tuple_size(message) == 3
|
142
133
|
|
|
@@ -301,6 +292,13 @@ defmodule Mint.HTTP do
|
301
292
|
seconds), and may be overridden by the caller. Set to `:infinity` to
|
302
293
|
disable the connect timeout.
|
303
294
|
|
295
|
+ * `:inet6` - if set to `true` enables IPv6 connection. Defaults to `false`
|
296
|
+ and may be overridden by the caller.
|
297
|
+
|
298
|
+ * `:inet4` - if set to `true` falls back to IPv4 if IPv6 connection fails.
|
299
|
+ Defaults to `true` and may be overridden by the caller. *Available since
|
300
|
+ v1.6.0*.
|
301
|
+
|
304
302
|
Options for `:https` only:
|
305
303
|
|
306
304
|
* `:alpn_advertised_protocols` - managed by Mint. Cannot be overridden.
|
|
@@ -410,7 +408,7 @@ defmodule Mint.HTTP do
|
410
408
|
def connect(scheme, address, port, opts \\ []) do
|
411
409
|
case Keyword.fetch(opts, :proxy) do
|
412
410
|
{:ok, {proxy_scheme, proxy_address, proxy_port, proxy_opts}} ->
|
413
|
- case scheme_to_transport(scheme) do
|
411
|
+ case Util.scheme_to_transport(scheme) do
|
414
412
|
Transport.TCP ->
|
415
413
|
proxy = {proxy_scheme, proxy_address, proxy_port}
|
416
414
|
host = {scheme, address, port}
|
|
@@ -505,16 +503,16 @@ defmodule Mint.HTTP do
|
505
503
|
either open, or closed (for both reading and writing). In HTTP/2, the connection can be closed only
|
506
504
|
for writing but not for reading, meaning that you cannot send any more data to the
|
507
505
|
server but you can still receive data from the server. In this case, `Mint.HTTP.open?(conn, :read)`
|
508
|
- would return `true` but `Mint.HTTP.open?(conn, :read_write)` would return `false`.
|
506
|
+ would return `true` but `Mint.HTTP.open?(conn, :write)` would return `false`.
|
509
507
|
See the "Closed connection" section in the module documentation of `Mint.HTTP2`.
|
510
508
|
|
511
509
|
If a connection is *completely closed* (that is, `Mint.HTTP.open?(conn, :read)` returns `false`),
|
512
510
|
it has become useless and you should get rid of it. If you still need a connection
|
513
511
|
to the server, start a new connection with `connect/4`.
|
514
512
|
|
515
|
- > #### The default value of `type` is `:read_write` {: .warning}
|
513
|
+ > #### The default value of `type` is `:write` {: .warning}
|
516
514
|
>
|
517
|
- > With the default value of `type` being `:read_write`, a call to
|
515
|
+ > With the default value of `type` being `:write`, a call to
|
518
516
|
> `Mint.HTTP.open?(conn)` will return `false` if `conn` was closed for writing
|
519
517
|
> but is still open for reading. If you need to make sure the connection is
|
520
518
|
> completely closed, check that `Mint.HTTP.open?(conn, :read)` returns `false`.
|
|
@@ -527,8 +525,8 @@ defmodule Mint.HTTP do
|
527
525
|
|
528
526
|
"""
|
529
527
|
@impl true
|
530
|
- @spec open?(t(), :read | :write | :read_write) :: boolean()
|
531
|
- def open?(conn, type \\ :read_write), do: conn_module(conn).open?(conn, type)
|
528
|
+ @spec open?(t(), :read | :write) :: boolean()
|
529
|
+ def open?(conn, type \\ :write), do: conn_module(conn).open?(conn, type)
|
532
530
|
|
533
531
|
@doc """
|
534
532
|
Sends a request to the connected server.
|
changed
lib/mint/http1.ex
|
@@ -13,9 +13,8 @@ defmodule Mint.HTTP1 do
|
13
13
|
how to use the data structure and client architecture, see `Mint`.
|
14
14
|
"""
|
15
15
|
|
16
|
- import Mint.Core.Util
|
16
|
+ alias Mint.Core.{Headers, Util}
|
17
17
|
|
18
|
- alias Mint.Core.Util
|
19
18
|
alias Mint.HTTP1.{Parse, Request, Response}
|
20
19
|
alias Mint.{HTTPError, TransportError, Types}
|
21
20
|
|
|
@@ -93,6 +92,7 @@ defmodule Mint.HTTP1 do
|
93
92
|
:transport,
|
94
93
|
:mode,
|
95
94
|
:scheme_as_string,
|
95
|
+ :case_sensitive_headers,
|
96
96
|
requests: :queue.new(),
|
97
97
|
state: :closed,
|
98
98
|
buffer: "",
|
|
@@ -117,6 +117,13 @@ defmodule Mint.HTTP1 do
|
117
117
|
Same as `Mint.HTTP.connect/4`, but forces an HTTP/1 or HTTP/1.1 connection.
|
118
118
|
|
119
119
|
This function doesn't support proxying.
|
120
|
+
|
121
|
+ ## Additional Options
|
122
|
+
|
123
|
+ * `:case_sensitive_headers` - (boolean) if set to `true` the case of the supplied
|
124
|
+ headers in requests will be preserved. The default is to lowercase the headers
|
125
|
+ because HTTP/1.1 header names are case-insensitive. *Available since v1.6.0*.
|
126
|
+
|
120
127
|
"""
|
121
128
|
@spec connect(Types.scheme(), Types.address(), :inet.port_number(), keyword()) ::
|
122
129
|
{:ok, t()} | {:error, Types.error()}
|
|
@@ -124,7 +131,7 @@ defmodule Mint.HTTP1 do
|
124
131
|
# TODO: Also ALPN negotiate HTTP1?
|
125
132
|
|
126
133
|
hostname = Mint.Core.Util.hostname(opts, address)
|
127
|
- transport = scheme_to_transport(scheme)
|
134
|
+ transport = Util.scheme_to_transport(scheme)
|
128
135
|
|
129
136
|
transport_opts =
|
130
137
|
Keyword.get(opts, :transport_opts, [])
|
|
@@ -147,7 +154,7 @@ defmodule Mint.HTTP1 do
|
147
154
|
def upgrade(old_scheme, socket, new_scheme, hostname, port, opts) do
|
148
155
|
# TODO: Also ALPN negotiate HTTP1?
|
149
156
|
|
150
|
- transport = scheme_to_transport(new_scheme)
|
157
|
+ transport = Util.scheme_to_transport(new_scheme)
|
151
158
|
|
152
159
|
transport_opts =
|
153
160
|
Keyword.get(opts, :transport_opts, [])
|
|
@@ -168,7 +175,7 @@ defmodule Mint.HTTP1 do
|
168
175
|
keyword()
|
169
176
|
) :: {:ok, t()} | {:error, Types.error()}
|
170
177
|
def initiate(scheme, socket, hostname, port, opts) do
|
171
|
- transport = scheme_to_transport(scheme)
|
178
|
+ transport = Util.scheme_to_transport(scheme)
|
172
179
|
mode = Keyword.get(opts, :mode, :active)
|
173
180
|
log? = Keyword.get(opts, :log, false)
|
174
181
|
|
|
@@ -182,7 +189,7 @@ defmodule Mint.HTTP1 do
|
182
189
|
"the :log option must be a boolean, got: #{inspect(log?)}"
|
183
190
|
end
|
184
191
|
|
185
|
- with :ok <- inet_opts(transport, socket),
|
192
|
+ with :ok <- Util.inet_opts(transport, socket),
|
186
193
|
:ok <- if(mode == :active, do: transport.setopts(socket, active: :once), else: :ok) do
|
187
194
|
conn = %__MODULE__{
|
188
195
|
transport: transport,
|
|
@@ -192,7 +199,8 @@ defmodule Mint.HTTP1 do
|
192
199
|
port: port,
|
193
200
|
scheme_as_string: Atom.to_string(scheme),
|
194
201
|
state: :open,
|
195
|
- log: log?
|
202
|
+ log: log?,
|
203
|
+ case_sensitive_headers: Keyword.get(opts, :case_sensitive_headers, false)
|
196
204
|
}
|
197
205
|
|
198
206
|
{:ok, conn}
|
|
@@ -223,9 +231,10 @@ defmodule Mint.HTTP1 do
|
223
231
|
See `Mint.HTTP.open?/1`.
|
224
232
|
"""
|
225
233
|
@impl true
|
226
|
- @spec open?(t(), :read | :write | :read_write) :: boolean()
|
227
|
- def open?(conn, type \\ :read_write)
|
234
|
+ @spec open?(t(), :read | :write) :: boolean()
|
235
|
+ def open?(conn, type \\ :write)
|
228
236
|
|
237
|
+ # TODO: hard-deprecate :read_write in 1.7.
|
229
238
|
def open?(%__MODULE__{state: state}, type) when type in [:read, :write, :read_write] do
|
230
239
|
state == :open
|
231
240
|
end
|
|
@@ -262,11 +271,17 @@ defmodule Mint.HTTP1 do
|
262
271
|
|
263
272
|
headers =
|
264
273
|
headers
|
265
|
- |> lower_header_keys()
|
274
|
+ |> Headers.from_raw()
|
266
275
|
|> add_default_headers(conn)
|
267
276
|
|
268
277
|
with {:ok, headers, encoding} <- add_content_length_or_transfer_encoding(headers, body),
|
269
|
- {:ok, iodata} <- Request.encode(method, path, headers, body),
|
278
|
+ {:ok, iodata} <-
|
279
|
+ Request.encode(
|
280
|
+ method,
|
281
|
+ path,
|
282
|
+ Headers.to_raw(headers, conn.case_sensitive_headers),
|
283
|
+ body
|
284
|
+ ),
|
270
285
|
:ok <- transport.send(socket, iodata) do
|
271
286
|
request_ref = make_ref()
|
272
287
|
request = new_request(request_ref, method, body, encoding)
|
|
@@ -363,7 +378,7 @@ defmodule Mint.HTTP1 do
|
363
378
|
ref,
|
364
379
|
chunk
|
365
380
|
) do
|
366
|
- with {:ok, chunk} <- validate_chunk(chunk),
|
381
|
+ with {:ok, chunk} <- validate_chunk(conn, chunk),
|
367
382
|
:ok <- conn.transport.send(conn.socket, Request.encode_chunk(chunk)) do
|
368
383
|
case chunk do
|
369
384
|
:eof ->
|
|
@@ -391,21 +406,21 @@ defmodule Mint.HTTP1 do
|
391
406
|
end
|
392
407
|
end
|
393
408
|
|
394
|
- defp validate_chunk({:eof, trailer_headers}) do
|
395
|
- headers = lower_header_keys(trailer_headers)
|
409
|
+ defp validate_chunk(conn, {:eof, trailers}) do
|
410
|
+ trailers = Headers.from_raw(trailers)
|
396
411
|
|
397
|
- if unallowed_header = find_unallowed_trailer_header(headers) do
|
412
|
+ if unallowed_header = Headers.find_unallowed_trailer(trailers) do
|
398
413
|
{:error, wrap_error({:unallowed_trailing_header, unallowed_header})}
|
399
414
|
else
|
400
|
- {:ok, {:eof, headers}}
|
415
|
+ {:ok, {:eof, Headers.to_raw(trailers, conn.case_sensitive_headers)}}
|
401
416
|
end
|
402
417
|
end
|
403
418
|
|
404
|
- defp validate_chunk(:eof) do
|
419
|
+ defp validate_chunk(_conn, :eof) do
|
405
420
|
{:ok, :eof}
|
406
421
|
end
|
407
422
|
|
408
|
- defp validate_chunk(chunk) do
|
423
|
+ defp validate_chunk(_conn, chunk) do
|
409
424
|
if IO.iodata_length(chunk) == 0 do
|
410
425
|
:empty_chunk
|
411
426
|
else
|
|
@@ -458,7 +473,7 @@ defmodule Mint.HTTP1 do
|
458
473
|
end
|
459
474
|
|
460
475
|
defp handle_data(%__MODULE__{request: request} = conn, data) do
|
461
|
- data = maybe_concat(conn.buffer, data)
|
476
|
+ data = Util.maybe_concat(conn.buffer, data)
|
462
477
|
|
463
478
|
case decode(request.state, conn, data, []) do
|
464
479
|
{:ok, conn, responses} ->
|
|
@@ -795,7 +810,7 @@ defmodule Mint.HTTP1 do
|
795
810
|
decode_trailer_headers(conn, rest, responses, headers)
|
796
811
|
|
797
812
|
{:ok, :eof, rest} ->
|
798
|
- headers = Util.remove_unallowed_trailer_headers(headers)
|
813
|
+ headers = Headers.remove_unallowed_trailer(headers)
|
799
814
|
|
800
815
|
responses = [
|
801
816
|
{:done, conn.request.ref}
|
|
@@ -972,14 +987,10 @@ defmodule Mint.HTTP1 do
|
972
987
|
}
|
973
988
|
end
|
974
989
|
|
975
|
- defp lower_header_keys(headers) do
|
976
|
- for {name, value} <- headers, do: {Util.downcase_ascii(name), value}
|
977
|
- end
|
978
|
-
|
979
990
|
defp add_default_headers(headers, conn) do
|
980
991
|
headers
|
981
|
- |> Util.put_new_header("user-agent", @user_agent)
|
982
|
- |> Util.put_new_header("host", default_host_header(conn))
|
992
|
+ |> Headers.put_new("User-Agent", "user-agent", @user_agent)
|
993
|
+ |> Headers.put_new("Host", "host", default_host_header(conn))
|
983
994
|
end
|
984
995
|
|
985
996
|
# If the port is the default for the scheme, don't add it to the host header
|
|
@@ -993,18 +1004,19 @@ defmodule Mint.HTTP1 do
|
993
1004
|
|
994
1005
|
defp add_content_length_or_transfer_encoding(headers, :stream) do
|
995
1006
|
cond do
|
996
|
- List.keymember?(headers, "content-length", 0) ->
|
1007
|
+ Headers.has?(headers, "content-length") ->
|
997
1008
|
{:ok, headers, :identity}
|
998
1009
|
|
999
|
- found = List.keyfind(headers, "transfer-encoding", 0) ->
|
1000
|
- {"transfer-encoding", value} = found
|
1010
|
+ found = Headers.find(headers, "transfer-encoding") ->
|
1011
|
+ {raw_name, value} = found
|
1001
1012
|
|
1002
1013
|
with {:ok, tokens} <- Parse.transfer_encoding_header(value) do
|
1003
1014
|
if "chunked" in tokens or "identity" in tokens do
|
1004
1015
|
{:ok, headers, :identity}
|
1005
1016
|
else
|
1006
|
- new_transfer_encoding = {"transfer-encoding", value <> ",chunked"}
|
1007
|
- headers = List.keyreplace(headers, "transfer-encoding", 0, new_transfer_encoding)
|
1017
|
+ headers =
|
1018
|
+ Headers.replace(headers, raw_name, "transfer-encoding", value <> ",chunked")
|
1019
|
+
|
1008
1020
|
{:ok, headers, :chunked}
|
1009
1021
|
end
|
1010
1022
|
end
|
|
@@ -1012,7 +1024,9 @@ defmodule Mint.HTTP1 do
|
1012
1024
|
# If no content-length or transfer-encoding are present, assume
|
1013
1025
|
# chunked transfer-encoding and handle the encoding ourselves.
|
1014
1026
|
true ->
|
1015
|
- headers = Util.put_new_header(headers, "transfer-encoding", "chunked")
|
1027
|
+ headers =
|
1028
|
+ Headers.put_new(headers, "Transfer-Encoding", "transfer-encoding", "chunked")
|
1029
|
+
|
1016
1030
|
{:ok, headers, :chunked}
|
1017
1031
|
end
|
1018
1032
|
end
|
|
@@ -1023,7 +1037,9 @@ defmodule Mint.HTTP1 do
|
1023
1037
|
|
1024
1038
|
defp add_content_length_or_transfer_encoding(headers, body) do
|
1025
1039
|
length_fun = fn -> body |> IO.iodata_length() |> Integer.to_string() end
|
1026
|
- {:ok, Util.put_new_header_lazy(headers, "content-length", length_fun), :identity}
|
1040
|
+
|
1041
|
+ {:ok, Headers.put_new_lazy(headers, "Content-Length", "content-length", length_fun),
|
1042
|
+ :identity}
|
1027
1043
|
end
|
1028
1044
|
|
1029
1045
|
defp wrap_error(reason) do
|
changed
lib/mint/http1/parse.ex
|
@@ -1,8 +1,6 @@
|
1
1
|
defmodule Mint.HTTP1.Parse do
|
2
2
|
@moduledoc false
|
3
3
|
|
4
|
- alias Mint.Core.Util
|
5
|
-
|
6
4
|
defmacro is_digit(char), do: quote(do: unquote(char) in ?0..?9)
|
7
5
|
defmacro is_alpha(char), do: quote(do: unquote(char) in ?a..?z or unquote(char) in ?A..?Z)
|
8
6
|
defmacro is_whitespace(char), do: quote(do: unquote(char) in ~c"\s\t")
|
|
@@ -55,7 +53,7 @@ defmodule Mint.HTTP1.Parse do
|
55
53
|
defp token_list_downcase(rest, acc), do: token_downcase(rest, _token_acc = <<>>, acc)
|
56
54
|
|
57
55
|
defp token_downcase(<<char, rest::binary>>, token_acc, acc) when is_tchar(char),
|
58
|
- do: token_downcase(rest, <<token_acc::binary, Util.downcase_ascii_char(char)>>, acc)
|
56
|
+ do: token_downcase(rest, <<token_acc::binary, downcase_ascii_char(char)>>, acc)
|
59
57
|
|
60
58
|
defp token_downcase(rest, token_acc, acc), do: token_list_sep_downcase(rest, [token_acc | acc])
|
61
59
|
|
|
@@ -68,4 +66,7 @@ defmodule Mint.HTTP1.Parse do
|
68
66
|
do: token_list_downcase(rest, acc)
|
69
67
|
|
70
68
|
defp token_list_sep_downcase(_rest, _acc), do: :error
|
69
|
+
|
70
|
+ defp downcase_ascii_char(char) when char in ?A..?Z, do: char + 32
|
71
|
+ defp downcase_ascii_char(char) when char in 0..127, do: char
|
71
72
|
end
|
changed
lib/mint/http1/response.ex
|
@@ -1,7 +1,7 @@
|
1
1
|
defmodule Mint.HTTP1.Response do
|
2
2
|
@moduledoc false
|
3
3
|
|
4
|
- alias Mint.Core.Util
|
4
|
+ alias Mint.Core.Headers
|
5
5
|
|
6
6
|
def decode_status_line(binary) do
|
7
7
|
case :erlang.decode_packet(:http_bin, binary, []) do
|
|
@@ -38,6 +38,6 @@ defmodule Mint.HTTP1.Response do
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
- defp header_name(atom) when is_atom(atom), do: atom |> Atom.to_string() |> Util.downcase_ascii()
|
42
|
- defp header_name(binary) when is_binary(binary), do: Util.downcase_ascii(binary)
|
41
|
+ defp header_name(atom) when is_atom(atom), do: atom |> Atom.to_string() |> header_name()
|
42
|
+ defp header_name(binary) when is_binary(binary), do: Headers.lower_raw(binary)
|
43
43
|
end
|
changed
lib/mint/http2.ex
|
@@ -125,12 +125,11 @@ defmodule Mint.HTTP2 do
|
125
125
|
> is enabled.
|
126
126
|
"""
|
127
127
|
|
128
|
- import Mint.Core.Util
|
129
128
|
import Mint.HTTP2.Frame, except: [encode: 1, decode_next: 1, inspect: 1]
|
130
129
|
|
131
130
|
alias Mint.{HTTPError, TransportError}
|
132
131
|
alias Mint.Types
|
133
|
- alias Mint.Core.Util
|
132
|
+ alias Mint.Core.{Headers, Util}
|
134
133
|
alias Mint.HTTP2.Frame
|
135
134
|
|
136
135
|
require Logger
|
|
@@ -170,6 +169,7 @@ defmodule Mint.HTTP2 do
|
170
169
|
:hostname,
|
171
170
|
:port,
|
172
171
|
:scheme,
|
172
|
+ :authority,
|
173
173
|
|
174
174
|
# Connection state (open, closed, and so on).
|
175
175
|
:state,
|
|
@@ -232,22 +232,13 @@ defmodule Mint.HTTP2 do
|
232
232
|
conn = unquote(conn)
|
233
233
|
|
234
234
|
if conn.log do
|
235
|
- Logger.log(normalize_logger_level(unquote(level)), unquote(message))
|
235
|
+ Logger.log(unquote(level), unquote(message))
|
236
236
|
else
|
237
237
|
:ok
|
238
238
|
end
|
239
239
|
end
|
240
240
|
end
|
241
241
|
|
242
|
- # TODO: remove this once we depend on Elixir 1.11+.
|
243
|
- if macro_exported?(Logger, :warning, 2) do
|
244
|
- defp normalize_logger_level(:warning), do: :warning
|
245
|
- else
|
246
|
- defp normalize_logger_level(:warning), do: :warn
|
247
|
- end
|
248
|
-
|
249
|
- defp normalize_logger_level(level), do: level
|
250
|
-
|
251
242
|
## Types
|
252
243
|
|
253
244
|
@typedoc """
|
|
@@ -404,7 +395,7 @@ defmodule Mint.HTTP2 do
|
404
395
|
keyword()
|
405
396
|
) :: {:ok, t()} | {:error, Types.error()}
|
406
397
|
def upgrade(old_scheme, socket, new_scheme, hostname, port, opts) do
|
407
|
- transport = scheme_to_transport(new_scheme)
|
398
|
+ transport = Util.scheme_to_transport(new_scheme)
|
408
399
|
|
409
400
|
transport_opts =
|
410
401
|
opts
|
|
@@ -448,8 +439,8 @@ defmodule Mint.HTTP2 do
|
448
439
|
See `Mint.HTTP.open?/1`.
|
449
440
|
"""
|
450
441
|
@impl true
|
451
|
- @spec open?(t(), :read | :write | :read_write) :: boolean()
|
452
|
- def open?(%__MODULE__{state: state} = _conn, type \\ :read_write)
|
442
|
+ @spec open?(t(), :read | :write) :: boolean()
|
443
|
+ def open?(%__MODULE__{state: state} = _conn, type \\ :write)
|
453
444
|
when type in [:read, :write, :read_write] do
|
454
445
|
case state do
|
455
446
|
:handshaking -> true
|
|
@@ -520,7 +511,7 @@ defmodule Mint.HTTP2 do
|
520
511
|
when is_binary(method) and is_binary(path) and is_list(headers) do
|
521
512
|
headers =
|
522
513
|
headers
|
523
|
- |> downcase_header_names()
|
514
|
+ |> Headers.lower_raws()
|
524
515
|
|> add_pseudo_headers(conn, method, path)
|
525
516
|
|> add_default_headers(body)
|
526
517
|
|> sort_pseudo_headers_to_front()
|
|
@@ -973,11 +964,19 @@ defmodule Mint.HTTP2 do
|
973
964
|
keyword()
|
974
965
|
) :: {:ok, t()} | {:error, Types.error()}
|
975
966
|
def initiate(scheme, socket, hostname, port, opts) do
|
976
|
- transport = scheme_to_transport(scheme)
|
967
|
+ transport = Util.scheme_to_transport(scheme)
|
968
|
+ scheme_string = Atom.to_string(scheme)
|
977
969
|
mode = Keyword.get(opts, :mode, :active)
|
978
970
|
log? = Keyword.get(opts, :log, false)
|
979
971
|
client_settings_params = Keyword.get(opts, :client_settings, [])
|
980
972
|
validate_client_settings!(client_settings_params)
|
973
|
+ # If the port is the default for the scheme, don't add it to the :authority pseudo-header
|
974
|
+ authority =
|
975
|
+ if URI.default_port(scheme_string) == port do
|
976
|
+ hostname
|
977
|
+ else
|
978
|
+ "#{hostname}:#{port}"
|
979
|
+ end
|
981
980
|
|
982
981
|
unless mode in [:active, :passive] do
|
983
982
|
raise ArgumentError,
|
|
@@ -992,15 +991,16 @@ defmodule Mint.HTTP2 do
|
992
991
|
conn = %__MODULE__{
|
993
992
|
hostname: hostname,
|
994
993
|
port: port,
|
995
|
- transport: scheme_to_transport(scheme),
|
994
|
+ authority: authority,
|
995
|
+ transport: Util.scheme_to_transport(scheme),
|
996
996
|
socket: socket,
|
997
997
|
mode: mode,
|
998
|
- scheme: Atom.to_string(scheme),
|
998
|
+ scheme: scheme_string,
|
999
999
|
state: :handshaking,
|
1000
1000
|
log: log?
|
1001
1001
|
}
|
1002
1002
|
|
1003
|
- with :ok <- inet_opts(transport, socket),
|
1003
|
+ with :ok <- Util.inet_opts(transport, socket),
|
1004
1004
|
client_settings = settings(stream_id: 0, params: client_settings_params),
|
1005
1005
|
preface = [@connection_preface, Frame.encode(client_settings)],
|
1006
1006
|
:ok <- transport.send(socket, preface),
|
|
@@ -1055,12 +1055,12 @@ defmodule Mint.HTTP2 do
|
1055
1055
|
defp negotiate(address, port, :http, transport_opts) do
|
1056
1056
|
# We don't support protocol negotiation for TCP connections
|
1057
1057
|
# so currently we just assume the HTTP/2 protocol
|
1058
|
- transport = scheme_to_transport(:http)
|
1058
|
+ transport = Util.scheme_to_transport(:http)
|
1059
1059
|
transport.connect(address, port, transport_opts)
|
1060
1060
|
end
|
1061
1061
|
|
1062
1062
|
defp negotiate(address, port, :https, transport_opts) do
|
1063
|
- transport = scheme_to_transport(:https)
|
1063
|
+ transport = Util.scheme_to_transport(:https)
|
1064
1064
|
|
1065
1065
|
with {:ok, socket} <- transport.connect(address, port, transport_opts),
|
1066
1066
|
{:ok, protocol} <- transport.negotiated_protocol(socket) do
|
|
@@ -1097,14 +1097,15 @@ defmodule Mint.HTTP2 do
|
1097
1097
|
encode_data(conn, stream_id, "", [:end_stream])
|
1098
1098
|
end
|
1099
1099
|
|
1100
|
- defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailer_headers}) do
|
1101
|
- lowered_headers = downcase_header_names(trailer_headers)
|
1100
|
+ defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailers}) do
|
1101
|
+ trailers = Headers.from_raw(trailers)
|
1102
1102
|
|
1103
|
- if unallowed_trailer_header = Util.find_unallowed_trailer_header(lowered_headers) do
|
1103
|
+ if unallowed_trailer_header = Headers.find_unallowed_trailer(trailers) do
|
1104
1104
|
error = wrap_error({:unallowed_trailing_header, unallowed_trailer_header})
|
1105
1105
|
throw({:mint, conn, error})
|
1106
1106
|
end
|
1107
1107
|
|
1108
|
+ trailer_headers = Headers.to_raw(trailers, _case_sensitive = false)
|
1108
1109
|
encode_headers(conn, stream_id, trailer_headers, [:end_headers, :end_stream])
|
1109
1110
|
end
|
1110
1111
|
|
|
@@ -1334,10 +1335,6 @@ defmodule Mint.HTTP2 do
|
1334
1335
|
end)
|
1335
1336
|
end
|
1336
1337
|
|
1337
|
- defp downcase_header_names(headers) do
|
1338
|
- for {name, value} <- headers, do: {Util.downcase_ascii(name), value}
|
1339
|
- end
|
1340
|
-
|
1341
1338
|
defp add_default_headers(headers, body) do
|
1342
1339
|
headers
|
1343
1340
|
|> Util.put_new_header("user-agent", @user_agent)
|
|
@@ -1355,10 +1352,10 @@ defmodule Mint.HTTP2 do
|
1355
1352
|
end
|
1356
1353
|
|
1357
1354
|
defp add_pseudo_headers(headers, conn, method, path) do
|
1358
|
- if String.upcase(method) == "CONNECT" do
|
1355
|
+ if same_method?(method, "CONNECT") do
|
1359
1356
|
[
|
1360
1357
|
{":method", method},
|
1361
|
- {":authority", authority_pseudo_header(conn.scheme, conn.port, conn.hostname)}
|
1358
|
+ {":authority", conn.authority}
|
1362
1359
|
| headers
|
1363
1360
|
]
|
1364
1361
|
else
|
|
@@ -1366,12 +1363,27 @@ defmodule Mint.HTTP2 do
|
1366
1363
|
{":method", method},
|
1367
1364
|
{":path", path},
|
1368
1365
|
{":scheme", conn.scheme},
|
1369
|
- {":authority", authority_pseudo_header(conn.scheme, conn.port, conn.hostname)}
|
1366
|
+ {":authority", conn.authority}
|
1370
1367
|
| headers
|
1371
1368
|
]
|
1372
1369
|
end
|
1373
1370
|
end
|
1374
1371
|
|
1372
|
+ # same_method?/2 is pretty optimized, so bench before changing.
|
1373
|
+
|
1374
|
+ # Same binary, which is a common case.
|
1375
|
+ defp same_method?(bin, bin), do: true
|
1376
|
+
|
1377
|
+ # Get out early if the size is different, these can't be the same.
|
1378
|
+ defp same_method?(bin1, bin2) when byte_size(bin1) != byte_size(bin2), do: false
|
1379
|
+
|
1380
|
+ defp same_method?(<<ch, rest1::binary>>, <<ch, rest2::binary>>), do: same_method?(rest1, rest2)
|
1381
|
+
|
1382
|
+ defp same_method?(<<lower, rest1::binary>>, <<char, rest2::binary>>) when lower - 32 == char,
|
1383
|
+ do: same_method?(rest1, rest2)
|
1384
|
+
|
1385
|
+ defp same_method?(_method1, _method2), do: false
|
1386
|
+
|
1375
1387
|
defp sort_pseudo_headers_to_front(headers) do
|
1376
1388
|
Enum.sort_by(headers, fn {key, _value} ->
|
1377
1389
|
not String.starts_with?(key, ":")
|
|
@@ -1381,7 +1393,7 @@ defmodule Mint.HTTP2 do
|
1381
1393
|
## Frame handling
|
1382
1394
|
|
1383
1395
|
defp maybe_concat_and_handle_new_data(conn, data) do
|
1384
|
- data = maybe_concat(conn.buffer, data)
|
1396
|
+ data = Util.maybe_concat(conn.buffer, data)
|
1385
1397
|
{conn, responses} = handle_new_data(conn, data, [])
|
1386
1398
|
{:ok, conn, Enum.reverse(responses)}
|
1387
1399
|
end
|
|
@@ -1684,7 +1696,7 @@ defmodule Mint.HTTP2 do
|
1684
1696
|
headers when received_first_headers? ->
|
1685
1697
|
if end_stream? do
|
1686
1698
|
conn = close_stream!(conn, stream.id, :no_error)
|
1687
|
- headers = headers |> Util.remove_unallowed_trailer_headers() |> join_cookie_headers()
|
1699
|
+ headers = headers |> Headers.remove_unallowed_trailer() |> join_cookie_headers()
|
1688
1700
|
{conn, [{:done, ref}, {:headers, ref, headers} | responses]}
|
1689
1701
|
else
|
1690
1702
|
# Trailer headers must set the END_STREAM flag because they're
|
|
@@ -1719,18 +1731,9 @@ defmodule Mint.HTTP2 do
|
1719
1731
|
end
|
1720
1732
|
end
|
1721
1733
|
|
1722
|
- # If the port is the default for the scheme, don't add it to the :authority pseudo-header
|
1723
|
- defp authority_pseudo_header(scheme, port, hostname) do
|
1724
|
- if URI.default_port(scheme) == port do
|
1725
|
- hostname
|
1726
|
- else
|
1727
|
- "#{hostname}:#{port}"
|
1728
|
- end
|
1729
|
- end
|
1730
|
-
|
1731
1734
|
defp join_cookie_headers(headers) do
|
1732
1735
|
# If we have 0 or 1 Cookie headers, we just use the old list of headers.
|
1733
|
- case Enum.split_with(headers, fn {name, _value} -> Util.downcase_ascii(name) == "cookie" end) do
|
1736
|
+ case Enum.split_with(headers, fn {name, _value} -> Headers.lower_raw(name) == "cookie" end) do
|
1734
1737
|
{[], _headers} ->
|
1735
1738
|
headers
|
1736
1739
|
|
|
@@ -2215,8 +2218,8 @@ defmodule Mint.HTTP2 do
|
2215
2218
|
"can't send more data on this request since it's not streaming"
|
2216
2219
|
end
|
2217
2220
|
|
2218
|
- def format_error({:unallowed_trailing_header, {name, value}}) do
|
2219
|
- "header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailer header"
|
2221
|
+ def format_error({:unallowed_trailing_header, name}) do
|
2222
|
+ "header #{inspect(name)} is not allowed as a trailer header"
|
2220
2223
|
end
|
2221
2224
|
|
2222
2225
|
def format_error(:missing_status_header) do
|
changed
lib/mint/http2/frame.ex
|
@@ -66,7 +66,6 @@ defmodule Mint.HTTP2.Frame do
|
66
66
|
for {frame, flags} <- @flags,
|
67
67
|
{flag_name, flag_value} <- flags do
|
68
68
|
defp set_flag(flags, unquote(frame), unquote(flag_name)), do: bor(flags, unquote(flag_value))
|
69
|
- defp set_flag(unquote(frame), unquote(flag_name)), do: unquote(flag_value)
|
70
69
|
|
71
70
|
def flag_set?(flags, unquote(frame), unquote(flag_name)),
|
72
71
|
do: band(flags, unquote(flag_value)) == unquote(flag_value)
|
changed
lib/mint/negotiate.ex
|
@@ -1,8 +1,6 @@
|
1
1
|
defmodule Mint.Negotiate do
|
2
2
|
@moduledoc false
|
3
3
|
|
4
|
- import Mint.Core.Util
|
5
|
-
|
6
4
|
alias Mint.{
|
7
5
|
HTTP1,
|
8
6
|
HTTP2,
|
|
@@ -10,6 +8,8 @@ defmodule Mint.Negotiate do
|
10
8
|
Types
|
11
9
|
}
|
12
10
|
|
11
|
+ alias Mint.Core.Util
|
12
|
+
|
13
13
|
@default_protocols [:http1, :http2]
|
14
14
|
@transport_opts [alpn_advertised_protocols: ["http/1.1", "h2"]]
|
15
15
|
|
|
@@ -68,7 +68,7 @@ defmodule Mint.Negotiate do
|
68
68
|
end
|
69
69
|
|
70
70
|
defp connect_negotiate(scheme, address, port, opts) do
|
71
|
- transport = scheme_to_transport(scheme)
|
71
|
+ transport = Util.scheme_to_transport(scheme)
|
72
72
|
hostname = Mint.Core.Util.hostname(opts, address)
|
73
73
|
|
74
74
|
transport_opts =
|
|
@@ -106,7 +106,7 @@ defmodule Mint.Negotiate do
|
106
106
|
end
|
107
107
|
|
108
108
|
defp connect_upgrade(proxy_scheme, transport_state, new_scheme, hostname, port, opts) do
|
109
|
- transport = scheme_to_transport(new_scheme)
|
109
|
+ transport = Util.scheme_to_transport(new_scheme)
|
110
110
|
|
111
111
|
transport_opts =
|
112
112
|
opts
|
|
@@ -123,7 +123,7 @@ defmodule Mint.Negotiate do
|
123
123
|
end
|
124
124
|
|
125
125
|
defp alpn_negotiate(scheme, socket, hostname, port, opts) do
|
126
|
- transport = scheme_to_transport(scheme)
|
126
|
+ transport = Util.scheme_to_transport(scheme)
|
127
127
|
|
128
128
|
case transport.negotiated_protocol(socket) do
|
129
129
|
{:ok, "http/1.1"} ->
|
changed
lib/mint/unsafe_proxy.ex
|
@@ -58,8 +58,8 @@ defmodule Mint.UnsafeProxy do
|
58
58
|
end
|
59
59
|
|
60
60
|
@impl true
|
61
|
- @spec open?(t(), :read | :write | :read_write) :: boolean()
|
62
|
- def open?(%UnsafeProxy{module: module, state: state}, type \\ :read_write) do
|
61
|
+ @spec open?(t(), :read | :write) :: boolean()
|
62
|
+ def open?(%UnsafeProxy{module: module, state: state}, type \\ :write) do
|
63
63
|
module.open?(state, type)
|
64
64
|
end
|
changed
mix.exs
|
@@ -1,14 +1,14 @@
|
1
1
|
defmodule Mint.MixProject do
|
2
2
|
use Mix.Project
|
3
3
|
|
4
|
- @version "1.5.2"
|
4
|
+ @version "1.6.0"
|
5
5
|
@repo_url "https://github.com/elixir-mint/mint"
|
6
6
|
|
7
7
|
def project do
|
8
8
|
[
|
9
9
|
app: :mint,
|
10
10
|
version: @version,
|
11
|
- elixir: "~> 1.10",
|
11
|
+ elixir: "~> 1.11",
|
12
12
|
start_permanent: Mix.env() == :prod,
|
13
13
|
elixirc_paths: elixirc_paths(Mix.env()),
|
14
14
|
deps: deps(),
|
|
@@ -72,14 +72,14 @@ defmodule Mint.MixProject do
|
72
72
|
defp deps do
|
73
73
|
[
|
74
74
|
{:castore, "~> 0.1.0 or ~> 1.0", optional: true},
|
75
|
- {:hpax, "~> 0.1.1"},
|
75
|
+ {:hpax, "~> 0.1.1 or ~> 0.2.0"},
|
76
76
|
|
77
77
|
# Dev/test dependencies
|
78
|
- {:dialyxir, "~> 1.3.0", only: [:dev, :test], runtime: false},
|
78
|
+ {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
|
79
79
|
{:ex_doc, "~> 0.20", only: :dev},
|
80
|
- {:excoveralls, "~> 0.17.0", only: :test},
|
80
|
+ {:excoveralls, "~> 0.18.0", only: :test},
|
81
81
|
{:mox, "~> 1.0", only: :test},
|
82
|
- {:stream_data, "~> 0.5.0", only: [:dev, :test]}
|
82
|
+ {:stream_data, "~> 0.6.0", only: [:dev, :test]}
|
83
83
|
]
|
84
84
|
end
|
85
85
|
end
|