From e1d0419ef3f66d79fcfc038419192ced50cba2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Meadows-J=C3=B6nsson?= Date: Mon, 14 Sep 2026 01:15:28 +0200 Subject: [PATCH] Return responses before an error in the order they were parsed HTTP1.handle_data/2 and the HTTP2 stream/2 and recv/3 catch clauses reversed the accumulated responses only on the success path, so the responses in {:error, conn, error, responses} came back newest first. The docs describe them as the responses parsed before the error. --- lib/mint/http1.ex | 2 +- lib/mint/http2.ex | 4 ++-- test/mint/http1/conn_test.exs | 13 ++++++++++++- test/mint/http2/conn_test.exs | 23 ++++++++++++++++++++++- 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/mint/http1.ex b/lib/mint/http1.ex index f21cd4eb..5a10c879 100644 --- a/lib/mint/http1.ex +++ b/lib/mint/http1.ex @@ -550,7 +550,7 @@ defmodule Mint.HTTP1 do {:error, conn, reason, responses} -> conn = internal_close(conn) - {:error, conn, reason, responses} + {:error, conn, reason, Enum.reverse(responses)} end end diff --git a/lib/mint/http2.ex b/lib/mint/http2.ex index 673f58b8..6a89b9a2 100644 --- a/lib/mint/http2.ex +++ b/lib/mint/http2.ex @@ -1045,7 +1045,7 @@ defmodule Mint.HTTP2 do other end catch - :throw, {:mint, conn, error, responses} -> {:error, conn, error, responses} + :throw, {:mint, conn, error, responses} -> {:error, conn, error, Enum.reverse(responses)} end def stream(%__MODULE__{}, _message) do @@ -1091,7 +1091,7 @@ defmodule Mint.HTTP2 do {:error, %{conn | state: :closed}, error, _responses = []} end catch - :throw, {:mint, conn, error, responses} -> {:error, conn, error, responses} + :throw, {:mint, conn, error, responses} -> {:error, conn, error, Enum.reverse(responses)} end def recv(_conn, _byte_count, _timeout) do diff --git a/test/mint/http1/conn_test.exs b/test/mint/http1/conn_test.exs index 44453eda..de7f5011 100644 --- a/test/mint/http1/conn_test.exs +++ b/test/mint/http1/conn_test.exs @@ -217,6 +217,17 @@ defmodule Mint.HTTP1Test do assert_closed_and_released(conn) end + test "responses before an error are returned in order", %{conn: conn} do + {:ok, conn, ref} = HTTP1.request(conn, "GET", "/", [], nil) + response = "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n5\r\nhello\r\nXX" + + assert {:error, conn, %HTTPError{reason: :invalid_chunk_size}, responses} = + HTTP1.stream(conn, {:tcp, conn.socket, response}) + + assert [{:status, ^ref, 200}, {:headers, ^ref, _}, {:data, ^ref, "hello"}] = responses + assert_closed_and_released(conn) + end + test "connection: close", %{conn: conn} do {:ok, conn, ref} = HTTP1.request(conn, "GET", "/", [], nil) response = "HTTP/1.1 200 OK\r\ncontent-length: 1\r\nconnection: close\r\n\r\nX" @@ -572,7 +583,7 @@ defmodule Mint.HTTP1Test do "1\r\nX\r\n0\r\nfoo: " <> String.duplicate("x", 25) <> "\r\n" assert {:error, _conn, %HTTPError{reason: {:max_header_list_size_exceeded, 32, 30}}, - [{:data, _, "X"}, {:headers, _, _}, {:status, _, 200}]} = + [{:status, _, 200}, {:headers, _, _}, {:data, _, "X"}]} = HTTP1.stream(conn, {:tcp, conn.socket, response}) end diff --git a/test/mint/http2/conn_test.exs b/test/mint/http2/conn_test.exs index 12f4a901..0dac8a54 100644 --- a/test/mint/http2/conn_test.exs +++ b/test/mint/http2/conn_test.exs @@ -545,6 +545,27 @@ defmodule Mint.HTTP2Test do assert HTTP2.open?(conn, :read) end + test "responses before a GOAWAY error are returned in order", %{conn: conn} do + {conn, ref} = open_request(conn) + + assert_recv_frames [headers(stream_id: stream_id)] + + assert {:error, %HTTP2{}, error, responses} = + stream_frames(conn, [ + headers( + stream_id: stream_id, + hbf: server_encode_headers([{":status", "200"}]), + flags: set_flags(:headers, [:end_headers]) + ), + data(stream_id: stream_id, data: "hello", flags: set_flags(:data, [])), + goaway(last_stream_id: stream_id, error_code: :protocol_error, debug_data: "") + ]) + + assert_http2_error error, {:server_closed_connection, :protocol_error, ""} + + assert [{:status, ^ref, 200}, {:headers, ^ref, []}, {:data, ^ref, "hello"}] = responses + end + test "with GOAWAY with :no_error and responses after the GOAWAY frame", %{conn: conn} do {conn, ref} = open_request(conn) @@ -2067,7 +2088,7 @@ defmodule Mint.HTTP2Test do window_update(stream_id: stream_id, window_size_increment: 1000) ]) - assert Enum.reverse(responses) == [ + assert responses == [ {:status, ref, 200}, {:headers, ref, []}, {:data, ref, ""},