Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to set filename on multipart requests #1140

Closed
lukaciko opened this issue Sep 29, 2015 · 37 comments
Closed

Add a way to set filename on multipart requests #1140

lukaciko opened this issue Sep 29, 2015 · 37 comments
Labels
Milestone

Comments

@lukaciko
Copy link
Contributor

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.

@erikdcch
Copy link

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 :)

@wKovacs64
Copy link

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.

@lukaciko
Copy link
Contributor Author

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?

@JakeWharton
Copy link
Member

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 notifications@github.com
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
#1140 (comment).

@juliocbcotta
Copy link
Contributor

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.

@JakeWharton
Copy link
Member

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

@wKovacs64
Copy link

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

@ghost
Copy link

ghost commented Nov 20, 2015

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

@wKovacs64
Copy link

@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.

@newmanw
Copy link

newmanw commented Nov 30, 2015

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?

@newmanw
Copy link

newmanw commented Dec 1, 2015

@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.

@newmanw
Copy link

newmanw commented Dec 1, 2015

@ayon115 found a good workaround for dynamic filename here: #1063 (comment)

@UMFsimke
Copy link

UMFsimke commented Dec 2, 2015

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

@JakeWharton
Copy link
Member

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

@timemanx
Copy link

timemanx commented Jan 8, 2016

@JakeWharton Has this been integrated into beta3?

@JakeWharton
Copy link
Member

No

On Fri, Jan 8, 2016 at 3:36 PM Shubhadeep Chaudhuri <
notifications@github.com> wrote:

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


Reply to this email directly or view it on GitHub
#1140 (comment).

@grennis
Copy link

grennis commented Jan 14, 2016

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.

@timemanx
Copy link

@JakeWharton Has this been integrated into beta4?

@JakeWharton
Copy link
Member

No

On Thu, Feb 11, 2016 at 1:11 AM Shubhadeep Chaudhuri <
notifications@github.com> wrote:

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


Reply to this email directly or view it on GitHub
#1140 (comment).

@mesterj
Copy link

mesterj commented Feb 18, 2016

It would be ever corrected? ;-)

@shakil807g
Copy link

no

@mesterj
Copy link

mesterj commented Feb 26, 2016

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

@grennis
Copy link

grennis commented Feb 26, 2016

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.

@mesterj
Copy link

mesterj commented Feb 27, 2016

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 .

@newmanw
Copy link

newmanw commented Feb 27, 2016

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

#1063 (comment)

@ArslanAli7
Copy link

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

@mesterj
Copy link

mesterj commented Mar 11, 2016

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

@gichamba
Copy link

@mesterj Care to share?

@mesterj
Copy link

mesterj commented Apr 24, 2016

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);

@wKovacs64
Copy link

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);

@JakeWharton
Copy link
Member

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

On Tue, Jun 7, 2016 at 1:49 PM Greg Williams notifications@github.com
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
#1140 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAEEEYU2DM3Wk34Te0xhhsXwnggStHkjks5qJa86gaJpZM4GFxD3
.

@mesterj
Copy link

mesterj commented Jun 7, 2016

Thank you. It is much better.

@sbljayarathna
Copy link

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());
            }
        });
    }

@jalle007
Copy link

jalle007 commented Aug 7, 2016

WTH is observation_id in your code?

@marieldee
Copy link

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!)

@Harmeet2491
Copy link

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

@yuanjunli
Copy link

yuanjunli commented Dec 28, 2016

@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.

tokuhirom added a commit to tokuhirom/line-bot-sdk-java that referenced this issue Aug 21, 2023
This is just a *hack*. but it works.

square/retrofit#1140
tokuhirom added a commit to line/line-bot-sdk-java that referenced this issue Aug 21, 2023
This is just a *hack*. but it works.

square/retrofit#1140
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests