mirror of
https://github.com/edgurgel/httparrot
synced 2025-04-05 08:12:31 -04:00
add support for multipart/form-data POST,PUT,PATCH similar to httpbin's
This commit is contained in:
parent
dac364cd7d
commit
16eff596f1
2 changed files with 184 additions and 1 deletions
|
@ -21,7 +21,8 @@ defmodule HTTParrot.PHandler do
|
|||
{[{{"application", "json", :*}, :post_binary},
|
||||
{{"application", "octet-stream", :*}, :post_binary},
|
||||
{{"text", "plain", :*}, :post_binary},
|
||||
{{"application", "x-www-form-urlencoded", :*}, :post_form}], req, state}
|
||||
{{"application", "x-www-form-urlencoded", :*}, :post_form},
|
||||
{{"multipart", "form-data", :*}, :post_multipart}], req, state}
|
||||
end
|
||||
|
||||
def content_types_provided(req, state) do
|
||||
|
@ -57,4 +58,70 @@ defmodule HTTParrot.PHandler do
|
|||
defp response(info, body) do
|
||||
info ++ body |> JSEX.encode!
|
||||
end
|
||||
|
||||
def post_multipart(req, _state) do
|
||||
{:ok, parts, req} = handle_multipart(req)
|
||||
|
||||
filter = fn({type, _name, _body}, type_atom) -> type == type_atom end
|
||||
reducer = fn({_type, name, body}, acc) -> acc ++ [{name, body}] end
|
||||
|
||||
# the other post handlers return a list with a single empty tuple if
|
||||
# there's no data for forms, so let's match that behavior...
|
||||
normalize = fn(parts) -> if parts == [], do: [{}], else: parts end
|
||||
|
||||
file_parts = Enum.filter(parts, &(filter.(&1, :file)))
|
||||
|> Enum.reduce([], &(reducer.(&1, &2)))
|
||||
|> normalize.()
|
||||
|
||||
form_parts = Enum.filter(parts, &(filter.(&1, :form)))
|
||||
|> Enum.reduce([], &(reducer.(&1, &2)))
|
||||
|> normalize.()
|
||||
|
||||
post(req, [form: form_parts, files: file_parts, data: "", json: nil])
|
||||
end
|
||||
|
||||
defp handle_multipart(req, parts \\ []) do
|
||||
case :cowboy_req.part(req) do
|
||||
{:done, req} -> {:ok, parts, req}
|
||||
{:ok, headers, req} ->
|
||||
content_disposition = List.keyfind(headers, "content-disposition", 0)
|
||||
if content_disposition != nil do
|
||||
case parse_content_disposition_header(content_disposition) do
|
||||
%{:type => "form-data", "name" => name, "filename" => _filename} ->
|
||||
{:ok, file, req} = handle_multipart_body(req)
|
||||
handle_multipart(req, parts ++ [{:file, name, file}])
|
||||
%{:type => "form-data", "name" => name} ->
|
||||
{:ok, form_part, req} = handle_multipart_body(req)
|
||||
handle_multipart(req, parts ++ [{:form, name, form_part}])
|
||||
_ ->
|
||||
{:ok, parts, req}
|
||||
end
|
||||
else
|
||||
{:ok, parts, req}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_multipart_body(req, parts \\ []) do
|
||||
case :cowboy_req.part_body(req) do
|
||||
{:ok, data, req} ->
|
||||
{:ok, Enum.join(parts ++ [data]), req}
|
||||
{:more, data, req} ->
|
||||
handle_multipart_body(req, parts ++ [data])
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_content_disposition_header(header) do
|
||||
parts = elem(header, 1) |> String.split(";")
|
||||
|
||||
type = Enum.at(parts, 0)
|
||||
parts = Enum.drop(parts, 1)
|
||||
|
||||
Enum.reduce(parts, %{:type => type}, fn part, acc ->
|
||||
[key, value] = String.split(part, "=")
|
||||
key = String.strip(key)
|
||||
value = String.strip(value) |> String.replace("\"", "")
|
||||
Map.put(acc, key, value)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,4 +70,120 @@ defmodule HTTParrot.PHandlerTest do
|
|||
|
||||
assert validate HTTParrot.GeneralRequestInfo
|
||||
end
|
||||
|
||||
test "returns json with general info and P[OST, ATCH, UT] octet-stream body data for multipart request (simple)" do
|
||||
expect(:cowboy_req, :part, fn req ->
|
||||
case req do
|
||||
:req1 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"key1\""}], :req2}
|
||||
:req3 ->
|
||||
{:done, :req4}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :part_body, [{[:req2], {:ok, "value1", :req3}}])
|
||||
expect(:cowboy_req, :parse_header,
|
||||
[{["content-type", :req4],
|
||||
{:ok, {"multipart", "form-data", [{"boundary", "----WebKitFormBoundary8BEQxJvZANFsvRV9"}]}, :req5}}])
|
||||
expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {[:info], :req6})
|
||||
|
||||
expect(JSEX, :is_json?, 1, false)
|
||||
expect(JSEX, :encode!, [{[[:info, {:form, [{"key1", "value1"}]}, {:files, [{}]}, {:data, ""}, {:json, nil}]], :response}])
|
||||
|
||||
expect(:cowboy_req, :set_resp_body, [{[:response, :req6], :req7}])
|
||||
|
||||
assert post_multipart(:req1, :state) == {true, :req7, nil}
|
||||
assert validate HTTParrot.GeneralRequestInfo
|
||||
end
|
||||
|
||||
test "returns json with general info and P[OST, ATCH, UT] octet-stream body data for multipart requests (multiple parts)" do
|
||||
expect(:cowboy_req, :part, fn req ->
|
||||
case req do
|
||||
:req1 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"key1\""}], :req2}
|
||||
:req3 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"key2\""}], :req4}
|
||||
:req5 ->
|
||||
{:done, :req6}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :part_body, fn req ->
|
||||
case req do
|
||||
:req2 -> {:ok, "value1", :req3}
|
||||
:req4 -> {:ok, "value2", :req5}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :parse_header,
|
||||
[{["content-type", :req6],
|
||||
{:ok, {"multipart", "form-data", [{"boundary", "----WebKitFormBoundary8BEQxJvZANFsvRV9"}]}, :req7}}])
|
||||
expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {[:info], :req8})
|
||||
|
||||
expect(JSEX, :is_json?, 1, false)
|
||||
expect(JSEX, :encode!, [{[[:info, {:form, [{"key1", "value1"}, {"key2", "value2"}]}, {:files, [{}]}, {:data, ""}, {:json, nil}]], :response}])
|
||||
|
||||
expect(:cowboy_req, :set_resp_body, [{[:response, :req8], :req9}])
|
||||
|
||||
assert post_multipart(:req1, :state) == {true, :req9, nil}
|
||||
assert validate HTTParrot.GeneralRequestInfo
|
||||
end
|
||||
|
||||
test "returns json with general info and P[OST, UT, ATCH] file data (one file)" do
|
||||
expect(:cowboy_req, :part, fn req ->
|
||||
case req do
|
||||
:req1 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"file1\"; filename=\"testdata.txt\""}, {"content-type", "text/plain"}], :req2}
|
||||
:req3 ->
|
||||
{:done, :req4}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :part_body, [{[:req2], {:ok, "here is some cool\ntest data.", :req3}}])
|
||||
expect(:cowboy_req, :parse_header,
|
||||
[{["content-type", :req4],
|
||||
{:ok, {"multipart", "form-data", [{"boundary", "----WebKitFormBoundary8BEQxJvZANFsvRV9"}]}, :req5}}])
|
||||
expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {[:info], :req6})
|
||||
|
||||
expect(JSEX, :is_json?, 1, false)
|
||||
expect(JSEX, :encode!, [{[[:info, {:form, [{}]}, {:files, [{"file1", "here is some cool\ntest data."}]}, {:data, ""}, {:json, nil}]], :response}])
|
||||
|
||||
expect(:cowboy_req, :set_resp_body, [{[:response, :req6], :req7}])
|
||||
|
||||
assert post_multipart(:req1, :state) == {true, :req7, nil}
|
||||
assert validate HTTParrot.GeneralRequestInfo
|
||||
end
|
||||
|
||||
test "returns json with general info and P[OST, UT, ATCH] file data (form-data plus one file)" do
|
||||
expect(:cowboy_req, :part, fn req ->
|
||||
case req do
|
||||
:req1 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"key1\""}], :req2}
|
||||
:req3 ->
|
||||
{:ok, [{"content-disposition", "form-data; name=\"file1\"; filename=\"testdata.txt\""}, {"content-type", "text/plain"}], :req4}
|
||||
:req5 ->
|
||||
{:done, :req6}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :part_body, fn req ->
|
||||
case req do
|
||||
:req2 -> {:ok, "value1", :req3}
|
||||
:req4 -> {:ok, "here is some cool\ntest data", :req5}
|
||||
end
|
||||
end)
|
||||
|
||||
expect(:cowboy_req, :parse_header,
|
||||
[{["content-type", :req6],
|
||||
{:ok, {"multipart", "form-data", [{"boundary", "----WebKitFormBoundary8BEQxJvZANFsvRV9"}]}, :req7}}])
|
||||
expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {[:info], :req8})
|
||||
|
||||
expect(JSEX, :is_json?, 1, false)
|
||||
expect(JSEX, :encode!, [{[[:info, {:form, [{"key1", "value1"}]}, {:files, [{"file1", "here is some cool\ntest data"}]}, {:data, ""}, {:json, nil}]], :response}])
|
||||
|
||||
expect(:cowboy_req, :set_resp_body, [{[:response, :req8], :req9}])
|
||||
|
||||
assert post_multipart(:req1, :state) == {true, :req9, nil}
|
||||
assert validate HTTParrot.GeneralRequestInfo
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue