3

我正在尝试编写一个插件,如果请求的 JSON 格式错误,这将生成一个自定义错误,这在我们的场景中很常见(因为我们在邮递员中使用变量。例如,有时值之外没有引号,它会导致格式错误JSON)。我得到的唯一帮助是https://groups.google.com/forum/#!topic/phoenix-talk/8F6upFh_lhc这当然不起作用。

defmodule PogioApi.Plug.PrepareParse do
  import Plug.Conn
  @env Application.get_env(:application_api, :env)

  def init(opts) do
    opts
  end

  def call(conn, opts) do
    %{method: method} = conn
    # TODO: check for PUT aswell
    if method in ["POST"] and not(@env in [:test]) do
      {:ok, body, _conn} = Plug.Conn.read_body(conn)
      case Jason.decode(body) do
        {:ok, _result} -> conn
        {:error, _reason} ->
          error = %{message: "Malformed JSON in the body"}
          conn
          |> put_resp_header("content-type", "application/json; charset=utf-8")
          |> send_resp(400, Jason.encode!(error))
          |> halt
      end
    else
      conn
    end
  end
end

这条线

{:ok, body, _conn} = Plug.Conn.read_body(conn)

如何正确阅读和解析正文。我知道在 POST 中,我们总是会收到 format=JSON 请求

问题:问题是正文只能读取一次。如果我之前在自定义插件中阅读过,Plug.Parses 将无法找到正文

4

1 回答 1

4

在 endpoint.ex 文件中添加自定义正文阅读器和您的自定义插件,如下所示

plug Api.Plug.PrepareParse # should be called before Plug.Parsers

plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  body_reader: {CacheBodyReader, :read_body, []}, # CacheBodyReader option is also needed
  json_decoder: Phoenix.json_library()

定义自定义正文阅读器

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

然后你的自定义解析准备

defmodule Api.Plug.PrepareParse do
  import Plug.Conn
  @env Application.get_env(:application_api, :env)
  @methods ~w(POST PUT PATCH PUT)

  def init(opts) do
    opts
  end

  def call(conn, opts) do
    %{method: method} = conn

    if method in @methods and not (@env in [:test]) do
      case Plug.Conn.read_body(conn, opts) do
        {:error, :timeout} ->
          raise Plug.TimeoutError

        {:error, _} ->
          raise Plug.BadRequestError

        {:more, _, conn} ->
          # raise Plug.PayloadTooLargeError, conn: conn, router: __MODULE__
          error = %{message: "Payload too large error"}
          render_error(conn, error)

        {:ok, "" = body, conn} ->
          body = "{}" // otherwise error
          update_in(conn.assigns[:raw_body], &[body | &1 || []])

        {:ok, body, conn} ->
          case Jason.decode(body) do
            {:ok, _result} ->
              update_in(conn.assigns[:raw_body], &[body | &1 || []])

            {:error, _reason} ->
              error = %{message: "Malformed JSON in the body"}
              render_error(conn, error)
          end
      end
    else
      conn
    end
  end

  def render_error(conn, error) do
    conn
    |> put_resp_header("content-type", "application/json; charset=utf-8")
    |> send_resp(400, Jason.encode!(error))
    |> halt
  end
end

参考文献少:

  1. https://elixirforum.com/t/how-to-read-request-body-multiple-times-during-request-handling/3845
  2. https://elixirforum.com/t/how-do-you-put-a-request-body-in-a-plug-conn/8584
  3. https://elixirforum.com/t/write-malformed-json-in-the-body-plug/30578
于 2020-04-13T09:23:34.260 回答