From 9d2a226b99e5b9e47f7614e52c19ea556bd1a081 Mon Sep 17 00:00:00 2001 From: Mat Trudel Date: Fri, 23 May 2025 17:23:06 -0400 Subject: [PATCH 1/4] Define optional get_sock_data/1 and get_ssl_data/1 callbacks --- lib/plug/adapters/test/conn.ex | 16 ++++++++++++++- lib/plug/conn.ex | 28 +++++++++++++++++++++++++++ lib/plug/conn/adapter.ex | 18 ++++++++++++++++- lib/plug/test.ex | 18 +++++++++++++++++ test/plug/adapters/test/conn_test.exs | 12 ++++++++++++ test/plug/conn_test.exs | 7 +++++++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lib/plug/adapters/test/conn.ex b/lib/plug/adapters/test/conn.ex index e396ceb3..630f8542 100644 --- a/lib/plug/adapters/test/conn.ex +++ b/lib/plug/adapters/test/conn.ex @@ -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 @@ -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 diff --git a/lib/plug/conn.ex b/lib/plug/conn.ex index b163ed1a..e13f126a 100644 --- a/lib/plug/conn.ex +++ b/lib/plug/conn.ex @@ -638,6 +638,34 @@ defmodule Plug.Conn do adapter.get_peer_data(payload) end + @doc """ + Returns the request sock (local) data if one is present. + """ + @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)}." <> + "You should either delete the call to `get_sock_data/1` or switch to an " <> + "adapter that does support this call such as Bandit." + end + end + + @doc """ + Returns SSL data for the connection if present. If the connection is not SSL, returns nil + """ + @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)}." <> + "You should either delete the call to `get_ssl_data/1` or switch to an " <> + "adapter that does support this call such as Bandit." + end + end + @doc """ Returns the HTTP protocol and version. diff --git a/lib/plug/conn/adapter.ex b/lib/plug/conn/adapter.ex index bf7780aa..c4e90369 100644 --- a/lib/plug/conn/adapter.ex +++ b/lib/plug/conn/adapter.ex @@ -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. @@ -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 diff --git a/lib/plug/test.ex b/lib/plug/test.ex index 0abed751..7d8925c6 100644 --- a/lib/plug/test.ex +++ b/lib/plug/test.ex @@ -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. """ diff --git a/test/plug/adapters/test/conn_test.exs b/test/plug/adapters/test/conn_test.exs index 84a80fe1..9c940ed1 100644 --- a/test/plug/adapters/test/conn_test.exs +++ b/test/plug/adapters/test/conn_test.exs @@ -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() diff --git a/test/plug/conn_test.exs b/test/plug/conn_test.exs index 371021ed..c89c1812 100644 --- a/test/plug/conn_test.exs +++ b/test/plug/conn_test.exs @@ -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 From d090571f47ad15eb73368f22f5480474f65f9b88 Mon Sep 17 00:00:00 2001 From: Mat Trudel Date: Sat, 24 May 2025 10:58:07 -0400 Subject: [PATCH 2/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/plug/conn.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plug/conn.ex b/lib/plug/conn.ex index e13f126a..d4b38bcb 100644 --- a/lib/plug/conn.ex +++ b/lib/plug/conn.ex @@ -653,7 +653,11 @@ defmodule Plug.Conn do end @doc """ - Returns SSL data for the connection if present. If the connection is not SSL, returns nil + 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 From 23060db2da19f87a3a8347983edaf99076298183 Mon Sep 17 00:00:00 2001 From: Mat Trudel Date: Sat, 24 May 2025 10:58:26 -0400 Subject: [PATCH 3/4] Update lib/plug/conn.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/plug/conn.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plug/conn.ex b/lib/plug/conn.ex index d4b38bcb..d45f9363 100644 --- a/lib/plug/conn.ex +++ b/lib/plug/conn.ex @@ -639,7 +639,9 @@ defmodule Plug.Conn do end @doc """ - Returns the request sock (local) data if one is present. + 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 From ce3f492c259b88889ece943399ea1b607a6e5236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 24 May 2025 18:09:58 +0200 Subject: [PATCH 4/4] Update conn.ex --- lib/plug/conn.ex | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/plug/conn.ex b/lib/plug/conn.ex index d45f9363..141b4c6d 100644 --- a/lib/plug/conn.ex +++ b/lib/plug/conn.ex @@ -648,9 +648,7 @@ defmodule Plug.Conn 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)}." <> - "You should either delete the call to `get_sock_data/1` or switch to an " <> - "adapter that does support this call such as Bandit." + raise "get_sock_data not supported by #{inspect(adapter)}" end end @@ -666,9 +664,7 @@ defmodule Plug.Conn 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)}." <> - "You should either delete the call to `get_ssl_data/1` or switch to an " <> - "adapter that does support this call such as Bandit." + raise "get_ssl_data not supported by #{inspect(adapter)}" end end