编写自定义插件,它可以处理 return 正文中格式错误 JSON 的正确错误

Write custom Plug which can handle and return proper error on malformed JSON in the body

我正在尝试编写一个插件,如果请求格式不正确,它将生成自定义错误 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 request

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

在 endpoint.ex 文件中添加一个自定义主体 reader 和您的自定义插件按顺序

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()

定义一个custom body reader

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