Retrofit: Add a way to set filename on multipart requests

Created on 29 Sep 2015  Â·  37Comments  Â·  Source: square/retrofit

This has already been discussed in #1063 #1092 #1096 and #1130, but I thought I'd open a separate issue to track this specifically.

RFC2388 describes the filename header parameter of multipart/form-data requests that Retrofit now uses. This parameter is required on some backend configurations. More details and examples can be found here.

We need a way of figuring out what the filename is. The problem with RequestBody (which is a part of OkHttp) is that it doesn't expose the filename when created with a File.

Feature

Most helpful comment

Using a map works (and may be the best option for beta versions of Retrofit 2 before they brought in OkHttp 3), but I believe they promoted Multipart for exactly this scenario. The current intended way to upload a file while specifying a filename (afaict) is using Multipart.Part. Something like this:

Interface:

@Multipart
@POST("upload")
Call<UploadResponse> uploadImage(@Part MultipartBody.Part filePart)

Client:

RequestBody fileReqBody = RequestBody.create(mediaType, file);
MultipartBody.Part filePart =
    MultipartBody.Part.createFormData(partName, file.getName(), fileReqBody);

Call<UploadResponse> call = api.uploadImage(filePart);

All 37 comments

Hi @lukaciko I had the same problem but finally I found the solution of this bug. I just had to add the name of the field in @Part() RequestBody.

This is an example:
Call postMethod(@Part("picture\"; filename=\"1.jpg\" ") RequestBody file);

picture - is the name of the parameter and after put the "filename" parameter with any name.

Note: Do not forget scape quotation marks :)

That may work, but it seems a bit hacky. Inherent support would seem more natural.

Should this be an OkHttp issue rather than a Retrofit one? Maybe the File overload of RequestBody.create() could automatically include the filename header parameter obtained via File#getName() or something.

