Читайте необработанное тело из разъема после парсера в Elixir

У меня есть требование проверить дайджест содержимого JSON, отправленного на сервер Phoenix. Для проверки дайджеста необходимо сырое тело. Есть ли способ получить доступ к необработанному контенту в плагине позже в конвейере, чем парсеры. Я хочу добавить следующий плагин проверки дайджеста в конец конвейера, но не могу понять, как он получает доступ к необработанному контенту, который был отправлен.

  plug Plug.Parsers,
    parsers: [:urlencoded, :json],
    pass: ["*/*"],
    json_decoder: Poison

  plug Plug.MethodOverride
  plug Plug.Head
  plug Plug.VerifyDigest

2 ответа

Скопировано из моего ответа здесь.

Вы можете передать обычай :body_reader возможность Plug.Parsers для того, чтобы кэшировать тело для последующего использования.

Вы захотите не читать тело перед парсером, а вместо этого кэшировать тело для чтения позже из вашего плагина, который хочет его хэшировать.

Вариант:

:body_reader - дополнительная замена (или обертка) для Plug.Conn.read_body/2 предоставить функцию, которая дает доступ к необработанному телу до его анализа и удаления. Это в стандартном формате {Module, :function, [args]} (MFA) и по умолчанию {Plug.Conn, :read_body, []},

Пример:

Иногда вы можете захотеть настроить, как парсер читает тело из соединения. Например, вы можете кэшировать тело для последующей проверки, например проверки подписи HTTP. Это может быть достигнуто с помощью специального считывателя тела, который будет считывать тело и сохранять его в соединении, например:

defmodule CacheBodyReader do
  def read_body(conn, opts) do
    {:ok, body, conn} = Plug.Conn.read_body(conn, opts)
    conn = update_in(conn.assigns[:raw_body], &[body | (&1 || [])])
    {:ok, body, conn}
  end
end

который затем может быть установлен как:

plug Plug.Parsers,
  parsers: [:urlencoded, :json],
  pass: ["text/*"],
  body_reader: {CacheBodyReader, :read_body, []},
  json_decoder: Jason

Он был добавлен в Plug v1.5.1.

Я столкнулся с подобной проблемой, и я написал плагин в соответствии с этим (заметьте, я все еще учусь, так что это может быть сделано лучше):

defmodule Esch.Plugs.HMACValidator do
  import Plug.Conn

  def init(default), do: default

  def call(%Plug.Conn{req_headers: req_headers} = conn, _default) do
    hmac_code_tuple = List.keyfind(req_headers, "hmac_token", 0)
    if hmac_code_tuple do
      hmac_code = elem(hmac_code_tuple,1) |> String.downcase

      {:ok, body, conn} = read_body(conn)

      hmac_test_code = :crypto.hmac(:sha512, "secret", body) |> Base.encode16 |> String.downcase

      if hmac_test_code == hmac_code do
        params = Poison.decode!(body)
        conn
        |> assign(:authorized_api_call, true)
        |> struct(%{:body_params => params})
      else
        conn |> put_resp_content_type("text/plain") |> send_resp(401, "Not Authorized") |> halt
      end
    else
      conn
    end
  end

  def call(conn, _default) do
    conn
  end
end

Приведенный выше запрос сравнивает подписанное тело HMAC с подписью HMAC в заголовке запроса.

Я обошел read_body-проблема, анализируя JSON в том же коде, когда подпись соответствует ожидаемой подписи. connДействие пропускается, если запрос не соответствует типичному вызову API (в моем случае не имеет токена заголовка HMAC), поэтому body_params остается непрочитанным.

Затем я подключил вышеупомянутый плагин endpoint.ex перед подключением Plug.Parsers.

...
plug MyApp.Plugs.HMACValidator

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  json_decoder: Poison
...

Я получил некоторое вдохновение от обсуждения в этом выпуске Феникса: Способ чтения тела запроса в виде строки

Другие вопросы по тегам