elixirplug

Plug.Parser not reading/parsing JSON body


I am coding an Elixir (1.8) + Plug_Cowboy (2.0.2) + Jason (1.1.2) server. From what I am getting from the documentation, once the Plug parser has passed, I should have everything in body_params. The problem is that accessing conn.body_params in my case returns %Plug.Conn.Unfetched{aspect: :body_params}. Check the code below:

defmodule Test.Router do
  use Plug.Router
  require Logger

  plug :match
  plug Plug.Parsers, parsers: [:json],
                     pass: ["application/json", "text/json"],
                     json_decoder: Jason
  plug :dispatch

  post "/test" do
    Logger.debug inspect(conn.body_params)
    conn
      |> put_resp_content_type("text/plain")
      |> send_resp(204, "Got it")
    end
end

Any idea what is going on?

I test this with:

curl -H "Content-Type: text/json" -d "{one: 1, two: 2}" 127.0.0.1:8080/test

I have tried adding :urlencoded to parsers, or rearranging the plug order, but to no avail.


Solution

  • Plug.Parsers.JSON only handles the application/json content type. This is the reason the body_params is not being populated. Your test JSON is also invalid - the object property names were not quoted.

    curl -H "Content-Type: application/json" -d '{"one": 1, "two": 2}' 127.0.0.1:8080/test
    

    The pass: option to Plug.Parsers instructs it to ignore requests of those types for which there is no parser defined (such as text/json in your case), rather than raise an UnsupportedMediaTypeError, which is what it would usually do. Adding that option was hiding the error.

    You might find it useful to usePlug.Debugger during development - it will give you better information of what is happening in unexpected situations.

    If for some reason you need to be able to parse JSON with the non-standard text/json type (because of requests from software you cannot control, for example), you can define a new parser for that type that just wraps up Plug.Parsers.JSON and rewrites text/json to application/json:

    defmodule WrongJson do
      def init(opts), do: Plug.Parsers.JSON.init(opts)
    
      def parse(conn, "text", "json", params, opts) do
        Plug.Parsers.JSON.parse(conn, "application", "json", params, opts)
      end
    
      def parse(conn, _type, _subtype, _params, _opts), do: {:next, conn}
    end
    

    Then add it to your list of parsers:

    plug Plug.Parsers,
      parsers: [:json, WrongJson],
      json_decoder: Jason
    

    Now text/json will also work.