I can parse the parts, separate the headers with “content-type”. I would like to change the key, say increment it like:
[{"a",10},{"a",20}, {"a", 30}]
to
[{"a-1", 10}, ,{"a-2",20}, {"a-3", 30}]
This should work if I inject it into multipart_to_params
.
Something ugly like this seems to work, needs to adapt the controller now
def filter_content_type(parts) do
filtered =
parts
|> Enum.filter(fn
{_, [{"content-type", _}, {"content-disposition", _}], %Plug.Upload{}} = part ->
part
{_, [_], _} ->
nil
end)
other = Enum.filter(parts, fn elt -> !Enum.member?(filtered, elt) end)
key = elem(hd(filtered), 0)
l = length(filtered)
new_keys = keys = Enum.map(1..l, fn i -> key <> "#{i}" end)
f =
Enum.zip_reduce([filtered, new_keys], [], fn elts, acc ->
[{_, headers, content}, new_key] = elts
[{new_key, headers, content} | acc]
end)
f ++ other
end
if you don’t want to admit zero files, some guards must be used on length(filtered)
, depending what you want to do.
#Plug.Parser.MY_MULTIPART
def multipart_to_params(parts, conn) do
new_parts = filter_content_type(parts)
acc =
for {name, _headers, body} <- Enum.reverse(new_parts),
reduce: Plug.Conn.Query.decode_init() do
acc -> Plug.Conn.Query.decode_each({name, body}, acc)
end
{:ok, Plug.Conn.Query.decode_done(acc, []), conn}
end
#router
pipeline :api do
plug :accepts, ["json"]
plug CORSPlug,
origin: ["http://localhost:3000", "http://localhost:4000"]
plug Plug.Parsers,
parsers: [:urlencoded, :my_multipart, :json],
pass: ["image/jpg", "image/png", "image/webp", "image/jpeg"],
json_decoder: Jason,
multipart_to_params: {Plug.Parsers.MY_MULTIPART, :multipart_to_params, []},
body_reader: {Plug.Parsers.MY_MULTIPART, :read_body, []}
end
so I get in the params:
params #=> %{
"file1" => %Plug.Upload{
path: "/var/folders/mz/91hbds1j23125yksdf67dcgm0000gn/T/plug-1696/multipart-1696237305-491320916966-6",
content_type: "image/png",
filename: "Screenshot2.png"
},
"file2" => %Plug.Upload{
path: "/var/folders/mz/91hbds1j23125yksdf67dcgm0000gn/T/plug-1696/multipart-1696237305-610219718990-6",
content_type: "image/png",
filename: "Screenshot1.png"
},
"w" => "100",
"thumb" => "on"
}
when I receive a FormData. This can be tested quickly:
<html><body>
<form id="f" action="http://localhost:4000/api" method="POST" enctype="multipart/form-data">
<input type="file" name="file" multiple />
<input type="number" name="width" />
<input type="checkbox" name="thumb"/>
<button form="f">Upload</button>
</form>
<script>
const form = ({ method, action } = document.forms[0]);
form.onsubmit= async (e) => {
e.preventDefault();
return fetch(action, { method, body: new FormData(form) })
.then((r) => r.json())
.then(console.log);
});
</script>
</body></html>