Search code examples
androidmultipartretrofit

Multipart Request using Retrofit 1.8.0 not working


I have like 4 days, trying to make a Multipart Request using Retrofit 1.8.0 in android with any success. My interface looks something like this

@Multipart
@POST("/posts/add.json") 
void addComment(
  @Part("id") String id,
  @Part("post[body]") String body,
  @Part("post[attachment]") TypedFile attachment,
  Callback<Map<String, String>> callback );

But, in the server side, I receive the following

Parameters: {"id"=># <File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-0>, "post"=>{"body"=>#<File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-1>, "attachment"=>#<File:/var/folders/z0/0ggjvvfj4t1fdsvbxf3lc9pw0000gn/T/RackMultipart9853-2>}, "controller"=>"posts", "action"=>"add", "format"=>"json"}

As you can see, the file part is sending it in every part but and I'm missing the parameters' value of id and post[body]

Here it's what Retrofit is trying to send

 02-06 15:01:16.213    32545-822/com.myapp D/Retrofit﹕ --fe41634b-6826-4ee4-95cb-65efb0ca66c2
Content-Disposition: form-data; name="id"
Content-Type: text/plain; charset=UTF-8
Content-Length: 3
Content-Transfer-Encoding: binary
189
--fe41634b-6826-4ee4-95cb-65efb0ca66c2
Content-Disposition: form-data; name="post[body]"
Content-Type: text/plain; charset=UTF-8
Content-Length: 4
Content-Transfer-Encoding: binary
test
--fe41634b-6826-4ee4-95cb-65efb0ca66c2
Content-Disposition: form-data; name="post[attachment]"; filename="IMG_20140203_144358.jpg"
Content-Type: image/jpg
Content-Length: 1615460
Content-Transfer-Encoding: binary
����/�Exif����MM��*���������

Here it is what the HttpMime library is sending in the Multipart, the difference is the "Content-Transfer-Encoding" header against Retrofit

Content-Disposition: form-data; name="id"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

Content-Disposition: form-data; name=“post[body]"
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 8bit

Content-Disposition: form-data; name=“post[attachment]"; filename="images.jpg"
Content-Type: image/jpg
Content-Transfer-Encoding: binary

Any clue? Thanks in advance

-------------------------------SOLUTION----------------------------------

At the end, I resolved this way, actually my answer is pretty close to @lazypig, it was a good guideline

The only thing that I changed was his class "ByteArrayTypedOutput"

I created a class called "MultipartTypedOutputCustom" http://pastie.org/10549360

And this is, how it looks my interface now

"PostsRetrofitAPI.java" class

@POST("/posts/add.json")
    void addComment(@Body MultipartTypedOutputCustom parts,
                    Callback<Map<String, String>> callback);

"PostsService.java"class

//Properties
private PostsRetrofitAPI mApi;
...

    @Override
        public void addComment(ServiceResponseHandler<Map<String, String>> handler, String id, String body, TypedFile attachment) {
           MultipartTypedOutputCustom parts = new MultipartTypedOutputCustom();
           parts.addPart("id", new TypedString(id));
           parts.addPart("post[body]", new TypedString(body));
           parts.addPart("post[attachment]", attachment);
    objectRetrofitCallback= new ObjectRetrofitCallback(handler, ServerError.class, ClientError.class);
            mApi.addComment(parts, objectRetrofitCallback);
        }

Solution

  • I did this instead for the interface

    interface MultipartFormDataService {
        @POST("/{uploadPath}")
        void multipartFormDataSend(
                @EncodedPath("uploadPath") String uploadPath,
                @Body MultipartTypedOutput multipartTypedOutput,
                Callback<String> cb);
    }
    

    Then later when I call it, it looks like this

    // creating the Multipart body using retrofit
    MultipartTypedOutput multipartTypedOutput = new MultipartTypedOutput();
    TypedString idParam = new TypedString("[ID Value]")
    TypedString bodyParam = new TypedString("[Body text]")
    ByteArrayTypedOutput byteMultipartTypedOut = new ByteArrayTypedOutput(bytes)
    
    // add parts
    multipartTypedOutput.addPart("id", idParam);
    multipartTypedOutput.addPart("body", bodyParam);
    multipartTypedOutput.addPart("attachment", extraParamTypedString);
    
    // send
    multipartService.multipartFormDataSend(
                    "[TARGET URL]",
                    multipartTypedOutput,
                aCallback);
    

    My ByteArrayTypedOutput was simple

    public class ByteArrayTypedOutput implements TypedOutput {
    
        private MultipartFormMetadata metadata;
        private byte[] imageData;
    
        public ByteArrayTypedOutput(MultipartFormMetadata metadata, byte[] imageData)
            this.metadata = metadata;
            this.imageData = imageData;
        }
    
        @Override
        public String fileName() {
            return metadata.fileName;
        }
    
        @Override
        public String mimeType() {
            return metadata.fileMimeType;
        }
    
        @Override
        public long length() {
            return imageData.length;
        }
    
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
             outputStream.write(imageData);
        }
    }