Injecting the filename can be used as a workaround, but it's not exactly pretty. It'd be better if it could be defined with an optional annotation parameter (i.e. @Part(value="picture", filename="cool_cat.jpg") but this still has the bigger issue - form field name is known in advanced and is thus a part of the interface, but filenames are dynamic.

@JakeWharton you've advocated using RequestBody directly when creating requests. Any ideas how we could change the API to accommodate this?

Sorry I haven't been too involved here. There is a lot going on in this
library, others, and internally, so I can only focus on so many things at
once.

Most of what you describe has nothing to do with Retrofit, but OkHttp.
RequestBody is an OkHttp type and internally we use OkHttp's multipart
builder. The filename property of TypedInput always felt out-of-place, so I
don't think we're likely to add it to RequestBody (which is the analogous
to TypedInput now).

I would encourage you to experiment with it and see possible ways forward
if you can. Otherwise I will take a look before v2.0, but I can't promise
when that will be just yet.

On Wed, Sep 30, 2015 at 3:40 AM Luka Cindro [email protected]
wrote:

Injecting the filename can be used as a workaround, but it's not exactly
pretty. It'd be better if it could be defined with an optional annotation
parameter (i.e. @Part(value="picture", filename="cool_cat.jpg") but this
still has the bigger issue - form field name is known in advanced and is
thus a part of the interface, but filenames are dynamic.

@JakeWharton https://github.com/JakeWharton you've advocated using
RequestBody directly when creating requests. Any ideas how we could
change the API to accommodate this?

—
Reply to this email directly or view it on GitHub
https://github.com/square/retrofit/issues/1140#issuecomment-144314860.

I have looked at the source of Retrofit and OkHttp and without changing RequestBody implementation/usage it seems a good way to go is to add some annotation as @PartFile("file") with a File object, retrofit could use it to set the "filename" header and to create the RequestBody object.

@BugsBunnyBR Yeah, that seems reasonable. I'll start thinking about this. It will be resolved with a sample before 2.0 is released.

@JakeWharton Bugs and I made an attempt in #1188 if that's of any value.

It's a possible way to support file name rather than (@Part("picture\"; filename=\"1.jpg\" ") ? I look not such pretty but error-prone.

@AtanL That's what #1188 does. It would look something like this:

File file = /* ... */;
TypedFile image = new TypedFile("image/png", file);

// ...

@PartFile("image") TypedFile image

The file name would default to the result of File#getName() but you could override it when creating a new TypedFile if desired. I'm not sure if this is the way the project wants to go yet, but it's out there.

Using beta.2

    public interface UploadService {
        @Multipart
        @PUT("/somepath/upload")
        Call<ResponseBody> upload(@Part("uploadFile") RequestBody upload);
    }

    UploadService service = retrofit.create(UploadService.class);

    File file = new File(filePath);
    RequestBody requestBody = RequestBody.create(MediaType.parse(mimeType), file);
    Response<ResponseBody> response = service.upload(requestBody).execute();

I would expect headers to look something like this:

Content-Disposition: form-data; name="fileUpload"; filename="somefile.jpg"
Content-Type: image/jpeg

Attached a log interceptor to OkHttp and am only seeing these headers:

 D/OkHttp: --> PUT /somepath/upload HTTP/1.1
 D/OkHttp: Authorization: Bearer 12345
 D/OkHttp: Accept-Encoding: gzip
 D/OkHttp: --> END POST

Is multipart file upload still broken?

@erikdcch

Call postMethod(@Part("picture\"; filename=\"1.jpg\" ") RequestBody file);

Does indeed work. However, from what I can tell there is no way to set a dynamic filename. IE I don't want to filename to be the same for every upload.

When removing the 'filename' portion from the annotation the upload does not work.

@ayon115 found a good workaround for dynamic filename here: https://github.com/square/retrofit/issues/1063#issuecomment-145920568

Btw guys, even with all this solutions I have next problem.

@Multipart
    @PUT("users/{id}")
    Call<JsonObject> uploadInsuranceCardImage(@Path("id") String id,
                                  @Part("profile\"; filename=\"image.jpg\"") RequestBody file);

When I do logging I get:

Content-Disposition: form-data; name="profile"; filename="image.jpg""
D/OkHttp﹕ Content-Transfer-Encoding: binary
D/OkHttp﹕ Content-Type: image/jpeg
D/OkHttp﹕ Content-Length: 58077

Notice on the end of filename double quotes.
If I remove escape quote after extension everything works fine

So the solution to this actually going to mostly live in OkHttp as a model representing both an entire multipart body and its parts (https://github.com/square/okhttp/pull/2082). This will allow a Multipart.Part to be passed for @Part which carries the required information for named files.

@JakeWharton Has this been integrated into beta3?

No

On Fri, Jan 8, 2016 at 3:36 PM Shubhadeep Chaudhuri <
[email protected]> wrote:

@JakeWharton https://github.com/JakeWharton Has this been integrated
into beta3?

—
Reply to this email directly or view it on GitHub
https://github.com/square/retrofit/issues/1140#issuecomment-170117247.

Just want to re-iterate comment from @UMFsimke

Many people have been repeating this workaround, but this does not work with all servers:

@Part("profile\"; filename=\"image.jpg\"")

This is wrong; it generates an extra quote. Should be:

@Part("profile\"; filename=\"image.jpg")

Formatted this way, the workaround does work for me.

@JakeWharton Has this been integrated into beta4?

No

On Thu, Feb 11, 2016 at 1:11 AM Shubhadeep Chaudhuri <
[email protected]> wrote:

@JakeWharton https://github.com/JakeWharton Has this been integrated
into beta4?

—
Reply to this email directly or view it on GitHub
https://github.com/square/retrofit/issues/1140#issuecomment-182733131.

It would be ever corrected? ;-)

no

Really?? It is time to start learning Volley??

No, it's time to start contributing and helping resolve the issue if it is that much of a problem for you. There is a simple documented workaround so I don't know why it would be though. By the way, volley is more analogous to okhttp, not retrofit, so not really appropriate.

I don't find workaround what is working. There are a lot bad solution here and stackoverflow. Can you show me the working version? I would be check .

@mesterj Looking at the first post, there are many links to other discussions about this. I am using this and it is working great.

https://github.com/square/retrofit/issues/1063#issuecomment-145920568

@mesterj Its working great for me also https://github.com/square/retrofit/issues/1063

Thank you. I found two workaround and I made my third which is really works. :-)

@mesterj Care to share?

In the API interface:

@Multipart @POST("com.mycompany.nyomtserv1.upload/upload") Call<ResponseBody> uploadImage(@PartMap Map<String, RequestBody> params);
When you have the file:

Map<String, RequestBody> map = new HashMap<>(); RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file); map.put("file\"; filename=\"" + file.getName(), fileBody);

