Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multipart Upload to Amazon S3 using Retrofit

I'm attempting to convert all my asynctasks and HttpPost code to use Retrofit, so far so good, but I'm having trouble uploading user files to an amazon s3 bucket. The file upload has two parts:

  1. Query api to get a upload_url and amazon upload params.
  2. Upload File to specified location with params provided in first call.

Here's an example list of parameters provided to me from the first call. According to the documentation, these values can change or not be included, and the 2nd api call must call use these params in the exact order they were provided.

"AWSAccessKeyId": "some_id",    
"key": "/users/1234/files/profile_pic.jpg",
"acl": "private",
"Filename": "profile_pic.jpg",
"Policy": "some_opaque_string",
"Signature": "another_opaque_string",
"Content-Type": "image/jpeg"

In order to deal with the dynamic content. I created a custom converter to have retrofit return me a LinkedHashMap in my first API call.

public class CustomConverter implements Converter {

@Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {       
        ...
        Type mapType = new TypeToken<LinkedHashMap<String, String>>(){}.getType();
        return new Gson().fromJson(JSON_STRING, mapType);    
}

Then in the second api call, Once I have these values I create a FormUrlEncodedTypedOutput by iterating over the HashMap and adding each item.

FormUrlEncodedTypedOutput params = new FormUrlEncodedTypedOutput();
for (Map.Entry<String, String> entry : uploadParams.entrySet()) {
            params.addField(KEY, VALUE);
}

Everything up to here seems to be working. I'm getting the necessary upload params and the ordering seems to be consistent. I'm a bit less sure about how I have my multipart retrofit call setup. I then use that inside a synchronous retrofit call inside an intentservice.

@Multipart
    @POST("/")
    Response uploadFile(@Part ("whatdoesthisdo?") FormUrlEncodedTypedOutput params, @Part("File") TypedFile file);

This results in a Amazon error.

"code" : "InvalidArgument"
"message" : "Bucket POST must contain a field named 'key'.  If it is specified, please check the order of the fields."

I've been googling and it seems like Amazon prefers the "key" value to be first? However, if i put the "key" in front of "AWSAccessKeyId" I get a 403 unauthorized error. Do i have my retrofit call setup correctly? If someone could help me figure this out, I'd appreciate it. It's taken a few days to convert most of my uploading code over to retrofit and if I've been stuck on this for a while.

Thanks!

like image 309
android_student Avatar asked Apr 18 '15 00:04

android_student


1 Answers

Solution was to use a @PartMap instead of the FormUrlEncodedTypedOutput.

@Multipart
@POST("/")
Response uploadFile(@PartMap LinkedHashMap<String,String> params, @Part("File") TypedFile file);
like image 165
android_student Avatar answered Oct 29 '22 11:10

android_student