Skip to content

Commit

Permalink
Fix Mint.HTTP2.close/1 with handshaking connections
Browse files Browse the repository at this point in the history
Closes #392.

This was introduced with the new "handshaking" state added in bb80a94.
  • Loading branch information
whatyouhide committed Mar 2, 2023
1 parent 9d5d883 commit 5bf4f29
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 20 deletions.
5 changes: 5 additions & 0 deletions lib/mint/http2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,11 @@ defmodule Mint.HTTP2 do
{:ok, put_in(conn.state, :closed)}
end

def close(%__MODULE__{state: :handshaking} = conn) do
_ = conn.transport.close(conn.socket)
{:ok, put_in(conn.state, :closed)}
end

def close(%__MODULE__{state: :closed} = conn) do
{:ok, conn}
end
Expand Down
18 changes: 18 additions & 0 deletions test/mint/http2/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,24 @@ defmodule Mint.HTTP2Test do
# Check port status again, after close it should be closed
assert :erlang.port_info(port) == :undefined
end

test "close/1 can close the connection right after starting" do
{:ok, port, server_socket_task} = TestServer.listen_and_accept()

assert {:ok, %HTTP2{} = conn} =
HTTP2.connect(:https, "localhost", port,
transport_opts: [verify: :verify_none],
mode: :passive
)

assert {:ok, server_socket} = Task.await(server_socket_task)
:ok = :ssl.setopts(server_socket, active: true)

assert {:ok, %HTTP2{} = conn} = HTTP2.close(conn)
refute HTTP2.open?(conn)

assert_receive {:ssl_closed, ^server_socket}
end
end

describe "client errors" do
Expand Down
40 changes: 20 additions & 20 deletions test/support/mint/http2/test_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,14 @@ defmodule Mint.HTTP2.TestServer do
@spec connect(keyword(), keyword()) :: {Mint.HTTP2.t(), %__MODULE__{}}
def connect(conn_options, server_settings, options \\ [])
when is_list(conn_options) and is_list(server_settings) and is_list(options) do
ref = make_ref()
parent = self()
ack_flags = Frame.set_flags(:settings, [:ack])

task = Task.async(fn -> start_socket_and_accept(parent, ref) end)
assert_receive {^ref, port}, 100
assert {:ok, port, server_socket_task} = listen_and_accept()

assert {:ok, conn} = HTTP2.connect(:https, "localhost", port, conn_options)
assert %HTTP2{} = conn
assert {:ok, %HTTP2{} = conn} = HTTP2.connect(:https, "localhost", port, conn_options)

{:ok, server_socket} = Task.await(task)
{:ok, server_socket} = Task.await(server_socket_task)
assert :ok = perform_http2_handshake(server_socket)

conn =
case Keyword.get(options, :first_server_frame, :settings) do
Expand Down Expand Up @@ -185,25 +182,28 @@ defmodule Mint.HTTP2.TestServer do
server.socket
end

defp start_socket_and_accept(parent, ref) do
@spec listen_and_accept() :: {:ok, :inet.port_number(), Task.t()}
def listen_and_accept do
{:ok, listen_socket} = :ssl.listen(0, @ssl_opts)
{:ok, {_address, port}} = :ssl.sockname(listen_socket)
send(parent, {ref, port})
parent = self()

# Let's accept a new connection.
{:ok, socket} = :ssl.transport_accept(listen_socket)
task =
Task.async(fn ->
# Let's accept a new connection.
{:ok, socket} = :ssl.transport_accept(listen_socket)

if function_exported?(:ssl, :handshake, 1) do
{:ok, _} = apply(:ssl, :handshake, [socket])
else
:ok = apply(:ssl, :ssl_accept, [socket])
end
if function_exported?(:ssl, :handshake, 1) do
{:ok, _} = apply(:ssl, :handshake, [socket])
else
:ok = apply(:ssl, :ssl_accept, [socket])
end

:ok = perform_http2_handshake(socket)
:ok = :ssl.controlling_process(socket, parent)
{:ok, socket}
end)

# We transfer ownership of the socket to the parent so that this task can die.
:ok = :ssl.controlling_process(socket, parent)
{:ok, socket}
{:ok, port, task}
end

connection_preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
Expand Down

0 comments on commit 5bf4f29

Please sign in to comment.