Skip to content

Commit 852d90e

Browse files
committed
Update
1 parent b499e0d commit 852d90e

File tree

6 files changed

+166
-92
lines changed

6 files changed

+166
-92
lines changed

lib/mint/core/util.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ defmodule Mint.Core.Util do
33

44
alias Mint.Types
55

6+
defguard is_timeout(timeout)
7+
when (is_integer(timeout) and timeout >= 0) or timeout == :infinity
8+
69
@spec hostname(keyword(), String.t()) :: String.t()
710
def hostname(opts, address) when is_list(opts) do
811
case Keyword.fetch(opts, :hostname) do

lib/mint/http.ex

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ defmodule Mint.HTTP do
123123

124124
alias Mint.{Types, TunnelProxy, UnsafeProxy}
125125
alias Mint.Core.{Transport, Util}
126+
require Util
126127

127128
@behaviour Mint.Core.Conn
128129

@@ -872,6 +873,100 @@ defmodule Mint.HTTP do
872873
| {:error, t(), Types.error(), [Types.response()]}
873874
def recv(conn, byte_count, timeout), do: conn_module(conn).recv(conn, byte_count, timeout)
874875

876+
@version Mix.Project.config()[:version]
877+
878+
@doc """
879+
Receives response from the socket in a blocking way.
880+
881+
This function receives a response for the request identified by `request_ref` on the connection
882+
`conn`.
883+
884+
`timeout` is the maximum time to wait before returning an error.
885+
886+
This function is a convenience for repeatedly calling `Mint.HTTP.recv/3`. The result is either:
887+
888+
* `{:ok, conn, response}` where `conn` is the updated connection and `response` is a map
889+
with the following keys:
890+
891+
* `:status` - HTTP response status, an integer.
892+
893+
* `:headers` - HTTP response headers, a list of tuples `{header_name, header_value}`.
894+
895+
* `:body` - HTTP response body, a binary.
896+
897+
* `{:error, conn, reason}` where `conn` is the updated connection and `reason` is the cause
898+
of the error. It is important to store the returned connection over the old connection in
899+
case of errors too, because the state of the connection might change when there are errors
900+
as well. An error when sending a request **does not** necessarily mean that the connection
901+
is closed. Use `open?/1` to verify that the connection is open.
902+
903+
## Examples
904+
905+
iex> {:ok, conn} = Mint.HTTP.connect(:https, "httpbin.org", 443, mode: :passive)
906+
iex> {:ok, conn, request_ref} = Mint.HTTP.request(conn, "GET", "/user-agent", [], nil)
907+
iex> {:ok, _conn, response} = Mint.HTTP.recv_response(conn, request_ref, 5000)
908+
iex> response
909+
%{
910+
status: 200,
911+
headers: [{"date", ...}, ...],
912+
body: "{\\n \\"user-agent\\": \\"#{@version}\\"\\n}\\n"
913+
}
914+
"""
915+
@spec recv_response(t(), Types.request_ref(), timeout()) ::
916+
{:ok, t(), response} | {:error, Types.error()}
917+
when response: %{
918+
status: non_neg_integer(),
919+
headers: [{binary(), binary()}],
920+
body: binary()
921+
}
922+
def recv_response(conn, ref, timeout)
923+
when is_reference(ref) and Util.is_timeout(timeout) do
924+
recv_response([], %{status: nil, headers: [], body: ""}, conn, ref, timeout)
925+
end
926+
927+
defp recv_response([{:status, ref, status} | rest], acc, conn, ref, timeout) do
928+
acc = put_in(acc.status, status)
929+
recv_response(rest, acc, conn, ref, timeout)
930+
end
931+
932+
defp recv_response([{:headers, ref, headers} | rest], acc, conn, ref, timeout) do
933+
acc = update_in(acc.headers, &(&1 ++ headers))
934+
recv_response(rest, acc, conn, ref, timeout)
935+
end
936+
937+
defp recv_response([{:data, ref, data} | rest], acc, conn, ref, timeout) do
938+
acc = update_in(acc.body, &(&1 <> data))
939+
recv_response(rest, acc, conn, ref, timeout)
940+
end
941+
942+
defp recv_response([{:done, ref} | _rest], acc, conn, ref, _timeout) do
943+
{:ok, conn, acc}
944+
end
945+
946+
defp recv_response([{:error, ref, error} | _rest], _acc, conn, ref, _timeout) do
947+
{:error, conn, error}
948+
end
949+
950+
# Ignore entries from other requests.
951+
defp recv_response([_entry | rest], acc, conn, ref, timeout) do
952+
recv_response(rest, acc, conn, ref, timeout)
953+
end
954+
955+
defp recv_response([], acc, conn, ref, timeout) do
956+
start_time = System.monotonic_time(:millisecond)
957+
958+
with {:ok, conn, entries} <- recv(conn, 0, timeout) do
959+
timeout =
960+
if is_integer(timeout) do
961+
timeout - System.monotonic_time(:millisecond) - start_time
962+
else
963+
timeout
964+
end
965+
966+
recv_response(entries, acc, conn, ref, timeout)
967+
end
968+
end
969+
875970
@doc """
876971
Changes the mode of the underlying socket.
877972

lib/mint/http1.ex

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -529,67 +529,6 @@ defmodule Mint.HTTP1 do
529529
"Use Mint.HTTP.set_mode/2 to set the connection to passive mode"
530530
end
531531

532-
@doc """
533-
TODO
534-
535-
## Examples
536-
537-
iex> {:ok, conn} = Mint.HTTP1.connect(:https, "httpbin.org", 443, mode: :passive)
538-
iex> {:ok, conn, ref} = Mint.HTTP1.request(conn, "GET", "/status/user-agent", [], nil)
539-
iex> {:ok, _conn, response} = Mint.HTTP1.recv_response(conn, ref, 5000)
540-
iex> response
541-
%{
542-
status: 201,
543-
headers: [
544-
{"date", ...},
545-
...
546-
],
547-
body: "{\\n \\"user-agent\\": \\"mint/1.6.2\\"\\n}\\n"
548-
}
549-
"""
550-
def recv_response(conn, ref, timeout) do
551-
recv_response([], %{status: nil, headers: [], body: ""}, conn, ref, timeout)
552-
end
553-
554-
defp recv_response([{:status, ref, status} | rest], acc, conn, ref, timeout) do
555-
acc = put_in(acc.status, status)
556-
recv_response(rest, acc, conn, ref, timeout)
557-
end
558-
559-
defp recv_response([{:headers, ref, headers} | rest], acc, conn, ref, timeout) do
560-
acc = update_in(acc.headers, &(&1 ++ headers))
561-
recv_response(rest, acc, conn, ref, timeout)
562-
end
563-
564-
defp recv_response([{:data, ref, data} | rest], acc, conn, ref, timeout) do
565-
acc = update_in(acc.body, &(&1 <> data))
566-
recv_response(rest, acc, conn, ref, timeout)
567-
end
568-
569-
defp recv_response([{:done, ref} | _], acc, conn, ref, _timeout) do
570-
{:ok, conn, acc}
571-
end
572-
573-
# Ignore entries from other requests.
574-
defp recv_response([_entry | rest], acc, conn, ref, timeout) do
575-
recv_response(rest, acc, conn, ref, timeout)
576-
end
577-
578-
defp recv_response([], acc, conn, ref, timeout) do
579-
start_time = System.monotonic_time(:millisecond)
580-
581-
with {:ok, conn, entries} <- recv(conn, 0, timeout) do
582-
timeout =
583-
if is_integer(timeout) do
584-
timeout - System.monotonic_time(:millisecond) - start_time
585-
else
586-
timeout
587-
end
588-
589-
recv_response(entries, acc, conn, ref, timeout)
590-
end
591-
end
592-
593532
@doc """
594533
See `Mint.HTTP.set_mode/2`.
595534
"""

test/http_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
defmodule Mint.HTTPTest do
22
use ExUnit.Case, async: true
3-
doctest Mint.HTTP
3+
doctest Mint.HTTP, except: [recv_response: 3]
44
end

test/mint/http1/conn_test.exs

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,9 @@ defmodule Mint.HTTP1Test do
55

66
require Mint.HTTP
77

8-
test "foo" do
9-
{:ok, _} =
10-
Bandit.start_link(
11-
port: 4000,
12-
plug: fn conn, _ ->
13-
Plug.Conn.send_resp(conn, 200, "hi")
14-
end,
15-
startup_log: false
16-
)
17-
18-
{:ok, conn} = HTTP1.connect(:http, "localhost", 4000, mode: :passive)
19-
{:ok, conn, ref} = HTTP1.request(conn, "GET", "/", [], nil)
20-
{:ok, conn, response} = HTTP1.recv_response(conn, ref, 5000)
21-
22-
assert %{
23-
status: 200,
24-
headers: [
25-
{"date", _},
26-
{"content-length", "2"},
27-
{"vary", "accept-encoding"},
28-
{"cache-control", "max-age=0, private, must-revalidate"}
29-
],
30-
body: "hi"
31-
} = response
32-
33-
assert HTTP1.open?(conn)
34-
end
35-
368
setup do
379
{:ok, port, server_ref} = TestServer.start()
38-
assert {:ok, conn} = HTTP1.connect(:http, "localhost", port, mode: :passive)
10+
assert {:ok, conn} = HTTP1.connect(:http, "localhost", port)
3911
assert_receive {^server_ref, server_socket}
4012

4113
[conn: conn, port: port, server_ref: server_ref, server_socket: server_socket]
@@ -488,6 +460,28 @@ defmodule Mint.HTTP1Test do
488460
assert responses == [{:status, ref, 200}]
489461
end
490462

463+
test "starting a connection in :passive mode and using Mint.HTTP.recv_response/3",
464+
%{port: port, server_ref: server_ref} do
465+
assert {:ok, conn} = HTTP1.connect(:http, "localhost", port, mode: :passive)
466+
assert_receive {^server_ref, server_socket}
467+
468+
{:ok, conn, ref} = HTTP1.request(conn, "GET", "/", [], nil)
469+
470+
:ok = :gen_tcp.send(server_socket, "HTTP/1.1 200 OK\r\n")
471+
:ok = :gen_tcp.send(server_socket, "content-type: text/plain\r\n")
472+
:ok = :gen_tcp.send(server_socket, "content-length: 10\r\n\r\n")
473+
:ok = :gen_tcp.send(server_socket, "hello")
474+
:ok = :gen_tcp.send(server_socket, "world")
475+
476+
assert {:ok, _conn, response} = Mint.HTTP.recv_response(conn, ref, 100)
477+
478+
assert response == %{
479+
body: "helloworld",
480+
headers: [{"content-type", "text/plain"}, {"content-length", "10"}],
481+
status: 200
482+
}
483+
end
484+
491485
test "changing the connection mode with set_mode/2",
492486
%{conn: conn, server_socket: server_socket} do
493487
assert_raise ArgumentError, ~r"can't use recv/3", fn ->

test/mint/http2/conn_test.exs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,11 @@ defmodule Mint.HTTP2Test do
11291129
hbf: hbf,
11301130
flags: set_flags(:headers, [:end_headers])
11311131
),
1132-
data(stream_id: stream_id, data: "some data", flags: set_flags(:data, [])),
1132+
data(
1133+
stream_id: stream_id,
1134+
data: "some data",
1135+
flags: set_flags(:data, [])
1136+
),
11331137
headers(
11341138
stream_id: stream_id,
11351139
hbf: trailer_hbf1,
@@ -2083,6 +2087,45 @@ defmodule Mint.HTTP2Test do
20832087
assert HTTP2.open?(conn)
20842088
end
20852089

2090+
@tag connect_options: [mode: :passive]
2091+
test "starting a connection with :passive mode and using Mint.HTTP.recv_response/3", %{
2092+
conn: conn
2093+
} do
2094+
{conn, ref} = open_request(conn)
2095+
2096+
assert_recv_frames [headers(stream_id: stream_id)]
2097+
2098+
data =
2099+
server_encode_frames([
2100+
headers(
2101+
stream_id: stream_id,
2102+
hbf:
2103+
server_encode_headers([
2104+
{":status", "200"},
2105+
{"content-type", "text/plain"},
2106+
{"content-length", "10"}
2107+
]),
2108+
flags: set_flags(:headers, [:end_headers])
2109+
),
2110+
data(
2111+
stream_id: stream_id,
2112+
data: "helloworld",
2113+
flags: set_flags(:data, [:end_stream])
2114+
)
2115+
])
2116+
2117+
:ok = :ssl.send(server_get_socket(), data)
2118+
assert {:ok, _conn, response} = Mint.HTTP.recv_response(conn, ref, 100)
2119+
2120+
assert response == %{
2121+
body: "helloworld",
2122+
headers: [{"content-type", "text/plain"}, {"content-length", "10"}],
2123+
status: 200
2124+
}
2125+
2126+
assert HTTP2.open?(conn)
2127+
end
2128+
20862129
test "changing the mode of a connection with set_mode/2", %{conn: conn} do
20872130
assert_raise ArgumentError, ~r"^can't use recv/3", fn ->
20882131
HTTP2.recv(conn, 0, 100)

0 commit comments

Comments
 (0)