Using a map works (and may be the best option for beta versions of Retrofit 2 before they brought in OkHttp 3), but I believe they promoted Multipart for exactly this scenario. The current intended way to upload a file while specifying a filename (afaict) is using Multipart.Part. Something like this:

Interface:

@Multipart
@POST("upload")
Call<UploadResponse> uploadImage(@Part MultipartBody.Part filePart)

Client:

RequestBody fileReqBody = RequestBody.create(mediaType, file);
MultipartBody.Part filePart =
    MultipartBody.Part.createFormData(partName, file.getName(), fileReqBody);

Call<UploadResponse> call = api.uploadImage(filePart);

MultipartBody.Part is not for @Body parameters but for @Part ones

On Tue, Jun 7, 2016 at 1:49 PM Greg Williams [email protected]
wrote:

If you're using Jackson, you'll likely get the error:
Unable to convert okhttp3.MultipartBody$Part to RequestBody
In this case, you can use MultipartBody.Builder() like this:
Interface:

@POST("upload")
Call uploadImage(@Body RequestBody requestBody);

Client:

RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(partName, file.getName(), RequestBody.create(mediaType, file))
.build();

Call call = api.uploadImage(requestBody);

—
You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
https://github.com/square/retrofit/issues/1140#issuecomment-224359826,
or mute the thread
https://github.com/notifications/unsubscribe/AAEEEYU2DM3Wk34Te0xhhsXwnggStHkjks5qJa86gaJpZM4GFxD3
.

Thank you. It is much better.

This is my code. This works..

public interface FileUploadService {  
    @Multipart
    @POST("upload")
    Call<ResponseBody> upload(@PartMap Map<String, RequestBody> params);
}

 private void uploadFile() {  
        FileUploadService service =
                ServiceGenerator.createService(FileUploadService.class);

        File file = new File(selectedImagePath);
image_name = file.getName();
String fileName = "file\"; filename=\"" + image_name;
RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);

 RequestBody id = RequestBody.create(MediaType.parse("multipart/form-data"), observation_id);

Map<String, RequestBody> requestBodyMap = new HashMap<>();
 requestBodyMap.put("id", id);
requestBodyMap.put(fileName, requestBody);


        Call<ResponseBody> call = service.upload(requestBodyMap);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call,
                                   Response<ResponseBody> response) {
                Log.v("Upload", "success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                Log.e("Upload error:", t.getMessage());
            }
        });
    }

WTH is observation_id in your code?

i am also using a PartMap, and kept getting 500 internal server error when i would add an image to the multipart request. i just tried out what @sbljayarathna did, and it worked for me, too. (thanks!)

I am trying to upload a pdf file but I keep getting unprocessable entity
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), file);

@UMFsimke what does you mean"Notice on the end of filename double quotes.
If I remove escape quote after extension everything works fine"
can you just show me the code.
i also meet the double quotes problem.

Was this page helpful?
0 / 5 - 0 ratings