Напишите пользовательский плагин, который может обрабатывать и возвращать правильную ошибку при неправильном JSON в теле - PullRequest
2 голосов
/ 13 апреля 2020

Я пытаюсь написать плагин, который будет генерировать пользовательскую ошибку, если запрос искажен JSON, что довольно часто встречается в нашем сценарии ios (так как мы используем переменные в почтальоне. Например, иногда нет кавычек за пределами значения, и это приводит к искажению JSON). Единственная помощь, которую я получил, это https://groups.google.com/forum/#! Topic / phoenix-talk / 8F6upFh_lh c, которая, конечно, не работает.

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 не сможет найти тело, если я прочитаю его раньше в моем собственном плагине

1 Ответ

2 голосов
/ 13 апреля 2020

в файле 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
...