Skip to content

Instantly share code, notes, and snippets.

@ArthurClemens
Created September 17, 2016 12:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ArthurClemens/dbd70f9b7a4342810d923670a9db0f39 to your computer and use it in GitHub Desktop.
Save ArthurClemens/dbd70f9b7a4342810d923670a9db0f39 to your computer and use it in GitHub Desktop.
Erlang: upload data using multipart/form-data
%% Usage:
%% upload_request(<<"site.com/api/upload">>, <<"path/to/file.png">>, <<"upload">>, <<"image/png">>, [], <<"some-token">>)
%%
%% Usage with RequestData:
%% Payload = [{upload_type, <<"user_picture">>}],
%% PayloadContent = jsx:encode(Payload),
%% RequestData = [
%% {<<"payload">>, PayloadContent}
%% ]
%% upload_request(<<"site.com/api/upload">>, <<"path/to/file.png">>, <<"upload">>, <<"image/png">>, RequestData, <<"some-token">>)
-spec upload_request(URL, FilePath, Name, MimeType, RequestData, AuthorizationToken) -> {ok, binary()} | {error, list()} when
URL:: binary(),
FilePath:: binary(),
Name:: binary(),
MimeType:: binary(),
RequestData:: list(),
AuthorizationToken:: binary().
upload_request(URL, FilePath, Name, MimeType, RequestData, AuthorizationToken) ->
Method = post,
Filename = filename:basename(FilePath),
{ok, Data} = file:read_file(FilePath),
Boundary = generate_uuid(),
RequestBody = format_multipart_formdata(Data, RequestData, Name, [Filename], MimeType, Boundary),
ContentType = "multipart/form-data; boundary=" ++ binary_to_list(Boundary),
ContentLength = integer_to_list(length(binary_to_list(RequestBody))),
Headers = [
{"Content-Length", ContentLength},
case AuthorizationToken =/= undefined of
true -> {"Authorization", "Bearer " ++ binary_to_list(AuthorizationToken)};
false -> {}
end
],
HTTPOptions = [],
Options = [{body_format, binary}],
inets:start(),
httpc:request(Method, {binary_to_list(URL), Headers, ContentType, RequestBody}, HTTPOptions, Options).
-spec format_multipart_formdata(Data, Params, Name, FileNames, MimeType, Boundary) -> binary() when
Data:: binary(),
Params:: list(),
Name:: binary(),
FileNames:: list(),
MimeType:: binary(),
Boundary:: binary().
format_multipart_formdata(Data, Params, Name, FileNames, MimeType, Boundary) ->
StartBoundary = erlang:iolist_to_binary([<<"--">>, Boundary]),
LineSeparator = <<"\r\n">>,
WithParams = lists:foldl(fun({Key, Value}, Acc) ->
erlang:iolist_to_binary([
Acc,
StartBoundary, LineSeparator,
<<"Content-Disposition: form-data; name=\"">>, Key, <<"\"">>, LineSeparator, LineSeparator,
Value, LineSeparator
])
end, <<"">>, Params),
WithPaths = lists:foldl(fun(FileName, Acc) ->
erlang:iolist_to_binary([
Acc,
StartBoundary, LineSeparator,
<<"Content-Disposition: form-data; name=\"">>, Name, <<"\"; filename=\"">>, FileName, <<"\"">>, LineSeparator,
<<"Content-Type: ">>, MimeType, LineSeparator, LineSeparator,
Data,
LineSeparator
])
end, WithParams, FileNames),
erlang:iolist_to_binary([WithPaths, StartBoundary, <<"--">>, LineSeparator]).
% Thanks to https://github.com/afiskon/erlang-uuid-v4
generate_uuid() ->
<<A:32, B:16, C:16, D:16, E:48>> = crypto:rand_bytes(16),
Str = io_lib:format("~8.16.0b-~4.16.0b-4~3.16.0b-~4.16.0b-~12.16.0b",
[A, B, C band 16#0fff, D band 16#3fff bor 16#8000, E]),
list_to_binary(Str).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment