Skip to content

Commit

Permalink
Rename "trailing" to "trailer" headers
Browse files Browse the repository at this point in the history
  • Loading branch information
whatyouhide committed Aug 29, 2023
1 parent c9b9e6a commit 2ce7593
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 147 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
* Fix a small bug with double "wrapping" of some `Mint.TransportError`s.
* Prevent unnecessary buffer allocations in the connections (less memory waste!).
* Add support for chunked transfer-encoding in HTTP/1 requests when you don't use `content-encoding`/`transfer-encoding` yourself.
* Add support for trailing headers in HTTP/* requests through `stream_request_body/3`.
* Add support for trailer headers in HTTP/* requests through `stream_request_body/3`.

This comment has been minimized.

Copy link
@wojtekmach

wojtekmach Aug 29, 2023

Contributor

trailer header feels like a misnomer though. should be trailer fields or trailers just like we have header fields or headers. But it might be a pragmatic choice to use this particular naming given we send them as :headers as well as users might not be aware of trailers (but then should they be using Mint? 🤔 😉). So yeah I think this is fine. thanks for making this change!

This comment has been minimized.

Copy link
@whatyouhide

whatyouhide Aug 29, 2023

Author Contributor

Yeah, that was my compromise. Call them "headers" everywhere so that the response name makes sense, but use the RFC word "trailer". Cool!

* Add a page about decompressing responses in the guides.

## v0.3.0
Expand All @@ -136,7 +136,7 @@

* Add `Mint.HTTP.controlling_process/2`, `Mint.HTTP1.controlling_process/2`, and `Mint.HTTP2.controlling_process/2` to change the controlling process of a connection.

* Support trailing response headers in HTTP/2 connections.
* Support trailer response headers in HTTP/2 connections.

## v0.2.1

Expand Down
2 changes: 1 addition & 1 deletion lib/mint/core/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule Mint.Core.Conn do
@callback stream_request_body(
conn(),
Types.request_ref(),
body_chunk :: iodata() | :eof | {:eof, trailing_headers :: Types.headers()}
body_chunk :: iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
) ::
{:ok, conn()} | {:error, conn(), Types.error()}

Expand Down
96 changes: 48 additions & 48 deletions lib/mint/core/util.ex
Original file line number Diff line number Diff line change
@@ -1,50 +1,50 @@
defmodule Mint.Core.Util do
@moduledoc false

@unallowed_trailing_headers MapSet.new([
"content-encoding",
"content-length",
"content-range",
"content-type",
"trailer",
"transfer-encoding",

# Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1)
"cache-control",
"expect",
"host",
"max-forwards",
"pragma",
"range",
"te",

# Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2)
"if-match",
"if-none-match",
"if-modified-since",
"if-unmodified-since",
"if-range",

# Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3)
"authorization",
"proxy-authenticate",
"proxy-authorization",
"www-authenticate",

# Cookie management (https://tools.ietf.org/html/rfc6265)
"cookie",
"set-cookie",

# Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1)
"age",
"cache-control",
"expires",
"date",
"location",
"retry-after",
"vary",
"warning"
])
@unallowed_trailer_headers MapSet.new([
"content-encoding",
"content-length",
"content-range",
"content-type",
"trailer",
"transfer-encoding",

# Control headers (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.1)
"cache-control",
"expect",
"host",
"max-forwards",
"pragma",
"range",
"te",

# Conditionals (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.5.2)
"if-match",
"if-none-match",
"if-modified-since",
"if-unmodified-since",
"if-range",

# Authentication/authorization (https://tools.ietf.org/html/rfc7235#section-5.3)
"authorization",
"proxy-authenticate",
"proxy-authorization",
"www-authenticate",

# Cookie management (https://tools.ietf.org/html/rfc6265)
"cookie",
"set-cookie",

# Control data (https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231.html#rfc.section.7.1)
"age",
"cache-control",
"expires",
"date",
"location",
"retry-after",
"vary",
"warning"
])

def hostname(opts, address) when is_list(opts) do
case Keyword.fetch(opts, :hostname) do
Expand Down Expand Up @@ -114,11 +114,11 @@ defmodule Mint.Core.Util do
def maybe_concat(<<>>, data), do: data
def maybe_concat(buffer, data) when is_binary(buffer), do: buffer <> data

def find_unallowed_trailing_header(headers) do
Enum.find(headers, fn {name, _value} -> name in @unallowed_trailing_headers end)
def find_unallowed_trailer_header(headers) do
Enum.find(headers, fn {name, _value} -> name in @unallowed_trailer_headers end)
end

def remove_unallowed_trailing_headers(headers) do
Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailing_headers end)
def remove_unallowed_trailer_headers(headers) do
Enum.reject(headers, fn {name, _value} -> name in @unallowed_trailer_headers end)
end
end
40 changes: 21 additions & 19 deletions lib/mint/http.ex
Original file line number Diff line number Diff line change
Expand Up @@ -606,41 +606,43 @@ defmodule Mint.HTTP do
* `:eof` - signals the end of the streaming of the request body for the given
request. Usually the server won't send any reply until this is sent.
* `{:eof, trailing_headers}` - sends **trailing headers** and signals the end
* `{:eof, trailer_headers}` - sends **trailer headers** and signals the end
of the streaming of the request body for the given request. This behaves the
same way as `:eof` but first sends the trailing headers. See the "Trailing headers"
section below.
same way as `:eof` but first sends the trailer headers. See the
[*Trailer headers*](#module-trailer-headers) section below.
This function always returns an updated connection to be stored over the old connection.
For information about transfer encoding and content length in HTTP/1, see
`Mint.HTTP1.stream_request_body/3`.
## Trailing headers
## Trailer headers
HTTP trailing headers can be sent after the body of a request. The behaviour is slightly
different for HTTP/1 and HTTP/2.
HTTP trailer headers can be sent after the body of a request. trailer headers are described
[in RFC 9110](https://www.rfc-editor.org/rfc/rfc9110#section-6.5).
In HTTP/1, trailing headers are only supported if the transfer encoding is set to
`chunked`. See `Mint.HTTP1.stream_request_body/3` for more information on chunked
transfer encoding.
The behaviour is slightly different for HTTP/1 and HTTP/2:
In HTTP/2, trailing headers behave like normal headers. You don't need to care
about the transfer encoding.
* In HTTP/1, trailer headers are only supported if the transfer encoding is set to
`chunked`. See `Mint.HTTP1.stream_request_body/3` for more information on chunked
transfer encoding.
* In HTTP/2, trailer headers behave like normal headers. You don't need to care
about the transfer encoding.
### The `trailer` header
As specified in [section 4.4 of RFC 7230](https://tools.ietf.org/html/rfc7230#section-4.4),
in HTTP/1 you need to specify which headers you're going to send as trailing
in HTTP/1 you need to specify which headers you're going to send as traoler
headers using the `trailer` header. The `trailer` header applies to both HTTP/1
and HTTP/2. See the examples below for more information.
### The `te` header
As specified in [section 4.3 of RFC 7230](https://tools.ietf.org/html/rfc7230#section-4.3),
the `te` (or `TE`) header is used to specify which transfer-encodings the client
is willing to accept (besides `chunked`). Mint supports decoding of trailing headers,
but if you want to notify the server that you are accepting trailing headers,
is willing to accept (besides `chunked`). Mint supports decoding of trailer headers,
but if you want to notify the server that you are accepting trailer headers,
use the `trailers` value in the `te` header. For example:
Mint.HTTP.request(conn, "GET", "/", [{"te", "trailers"}], "some body")
Expand All @@ -659,22 +661,22 @@ defmodule Mint.HTTP do
{:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, "}")
{:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, :eof)
Here's an example of sending trailing headers:
Here's an example of sending trailer headers:
headers = [{"content-type", "application/json"}, {"trailer", "my-trailer, x-expires"}]
{:ok, conn, request_ref} = Mint.HTTP.request(conn, "POST", "/", headers, :stream)
{:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, "{}")
trailing_headers = [{"my-trailer", "xxx"}, {"x-expires", "10 days"}]
{:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, {:eof, trailing_headers})
trailer_headers = [{"my-trailer", "xxx"}, {"x-expires", "10 days"}]
{:ok, conn} = Mint.HTTP.stream_request_body(conn, request_ref, {:eof, trailer_headers})
"""
@impl true
@spec stream_request_body(
t(),
Types.request_ref(),
iodata() | :eof | {:eof, trailing_headers :: Types.headers()}
iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
) ::
{:ok, t()} | {:error, t(), Types.error()}
def stream_request_body(conn, ref, body),
Expand Down Expand Up @@ -743,7 +745,7 @@ defmodule Mint.HTTP do
with a list of headers. Headers are in the form `{header_name, header_value}`
with `header_name` and `header_value` being strings. A single `:headers` response
will come after the `:status` response. A single `:headers` response may come
after all the `:data` responses if **trailing headers** are present.
after all the `:data` responses if **trailer headers** are present.
* `{:data, request_ref, binary}` - returned when the server replied with
a chunk of response body (as a binary). The request shouldn't be considered done
Expand Down
26 changes: 13 additions & 13 deletions lib/mint/http1.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Mint.HTTP1 do
* `{:invalid_token_list, string}` - when a header that is supposed to contain a list
of tokens (such as the `connection` header) contains a malformed list of tokens.
* `:trailing_headers_but_not_chunked_encoding` - when you try to send trailing
* `:trailing_headers_but_not_chunked_encoding` - when you try to send trailer
headers through `stream_request_body/3` but the transfer encoding of the request
was not `chunked`.
Expand Down Expand Up @@ -320,7 +320,7 @@ defmodule Mint.HTTP1 do
@spec stream_request_body(
t(),
Types.request_ref(),
iodata() | :eof | {:eof, trailing_headers :: Types.headers()}
iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
) ::
{:ok, t()} | {:error, t(), Types.error()}
def stream_request_body(
Expand All @@ -336,7 +336,7 @@ defmodule Mint.HTTP1 do
def stream_request_body(
%__MODULE__{streaming_request: %{state: {:stream_request, :identity}, ref: ref}} = conn,
ref,
{:eof, _trailing_headers}
{:eof, _trailer_headers}
) do
{:error, conn, wrap_error(:trailing_headers_but_not_chunked_encoding)}
end
Expand Down Expand Up @@ -371,7 +371,7 @@ defmodule Mint.HTTP1 do
conn = enqueue_request(%__MODULE__{conn | streaming_request: nil}, request)
{:ok, conn}

{:eof, _trailing_headers} ->
{:eof, _trailer_headers} ->
request = %{conn.streaming_request | state: :status}
conn = enqueue_request(%__MODULE__{conn | streaming_request: nil}, request)
{:ok, conn}
Expand All @@ -391,10 +391,10 @@ defmodule Mint.HTTP1 do
end
end

defp validate_chunk({:eof, trailing_headers}) do
headers = lower_header_keys(trailing_headers)
defp validate_chunk({:eof, trailer_headers}) do
headers = lower_header_keys(trailer_headers)

if unallowed_header = find_unallowed_trailing_header(headers) do
if unallowed_header = find_unallowed_trailer_header(headers) do
{:error, wrap_error({:unallowed_trailing_header, unallowed_header})}
else
{:ok, {:eof, headers}}
Expand Down Expand Up @@ -791,11 +791,11 @@ defmodule Mint.HTTP1 do
decode_trailer_headers(conn, rest, responses, headers)

{:ok, :eof, rest} ->
headers = Util.remove_unallowed_trailing_headers(headers)
headers = Util.remove_unallowed_trailer_headers(headers)

responses = [
{:done, conn.request.ref}
| add_trailing_headers(headers, conn.request.ref, responses)
| add_trailer_headers(headers, conn.request.ref, responses)
]

conn = request_done(conn)
Expand All @@ -821,9 +821,9 @@ defmodule Mint.HTTP1 do
decode(:status, %{conn | state: :status}, data, responses)
end

defp add_trailing_headers([], _request_ref, responses), do: responses
defp add_trailer_headers([], _request_ref, responses), do: responses

defp add_trailing_headers(headers, request_ref, responses),
defp add_trailer_headers(headers, request_ref, responses),
do: [{:headers, request_ref, Enum.reverse(headers)} | responses]

defp add_body(conn, data, responses) do
Expand Down Expand Up @@ -1095,10 +1095,10 @@ defmodule Mint.HTTP1 do
end

def format_error(:trailing_headers_but_not_chunked_encoding) do
"trailing headers can only be sent when using chunked transfer-encoding"
"trailer headers can only be sent when using chunked transfer-encoding"
end

def format_error({:unallowed_trailing_header, {name, value}}) do
"header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailing header"
"header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailer header"
end
end
24 changes: 12 additions & 12 deletions lib/mint/http2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ defmodule Mint.HTTP2 do
@spec stream_request_body(
t(),
Types.request_ref(),
iodata() | :eof | {:eof, trailing_headers :: Types.headers()}
iodata() | :eof | {:eof, trailer_headers :: Types.headers()}
) :: {:ok, t()} | {:error, t(), Types.error()}
def stream_request_body(conn, request_ref, chunk)

Expand Down Expand Up @@ -1097,15 +1097,15 @@ defmodule Mint.HTTP2 do
encode_data(conn, stream_id, "", [:end_stream])
end

defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailing_headers}) do
lowered_headers = downcase_header_names(trailing_headers)
defp encode_stream_body_request_payload(conn, stream_id, {:eof, trailer_headers}) do
lowered_headers = downcase_header_names(trailer_headers)

if unallowed_trailing_header = Util.find_unallowed_trailing_header(lowered_headers) do
error = wrap_error({:unallowed_trailing_header, unallowed_trailing_header})
if unallowed_trailer_header = Util.find_unallowed_trailer_header(lowered_headers) do
error = wrap_error({:unallowed_trailing_header, unallowed_trailer_header})
throw({:mint, conn, error})
end

encode_headers(conn, stream_id, trailing_headers, [:end_headers, :end_stream])
encode_headers(conn, stream_id, trailer_headers, [:end_headers, :end_stream])
end

defp encode_stream_body_request_payload(conn, stream_id, iodata) do
Expand Down Expand Up @@ -1680,24 +1680,24 @@ defmodule Mint.HTTP2 do
{conn, new_responses}
end

# Trailing headers. We don't care about the :status header here.
# Trailer headers. We don't care about the :status header here.
headers when received_first_headers? ->
if end_stream? do
conn = close_stream!(conn, stream.id, :no_error)
headers = headers |> Util.remove_unallowed_trailing_headers() |> join_cookie_headers()
headers = headers |> Util.remove_unallowed_trailer_headers() |> join_cookie_headers()
{conn, [{:done, ref}, {:headers, ref, headers} | responses]}
else
# Trailing headers must set the END_STREAM flag because they're
# Trailer headers must set the END_STREAM flag because they're
# the last thing allowed on the stream (other than RST_STREAM and
# the usual frames).
conn = close_stream!(conn, stream.id, :protocol_error)
debug_data = "trailing headers didn't set the END_STREAM flag"
debug_data = "trailer headers didn't set the END_STREAM flag"
error = wrap_error({:protocol_error, debug_data})
responses = [{:error, stream.ref, error} | responses]
{conn, responses}
end

# Non-trailing headers need to have a :status header, otherwise
# Non-trailer headers need to have a :status header, otherwise
# it's a protocol error.
_headers ->
conn = close_stream!(conn, stream.id, :protocol_error)
Expand Down Expand Up @@ -2216,7 +2216,7 @@ defmodule Mint.HTTP2 do
end

def format_error({:unallowed_trailing_header, {name, value}}) do
"header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailing header"
"header #{inspect(name)} (with value #{inspect(value)}) is not allowed as a trailer header"
end

def format_error(:missing_status_header) do
Expand Down
2 changes: 1 addition & 1 deletion lib/mint/tunnel_proxy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ defmodule Mint.TunnelProxy do
"expected tunnel proxy to return a status between 200 and 299, got: #{inspect(status)}"

{:unexpected_trailing_responses, responses} ->
"tunnel proxy returned unexpected trailing responses: #{inspect(responses)}"
"tunnel proxy returned unexpected trailer responses: #{inspect(responses)}"

http_reason ->
"error when establishing the tunnel proxy connection: " <>
Expand Down
Loading

0 comments on commit 2ce7593

Please sign in to comment.