Skip to content
This repository has been archived by the owner on Jun 26, 2024. It is now read-only.

Commit

Permalink
Merge branch 'feat-s3-multipart-upload' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Kazuhiro Ogura committed Oct 23, 2011
2 parents 4c7a6f2 + c399453 commit 20acb3d
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 11 deletions.
102 changes: 93 additions & 9 deletions src/erlaws_s3.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
-export([list_buckets/0, create_bucket/1, create_bucket/2, delete_bucket/1]).
-export([list_contents/1, list_contents/2, put_object/5, put_file/5, get_object/2]).
-export([info_object/2, delete_object/2]).
-export([initiate_mp_upload/4, complete_mp_upload/4, abort_mp_upload/3, upload_part/6]).

%% include record definitions
-include_lib("xmerl/include/xmerl.hrl").
Expand Down Expand Up @@ -195,10 +196,90 @@ put_object(Bucket, Key, Data, ContentType, Metadata) when is_integer(hd(ContentT
put_object(Bucket, Key, Data, HTTPHeaders, Metadata) ->
try genericRequest(put, Bucket, Key, [], Metadata, HTTPHeaders, Data) of
{ok, Headers, _Body} ->
RequestId = case lists:keytake(?S3_REQ_ID_HEADER, 1, Headers) of
{value, {_, ReqId}, _} -> ReqId;
_ -> "" end,
{ok, #s3_object_info{key=Key, size=size(Data)}, {requestId, RequestId}}
{ok,
#s3_object_info{key=Key, size=iolist_size(Data),
etag=proplists:get_value("etag", Headers)},
{requestId, proplists:get_value(?S3_REQ_ID_HEADER, Headers, "")}}
catch
throw:{error, Descr} ->
{error, Descr}
end.

%% @doc
%% Initiates multipart upload.
%% @end
-spec initiate_mp_upload(Bucket::string(), Key::string(),
HTTPHeaders::[{string(), string()}],
Metadata::[{string(), string()}]) ->
{ok, UploadId::string(), ReqId::string()}.
initiate_mp_upload(Bucket, Key, HTTPHeaders, Metadata) ->
try genericRequest(post, Bucket, Key, [{"uploads", ""}],
Metadata, HTTPHeaders, <<>>) of
{ok, Headers, Body} ->
{XmlDoc, _Rest} = xmerl_scan:string(binary_to_list(Body)),
[_XBucket|_] = xmerl_xpath:string("/InitiateMultipartUploadResult/Bucket/text()", XmlDoc),
[_XKey|_] = xmerl_xpath:string("/InitiateMultipartUploadResult/Key/text()", XmlDoc),
[XUploadId|_] = xmerl_xpath:string("/InitiateMultipartUploadResult/UploadId/text()", XmlDoc),
{ok, XUploadId#xmlText.value, proplists:get_value(?S3_REQ_ID_HEADER, Headers, "")}
catch
throw:{error, Descr} ->
{error, Descr}
end.

%% @doc
%% Completes multipart upload.
%% @end
-spec complete_mp_upload(Bucket::string(), Key::string(), UploadId::string(),
[{PartNum::integer(), ETag::string()}]) -> ok.
complete_mp_upload(Bucket, Key, UploadId, Parts) ->
F = fun({PartNum, ETag}) ->
{'Part', [],
[
{'PartNumber', [integer_to_list(PartNum)]},
{'ETag', [ETag]}
]
}
end,
Req = {'CompleteMultipartUpload', [], [F(X) || X <- Parts]},
XMLReqBody = xmerl:export_simple([Req], xmerl_xml),
try genericRequest(post, Bucket, Key,
[{"uploadId", UploadId}], [], [], XMLReqBody) of
{ok, _Headers, _Body} -> ok
catch
throw:{error, Descr} ->
{error, Descr}
end.

%% @doc
%% Aborts multipart upload.
%% @end
-spec abort_mp_upload(Bucket::string(), Key::string(), UploadId::string()) ->
ok.
abort_mp_upload(Bucket, Key, UploadId) ->
try genericRequest(delete, Bucket, Key,
[{"uploadId", UploadId}], [], [], <<>>) of
{ok, _Headers, _Body} -> ok
catch
throw:{error, Descr} ->
{error, Descr}
end.

%% @doc
%% Uploads a part in multipart upload.
%% @end
-spec upload_part(Bucket::string(), Key::string(), PartNum::integer(),
UploadId::string(), Data::binary(),
HTTPHeaders::[{string(), string()}]) -> ok.
upload_part(Bucket, Key, PartNum, UploadId, Data, HTTPHeaders) ->
try genericRequest(put, Bucket, Key,
[{"partNumber", integer_to_list(PartNum)},
{"uploadId", UploadId}],
[], HTTPHeaders, Data) of
{ok, Headers, _Body} ->
{ok,
#s3_object_info{key=Key, size=iolist_size(Data),
etag=proplists:get_value("etag", Headers)},
{requestId, proplists:get_value(?S3_REQ_ID_HEADER, Headers, "")}}
catch
throw:{error, Descr} ->
{error, Descr}
Expand Down Expand Up @@ -354,10 +435,10 @@ buildContentHeaders(Contents) when is_integer(Contents) ->
[{"Content-Length", integer_to_list(Contents)}];
% Detect gzip header and put appropriate Content-Encoding. Questionable?..
buildContentHeaders(<<16#1f, 16#8b, _/binary>> = Contents) ->
[{"Content-Length", integer_to_list(size(Contents))},
[{"Content-Length", integer_to_list(iolist_size(Contents))},
{"Content-Encoding", "gzip"}];
buildContentHeaders(Contents) ->
[{"Content-Length", integer_to_list(size(Contents))}].
[{"Content-Length", integer_to_list(iolist_size(Contents))}].

buildMetadataHeaders(Metadata) ->
lists:foldl(fun({Key, Value}, Acc) ->
Expand Down Expand Up @@ -391,8 +472,8 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata,
MethodString = string:to_upper( atom_to_list(Method) ),
Url = buildUrl(Bucket,Path,QueryParams),

ContentMD5 = case Body of
<<>> -> "";
ContentMD5 = case iolist_size(Body) of
0 -> "";
_ -> binary_to_list(base64:encode(erlang:md5(Body)))
end,

Expand All @@ -411,7 +492,9 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata,

Signature = sign(SecretAccessKey,
stringToSign(MethodString, ContentMD5, ContentType, Date,
Bucket, Path, Headers )),
Bucket,
Path ++ erlaws_util:queryParams(QueryParams),
Headers )),

FinalHeaders = [ {"Authorization","AWS " ++ AccessKey ++ ":" ++ Signature },
{"Host", buildHost(Bucket) },
Expand All @@ -423,6 +506,7 @@ genericRequest( Method, Bucket, Path, QueryParams, Metadata,
get -> { Url, FinalHeaders };
head -> { Url, FinalHeaders };
put -> { Url, FinalHeaders, ContentType, Body };
post -> { Url, FinalHeaders, ContentType, Body };
delete -> { Url, FinalHeaders }
end,

Expand Down
13 changes: 11 additions & 2 deletions src/erlaws_util.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ mkEnumeration(Values, Separator) ->

queryParams( [] ) -> "";
queryParams( ParamList ) ->
"?" ++ mkEnumeration([[url_encode(Param), "=", url_encode(Value)]
|| {Param, Value} <- ParamList], "&" ).
L0 = lists:foldl(fun({P, V}, Acc) ->
case V of
"" ->
[url_encode(P)|Acc];
_ ->
[url_encode(P) ++ "=" ++ url_encode(V)|Acc]
end
end,
[],
lists:keysort(1, ParamList)),
"?" ++ mkEnumeration(lists:reverse(L0), "&").

%% The following code is taken from the ibrowse Http client
%% library.
Expand Down

0 comments on commit 20acb3d

Please sign in to comment.