Skip to content

Implement a more comprehensive way to get connection information #1269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/plug/adapters/test/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ defmodule Plug.Adapters.Test.Conn do
address: {127, 0, 0, 1},
port: 111_317,
ssl_cert: nil
})
}),
sock_data:
get_from_adapter(conn, :get_sock_data, %{
address: {127, 0, 0, 1},
port: 111_318
}),
ssl_data: get_from_adapter(conn, :get_ssl_data, nil)
}

conn_port = if conn.port != 0, do: conn.port, else: 80
Expand Down Expand Up @@ -140,6 +146,14 @@ defmodule Plug.Adapters.Test.Conn do
Map.fetch!(payload, :peer_data)
end

def get_sock_data(payload) do
Map.fetch!(payload, :sock_data)
end

def get_ssl_data(payload) do
Map.fetch!(payload, :ssl_data)
end

def get_http_protocol(payload) do
Map.fetch!(payload, :http_protocol)
end
Expand Down
30 changes: 30 additions & 0 deletions lib/plug/conn.ex
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,36 @@ defmodule Plug.Conn do
adapter.get_peer_data(payload)
end

@doc """
Returns the request sock (local) data.

It raises if the adapter does not provide this metadata.
"""
@spec get_sock_data(t) :: Plug.Conn.Adapter.sock_data()
def get_sock_data(%Conn{adapter: {adapter, payload}}) do
if function_exported?(adapter, :get_sock_data, 1) do
adapter.get_sock_data(payload)
else
raise "get_sock_data not supported by #{inspect(adapter)}"
end
end

@doc """
Returns SSL data for the connection.

If the connection is not SSL, returns nil.

It raises if the adapter does not provide this metadata.
"""
@spec get_ssl_data(t) :: Plug.Conn.Adapter.ssl_data()
def get_ssl_data(%Conn{adapter: {adapter, payload}}) do
if function_exported?(adapter, :get_ssl_data, 1) do
adapter.get_ssl_data(payload)
else
raise "get_ssl_data not supported by #{inspect(adapter)}"
end
end

@doc """
Returns the HTTP protocol and version.

Expand Down
18 changes: 17 additions & 1 deletion lib/plug/conn/adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ defmodule Plug.Conn.Adapter do
port: :inet.port_number(),
ssl_cert: binary | nil
}
@type sock_data :: %{
address: :inet.ip_address(),
port: :inet.port_number()
}
@type ssl_data :: :ssl.connection_info() | nil

@doc """
Function used by adapters to create a new connection.
Expand Down Expand Up @@ -166,10 +171,21 @@ defmodule Plug.Conn.Adapter do
"""
@callback get_peer_data(payload) :: peer_data()

@doc """
Returns sock (local-side) information such as the address and port.
"""
@callback get_sock_data(payload) :: sock_data()

@doc """
Returns details of the negotiated SSL connection, if present. If the connection is not SSL,
returns nil
"""
@callback get_ssl_data(payload) :: ssl_data()

@doc """
Returns the HTTP protocol and its version.
"""
@callback get_http_protocol(payload) :: http_protocol

@optional_callbacks push: 3
@optional_callbacks push: 3, get_sock_data: 1, get_ssl_data: 1
end
18 changes: 18 additions & 0 deletions lib/plug/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,24 @@ defmodule Plug.Test do
end)
end

@doc """
Puts the sock data.
"""
def put_sock_data(conn, sock_data) do
update_in(conn.adapter, fn {adapter, payload} ->
{adapter, Map.put(payload, :sock_data, sock_data)}
end)
end

@doc """
Puts the ssl data.
"""
def put_ssl_data(conn, ssl_data) do
update_in(conn.adapter, fn {adapter, payload} ->
{adapter, Map.put(payload, :ssl_data, ssl_data)}
end)
end

@doc """
Puts a request cookie.
"""
Expand Down
12 changes: 12 additions & 0 deletions test/plug/adapters/test/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ defmodule Plug.Adapters.Test.ConnTest do
assert peer_data == Plug.Conn.get_peer_data(conn)
end

test "use custom sock data" do
sock_data = %{address: {127, 0, 0, 1}, port: 111_318}
conn = conn(:get, "/") |> put_sock_data(sock_data)
assert sock_data == Plug.Conn.get_sock_data(conn)
end

test "use custom ssl data" do
ssl_data = %{address: {127, 0, 0, 1}, port: 111_317}
conn = conn(:get, "/") |> put_ssl_data(ssl_data)
assert ssl_data == Plug.Conn.get_ssl_data(conn)
end

test "push/3 sends message including path and headers" do
ref = make_ref()

Expand Down
7 changes: 7 additions & 0 deletions test/plug/conn_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ defmodule Plug.ConnTest do
port: 111_317,
ssl_cert: nil
}

assert get_sock_data(conn) == %{
address: {127, 0, 0, 1},
port: 111_318
}

assert is_nil(get_ssl_data(conn))
end

test "path_info" do
Expand Down