I am a Phoenix/Elixir beginner and am trying to write an API to allow users to sign up in my application.
The API endpoint works as expected unless I try to set the HTTP status code of the response. When I include lines A, B and C (indicated in the code below), I get a FunctionClauseError
with the message no function clause matching in :cowboy_req.status/1
.
The complete error message is as follows:
[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
My code is as follows:
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
Why does this happen and where am I going wrong?
It means the request contains incorrect syntax or cannot be fulfilled. 5. 5xx: Server Error. It means the server failed to fulfill an apparently valid request. HTTP status codes are extensible and HTTP applications are not required to understand the meaning of all the registered status codes.
status() method on the res object will set a HTTP status code of 404 . To send the status code to the client-side, you can method chain using the . send() method. The status code 404 tells the client side that the data requested is not found.
Edit as of 22nd October 2016 this is now possible on Plug master. Here is the relevant section of the docs for reference:
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 manually specifies the HTTP response code and matches on the integer specified.
https://github.com/ninenines/cowboy/blob/1.0.x/src/cowboy_req.erl#L1318
A binary is permitted, however doing:
conn
|> put_status("451 Unavailable For Legal Reasons")
Won't work as plug only permits an integer or a known atom.
This should probably be considered a bug. You can try and get a pull request into Cowboy in the file I linked.
If getting a PR merged into Cowboy isn't possible, it can also be performed in Plug for the Cowboy adapter by transforming the status (this is a naive solution):
status = if (status == 451) do
"451 Unavailable For Legal Reasons"
else
status
end
In this file https://github.com/elixir-lang/plug/blob/master/lib/plug/adapters/cowboy/conn.ex#L33
See also https://github.com/ninenines/cowboy/issues/965 and https://github.com/elixir-lang/plug/issues/451
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With