[Documentation] Hackney multipart/form-data. How to construct a POST request with multipart/form-data as Content-Type
Opened this issue · 10 comments
You can post with a property list. Just pass th body {form, Props}
where each Key, Values are binaries. That should be enough :)
Thanks @benoitc for the quick response,
But in my case, I have a file upload, which is in document_file
. So I think I must use multipart/form-data
?
Dear @benoitc,
Please correct me if I am wrong.
Since the request I am making has 2 fields, one of them is a file. So, as far as I know, I can use neither {form, Props}
nor {file, File}
({file, File}
is just for a single file without other fields (text fields)).
So currently I should have the following body for multipart
FName = hackney_bstr:to_binary(filename:basename(<<"ibrowse_tmp_file_157091908290916">>)),
MyName = <<"document_file">>,
Disposition = {<<"form-data">>,
[{<<"name">>, <<"\"", MyName/binary, "\"">>},
{<<"filename">>, <<"\"", FName/binary, "\"">>}]},
ExtraHeaders = [],
Body = {multipart, [
{file, <<"ibrowse_tmp_file_157091908290916">>, Disposition, ExtraHeaders},
{<<"document_type">>, <<"ABC">>}
]}
-----> Body is the body of the hackney:request()
But this seems not working!
Really appreciate your help!
I log the request on the server and only receive the file but the text fied (which is document_type
). Still working on it
Dear @benoitc,
For text fields in multipart/form-data
, in my opinion, it is not necessary to have explicit content-type
(comparing to other data).
After spending time reading the source code of hackney
, I found that if a part (in multipart
) is pattern-matched into {Name, Bin}
(which is {<<"document_type">>, <<"ABC">>}
in my case), the Name
is used to determine the content-type
of this part using mimerl:filename(Name)
, which will result in application/octet-stream
as default because <<"document_type">>
has no extension. Thus makes the request wrong.
My workaround is that I temporarily remove the content-type
header for mp_data_header
Currently, it works as I expect.
Please tell me your thought.
Thanks in advance.
fwiw, I got file upload working with the following:
FName = hackney_bstr:to_binary(filename:basename(Path)),
Disposition = {<<"form-data">>,
[{name, <<"field_name">>}, {filename, FName}]},
ReqBody = {multipart, [
{file, hackney_bstr:to_binary(Path), Disposition, []}
]},
{ok, 200, _Hdrs, ResBody} = hackney:post(Url, [], ReqBody, [with_body]),
I can then parse it out using cowboy (1.0.4, because reasons):
read_form(Req, Acc) ->
case cowboy_req:part(Req) of
{ok, Headers, Req2} ->
case cow_multipart:form_data(Headers) of
{file, Field, _FName, _ContentType = <<"application/octet-stream">>, _TE} ->
{ok, Data, Req3} = cowboy_req:part_body(Req2),
read_form(Req3, Acc#{ Field => Data });
{data, Field} ->
{ok, Data, Req3} = cowboy_req:part_body(Req2),
read_form(Req3, Acc#{ Field => Data })
end;
{done, Req2} ->
{Acc, Req2}
end.
@benoitc The problem now is that hackney lib doesn't seem to work w/making requests against express/multer middleware. This is costing me as I tried every format possible yet I'm actually using Httpoison in Elixirlang. I have no problem with the files but no form-data body params are being parsed. Looks like it may be a problem with "Content-Disposition" in the extra headers...
files =
dir
|> File.ls!()
|> Enum.map(fn filename ->
{:file, @root_dir <> "#{id}/" <> filename, {"form-data", [name: "files[]", filename: filename]}, []}
end)
case HTTPoison.post(
apiv1_host <> storage_path,
{:multipart, files ++ [{"id", id, ["content-disposition": ~s(form-data; name="id")]}, {"title", opp_name, ["content-disposition": ~s(form-data; name="title")]}]}
) do
{:ok, res} ->
Logger.info("#{inspect(res.status_code)}")
:ok
{:error, error} ->
Logger.error("#{inspect(error)}")
:error
end
Now to me.... that should work. Otherwise what's going on w/ExtraHeaders as a param here?
Looks like @npkhoa2197 solved the problem if what he says is true.... It's probably why busboy cannot parse requests from Hackney correctly. I guess nobody is really using Express but I have many APIs and don't feel like re-writing everything tonight...
Three cheers to @npkhoa2197 as he nailed it @benoitc. @edgurgel In the Httpoison lib there is a problem interfacing with multer using just {Name, Content} It results in multipart being stamped incorrectly. You MUST use {Name, Content, ExtraHeaders} in all cases to properly adhere to multipart/form-data API specification. See below.
{:multipart, files ++ [{"id", id, ["content-type": "text/plain", "content-disposition": ~s(form-data; name="id")]}, {"title", opp_name, ["content-type": "text/plain", "content-disposition": ~s(form-data; name="title")]}]}