【问题标题】:Error when trying to send HTTP status code in Phoenix尝试在 Phoenix 中发送 HTTP 状态代码时出错
【发布时间】:2017-01-24 17:05:06
【问题描述】:

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

除非我尝试设置响应的 HTTP 状态代码,否则 API 端点会按预期工作。当我包含 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

为什么会发生这种情况,我哪里出错了?

【问题讨论】:

    标签: elixir phoenix-framework cowboy


    【解决方案1】:

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

    自定义状态代码

    Plug 允许覆盖或添加状态代码,以允许 Plug 或未直接指定的新代码 它的适配器。添加或覆盖状态代码是通过 :plug 应用程序的混合配置。例如,要 覆盖 404 状态代码的现有 404 原因短语
    (默认为“未找到”)并添加新的 451 状态码,如下 可以指定配置:

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

    由于此配置是特定于 Plug 的,因此需要重新编译 Plug 才能进行更改:这不会发生 自动作为依赖项不会自动重新编译时 他们的配置更改。 重新编译插件:

    MIX_ENV=prod mix deps.compile plug
    

    在许多函数中可以用来代替状态码的原子是从 状态码的原因短语。有了上面的配置, 以下将全部工作:

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

    即使 404 已被覆盖,:not_found 原子仍然可以 用于将状态设置为 404 以及新原子 :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")
    

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

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

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

    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/965https://github.com/elixir-lang/plug/issues/451

    【讨论】:

    • 感谢您的帮助并为此提出问题。现在,我会坚持使用已知的 HTTP 代码并将自定义代码作为 JSON 响应的一部分发送。
    • @MohideenImranKhan 我为此发送了 PR,它已合并到 master 中,因此它将在 Plug 的下一个版本中可用。 github.com/elixir-lang/plug/pull/470
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 2012-12-04
    • 2020-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多