繁体   English   中英

来自 %Plug.Conn{} 远程 IP 的 Phoenix 解析请求主机

[英]Phoenix Resolve Request Host from %Plug.Conn{} remote ip

我正在尝试做的一些总结。 我有一个在 AWS Elastic Beanstalk 实例上运行的 Phoenix 应用程序,我正在发送包含用于操作(拆分、合并等)的 PDF 的 REST API 请求。 每个请求都保存在数据库中。 这是我的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

通过Graphql,我正在从独立的Rails服务器的请求,并显示该主机名, inserted_at领域, response_code所有要求的领域。

我在尝试解析客户端的主机名时遇到问题。 这是我使用的 Erlang 方法,其中方法参数 remote_ip 相对conn.remote_ip

 {:ok, {:hostent, request_host, _, _, _, _}} = :inet.gethostbyaddr(remote_ip)

此方法返回我的 Phoenix 应用程序的请求主机,而不是客户端的。
我在这里做错了什么?
提前致谢 :)

文档中所述:

[the remote_ip ] 字段旨在被理解例如X-Forwarded-For标头或 HAProxy 的 PROXY 协议的插件覆盖。 它默认为对等方的 IP。

这在此处详细说明:

当您的应用在 Nginx 等代理后运行时,请求看起来像是来自 Nginx,即 IP 将为 127.0.0.1。 同样,如果 Nginx 在 CDN 后面,那么所有请求都将来自 CDN 的 IP。

所以你可以写一个插件来覆盖Plug.Connremote_ip字段。 以下是此类插头的示例。 这个例子是从这篇博文中复制

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

该库可用于简化代理后 IP 地址的处理。

https://github.com/ajvondrak/remote_ip

作者有一篇很好的文章,介绍了适用的用例以及为什么该库可能比滚动自己的库更好:这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM