json.ex (3218B)
1 defmodule Plug.Parsers.JSON do 2 @moduledoc """ 3 Parses JSON request body. 4 5 JSON documents that aren't maps (arrays, strings, numbers, etc) are parsed 6 into a `"_json"` key to allow proper param merging. 7 8 An empty request body is parsed as an empty map. 9 10 ## Options 11 12 All options supported by `Plug.Conn.read_body/2` are also supported here. 13 They are repeated here for convenience: 14 15 * `:length` - sets the maximum number of bytes to read from the request, 16 defaults to 8_000_000 bytes 17 * `:read_length` - sets the amount of bytes to read at one time from the 18 underlying socket to fill the chunk, defaults to 1_000_000 bytes 19 * `:read_timeout` - sets the timeout for each socket read, defaults to 20 15_000ms 21 22 So by default, `Plug.Parsers` will read 1_000_000 bytes at a time from the 23 socket with an overall limit of 8_000_000 bytes. 24 """ 25 26 @behaviour Plug.Parsers 27 28 @impl true 29 def init(opts) do 30 {decoder, opts} = Keyword.pop(opts, :json_decoder) 31 {body_reader, opts} = Keyword.pop(opts, :body_reader, {Plug.Conn, :read_body, []}) 32 decoder = validate_decoder!(decoder) 33 {body_reader, decoder, opts} 34 end 35 36 defp validate_decoder!(nil) do 37 raise ArgumentError, "JSON parser expects a :json_decoder option" 38 end 39 40 defp validate_decoder!({module, fun, args} = mfa) 41 when is_atom(module) and is_atom(fun) and is_list(args) do 42 arity = length(args) + 1 43 44 if Code.ensure_compiled(module) != {:module, module} do 45 raise ArgumentError, 46 "invalid :json_decoder option. The module #{inspect(module)} is not " <> 47 "loaded and could not be found" 48 end 49 50 if not function_exported?(module, fun, arity) do 51 raise ArgumentError, 52 "invalid :json_decoder option. The module #{inspect(module)} must " <> 53 "implement #{fun}/#{arity}" 54 end 55 56 mfa 57 end 58 59 defp validate_decoder!(decoder) when is_atom(decoder) do 60 validate_decoder!({decoder, :decode!, []}) 61 end 62 63 defp validate_decoder!(decoder) do 64 raise ArgumentError, 65 "the :json_decoder option expects a module, or a three-element " <> 66 "tuple in the form of {module, function, extra_args}, got: #{inspect(decoder)}" 67 end 68 69 @impl true 70 def parse(conn, "application", subtype, _headers, {{mod, fun, args}, decoder, opts}) do 71 if subtype == "json" or String.ends_with?(subtype, "+json") do 72 apply(mod, fun, [conn, opts | args]) |> decode(decoder) 73 else 74 {:next, conn} 75 end 76 end 77 78 def parse(conn, _type, _subtype, _headers, _opts) do 79 {:next, conn} 80 end 81 82 defp decode({:ok, "", conn}, _decoder) do 83 {:ok, %{}, conn} 84 end 85 86 defp decode({:ok, body, conn}, {module, fun, args}) do 87 try do 88 apply(module, fun, [body | args]) 89 rescue 90 e -> raise Plug.Parsers.ParseError, exception: e 91 else 92 terms when is_map(terms) -> 93 {:ok, terms, conn} 94 95 terms -> 96 {:ok, %{"_json" => terms}, conn} 97 end 98 end 99 100 defp decode({:more, _, conn}, _decoder) do 101 {:error, :too_large, conn} 102 end 103 104 defp decode({:error, :timeout}, _decoder) do 105 raise Plug.TimeoutError 106 end 107 108 defp decode({:error, _}, _decoder) do 109 raise Plug.BadRequestError 110 end 111 end