尝试在 Phoenix 中发送 HTTP 状态代码时出错

Error when trying to send HTTP status code in Phoenix

我是 Phoenix/Elixir 初学者,正在尝试编写一个 API 以允许用户在我的应用程序中注册。

API 端点按预期工作,除非我尝试设置响应的 HTTP 状态代码。当我包含 A、B 和 C 行(在下面的代码中指示)时,我得到一个 FunctionClauseError 和消息 no function clause matching in :cowboy_req.status/1.

完整的报错信息如下:

[error] #PID<0.344.0> running App.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /api/user/
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in :cowboy_req.status/1
        (cowboy) src/cowboy_req.erl:1272: :cowboy_req.status(451)
        (cowboy) src/cowboy_req.erl:1202: :cowboy_req.response/6
        (cowboy) src/cowboy_req.erl:933: :cowboy_req.reply_no_compress/8
        (cowboy) src/cowboy_req.erl:888: :cowboy_req.reply/4
        (plug) lib/plug/adapters/cowboy/conn.ex:34: Plug.Adapters.Cowboy.Conn.send_resp/4
        (plug) lib/plug/conn.ex:356: Plug.Conn.send_resp/1
        (app) web/controllers/user_controller.ex:1: App.UserController.action/2
        (app) web/controllers/user_controller.ex:1: App.UserController.phoenix_controller_app/2
        (app) lib/app/endpoint.ex:1: App.Endpoint.instrument/4
        (app) lib/phoenix/router.ex:261: App.Router.dispatch/2
        (app) web/router.ex:1: App.Router.do_call/2
        (app) lib/app/endpoint.ex:1: App.Endpoint.phoenix_app/1
        (app) lib/plug/debugger.ex:122: App.Endpoint."call (overridable 3)"/2
        (app) lib/app/endpoint.ex:1: App.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

我的代码如下:

defmodule App.UserController do
  use App.Web, :controller

  import Ecto.Changeset

  alias App.User
  alias App.Session

  def create(conn, params) do
    changeset = User.changeset(%User{}, params)

    case Repo.insert(changeset) do
      {:ok, _user} ->
        email = get_field(changeset, :email)
        password = get_field(changeset, :password)

        # Log on user upon sign up
        session_changeset = Session.changeset(%Session{
          email: email,
          password: password
        })
        result = Repo.insert(session_changeset)

        case result do
          {:ok, session} ->
            conn
            |> put_resp_cookie("SID", session.session_id)
            |> put_status(201)  # line A
            |> render("signup.json", data: %{
                 changeset: changeset
               })
          {:error, changeset} ->
            conn
            |> put_status(251)  # line B
            |> render("signup.json", data: %{
                 changeset: changeset
               })
        end
      {:error, changeset} ->
        conn
        |> put_status(451)  # line C
        |> render("signup.json", data: %{
             changeset: changeset
           })
    end
  end

end

为什么会这样,我哪里出错了?

编辑 自 2016 年 10 月 22 日起,现在可以在 Plug master 上使用。这是文档的相关部分以供参考:

Custom status codes

Plug allows status codes to be overridden or added in order to allow new codes not directly specified by Plug or its adapters. Adding or overriding a status code is done through the Mix configuration of the :plug application. For example, to override the existing 404 reason phrase for the 404 status code
("Not Found" by default) and add a new 451 status code, the following config can be specified:

  config :plug, :statuses, %{
    404 => "Actually This Was Found",
    451 => "Unavailable For Legal Reasons"
  }

As this configuration is Plug specific, Plug will need to be recompiled for the changes to take place: this will not happen automatically as dependencies are not automatically recompiled when their configuration changes. To recompile Plug:

MIX_ENV=prod mix deps.compile plug

The atoms that can be used in place of the status code in many functions are inflected from the reason phrase of the status code. With the above configuration, the following will all work:

  put_status(conn, :not_found)                     # 404
  put_status(conn, :actually_this_was_found)       # 404
  put_status(conn, :unavailable_for_legal_reasons) # 451

Even though 404 has been overridden, the :not_found atom can still be used to set the status to 404 as well as the new atom :actually_this_was_found inflected from the reason phrase "Actually This Was Found".


Cowboy 手动指定 HTTP 响应代码并匹配指定的整数。

https://github.com/ninenines/cowboy/blob/1.0.x/src/cowboy_req.erl#L1318

允许二进制文件,但是这样做:

conn
|> put_status("451 Unavailable For Legal Reasons")

不会工作,因为插件只允许整数或已知原子。

这应该被认为是一个错误。您可以尝试在我链接的文件中获取 Cowboy 的拉取请求。

如果无法将 PR 合并到 Cowboy 中,也可以通过转换状态在 Cowboy 适配器的 Plug 中执行(这是一个幼稚的解决方案):

status = if (status == 451) do
  "451 Unavailable For Legal Reasons"
else
  status
end

在此文件中https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy/conn.ex#L33

另见 https://github.com/ninenines/cowboy/issues/965 and https://github.com/elixir-lang/plug/issues/451