Skip to content

Instantly share code, notes, and snippets.

@ArthurClemens
Created September 17, 2016 12:07
Show Gist options
  • Select an option

  • Save ArthurClemens/dbd70f9b7a4342810d923670a9db0f39 to your computer and use it in GitHub Desktop.

Select an option

Save ArthurClemens/dbd70f9b7a4342810d923670a9db0f39 to your computer and use it in GitHub Desktop.

Revisions

  1. ArthurClemens created this gist Sep 17, 2016.
    74 changes: 74 additions & 0 deletions multipart-upload.erl
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,74 @@
    %% 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).