[英]Phoenix Resolve Request Host from %Plug.Conn{} remote ip
A little summary of what I'm trying to do.我正在尝试做的一些总结。 I have a Phoenix app running on an AWS Elastic Beanstalk instance and I'm sending REST API requests containing PDFs for manipulation (splitting, merging etc).
我有一个在 AWS Elastic Beanstalk 实例上运行的 Phoenix 应用程序,我正在发送包含用于操作(拆分、合并等)的 PDF 的 REST API 请求。 Each Request is saved in the database.
每个请求都保存在数据库中。 This is what my
requests
schema looks like:这是我的
requests
架构的样子:
schema "requests" do
field :body, :string
field :endpoint, :string
field :method, :string
field :request_host, :string
field :response_body, :string
field :response_code, :integer
field :work_group_id, :integer
field :identifier, :string
field :responded_at, :utc_datetime
timestamps()
end
Through Graphql, I'm making a request from a separate RAILS server and displaying the hostname, inserted_at
field, and response_code
field of all the requests.通过Graphql,我正在从独立的Rails服务器的请求,并显示该主机名,
inserted_at
领域, response_code
所有要求的领域。
I'm experiencing problems trying to resolve the client's host name.我在尝试解析客户端的主机名时遇到问题。 This is the Erlang method I'm using where the method argument remote_ip is relatively
conn.remote_ip
:这是我使用的 Erlang 方法,其中方法参数 remote_ip 相对
conn.remote_ip
:
{:ok, {:hostent, request_host, _, _, _, _}} = :inet.gethostbyaddr(remote_ip)
This method returns my Phoenix app's request host and not the clients'.此方法返回我的 Phoenix 应用程序的请求主机,而不是客户端的。
What am I doing wrong here?我在这里做错了什么?
Thanks in advance :)提前致谢 :)
As described in the docs :如文档中所述:
[The
remote_ip
] field is meant to be overwritten by plugs that understand eg theX-Forwarded-For
header or HAProxy's PROXY protocol.[the
remote_ip
] 字段旨在被理解例如X-Forwarded-For
标头或 HAProxy 的 PROXY 协议的插件覆盖。 It defaults to peer's IP.它默认为对等方的 IP。
This is elaborated upon here :这在此处详细说明:
When your app is running behind a proxy like Nginx, then the request will look like it's coming from Nginx, ie the IP will be 127.0.0.1.
当您的应用在 Nginx 等代理后运行时,请求看起来像是来自 Nginx,即 IP 将为 127.0.0.1。 Similarly, If Nginx is behind a CDN, then all the requests will come from the IP of the CDN.
同样,如果 Nginx 在 CDN 后面,那么所有请求都将来自 CDN 的 IP。
So you can write a plug to overwrite the remote_ip
field of the Plug.Conn
.所以你可以写一个插件来覆盖
Plug.Conn
的remote_ip
字段。 Following is an example of such a plug.以下是此类插头的示例。 This example is copied from this blog post .
这个例子是从这篇博文中复制的。
defmodule MyApp.Plug.PublicIp do
@moduledoc "Get public IP address of request from x-forwarded-for header"
@behaviour Plug
@app :my_app
def init(opts), do: opts
def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
def call(conn, _opts) do
process(conn, Plug.Conn.get_req_header(conn, "x-forwarded-for"))
end
def process(conn, []) do
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
end
def process(conn, vals) do
if Application.get_env(@app, :trust_x_forwarded_for, false) do ip_address = get_ip_address(conn, vals) # Rewrite standard remote_ip field with value from header
# See https://hexdocs.pm/plug/Plug.Conn.html
conn = %{conn | remote_ip: ip_address}
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(ip_address)))
else
Plug.Conn.assign(conn, :ip, to_string(:inet.ntoa(get_peer_ip(conn))))
end
end
defp get_ip_address(conn, vals)
defp get_ip_address(conn, []), do: get_peer_ip(conn)
defp get_ip_address(conn, [val | _]) do
# Split into multiple values
comps = val
|> String.split(~r{\s*,\s*}, trim: true)
|> Enum.filter(&(&1 != "unknown")) # Get rid of "unknown" values
|> Enum.map(&(hd(String.split(&1, ":")))) # Split IP from port, if any
|> Enum.filter(&(&1 != "")) # Filter out blanks
|> Enum.map(&(parse_address(&1))) # Parse address into :inet.ip_address tuple
|> Enum.filter(&(is_public_ip(&1))) # Elminate internal IP addreses, e.g. 192.168.1.1
case comps do
[] -> get_peer_ip(conn)
[comp | _] -> comp
end
end
@spec get_peer_ip(Plug.Conn.t) :: :inet.ip_address
defp get_peer_ip(conn) do
{ip, _port} = conn.peer
ip
end
@spec parse_address(String.t) :: :inet.ip_address
defp parse_address(ip) do
case :inet.parse_ipv4strict_address(to_charlist(ip)) do
{:ok, ip_address} -> ip_address
{:error, :einval} -> :einval
end
end
# Whether the input is a valid, public IP address
# http://en.wikipedia.org/wiki/Private_network
@spec is_public_ip(:inet.ip_address | atom) :: boolean
defp is_public_ip(ip_address) do
case ip_address do
{10, _, _, _} -> false
{192, 168, _, _} -> false
{172, second, _, _} when second >= 16 and second <= 31 -> false
{127, 0, 0, _} -> false
{_, _, _, _} -> true
:einval -> false
end
end
end
This library is available to simplify handling of IP addresses behind proxies.该库可用于简化代理后 IP 地址的处理。
https://github.com/ajvondrak/remote_ip https://github.com/ajvondrak/remote_ip
The author has a nice writeup of applicable use-cases and why the library is probably a better choice than rolling your own: here作者有一篇很好的文章,介绍了适用的用例以及为什么该库可能比滚动自己的库更好:这里
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.