Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
251 views
in Technique[技术] by (71.8m points)

multipartform data - Android Upload Multiple Files In A Single Request

I'm posting content to the server in a multipart/form-data request. and the data I'm posting contains multiple parameters including a file array parameter (files[]).

Using postman I'm setting the parameters and the files as:

first-parameter=text content
second-parameter=text content
files[0]={first selected file}
files[1]={second selected file}

Submitting this request on postman is always successful, and the files are uploaded successfully.

When I generate code snippets on postman, this is the result:

POST /api/*******/new HTTP/1.1
Host: ***.***.***.*** ip-address
Authorization: {authorization header}
Cache-Control: no-cache
Postman-Token: d15f13f1-4a65-81d1-bf91-f5accd1b1dd0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="first-parameter"

first-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="second-parameter"

second-parameter-value
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[0]"; filename=""
Content-Type: 

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="files[1]"; filename=""
Content-Type: 

------WebKitFormBoundary7MA4YWxkTrZu0gW--

On Android, I'm using Retrofit with the HttpLoggingInterceptor libraries, and using the @PartMap annotation when posting the request:

//API Definition:

interface ApiDefinitions {

@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
                                      @PartMap Map<String, RequestBody> params);

}

//Preparing and sending the request code:

public void postContent() {
    HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
    interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient client = new OkHttpClient
            .Builder()
            .addInterceptor(interceptor)
            .build();

    Retrofit retrofit = new Retrofit
            .Builder()
            .baseUrl(API_URL)
            .client(client)
            .addConverterFactory(GsonConverterFactory.create(new Gson()))
            .build();

    ApiDefinitions api = retrofit.create(ApiDefinitions.class);

    MediaType mediaType = MediaType.parse("multipart/form-data");

    Map<String, RequestBody> params = new HashMap<>();
    params.put("first-parameter", MultipartBody.create(mediaType, "first-parameter-value"));
    params.put("second-parameter", MultipartBody.create(mediaType, "second-parameter-value"));
    params.put("files[0]", MultipartBody.create(mediaType, new File("First file path")));
    params.put("files[1]", MultipartBody.create(mediaType, new File("Second file path")));

    Call<ApiResponse> call = api.submitNew("Auth Token", params);
    enqueue.enqueue(new Callback<ApiResponse>() {

        @Override
        public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
        }

        @Override
        public void onFailure(Call<ApiResponse> call, Throwable t) {
        }
    });
}

Submitting this request works with no errors, http response is 200, But the files are not uploaded !!!

Since I'm using the HttpLoggingInterceptor, I've tried comparing the retrofit logs with post man code snippets, this is the retrofit log:

D/OkHttp: --> POST http://{api_address}/api/*****/new http/1.1
D/OkHttp: Content-Type: multipart/form-data; boundary=64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Length: 119325
D/OkHttp: Authorization: {Authorization-Token}
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="first-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: first-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="second-parameter"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data; charset=utf-8
D/OkHttp: Content-Length: 10
D/OkHttp: second-parameter-value
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[0]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: ?PNG
D/OkHttp: 
D/OkHttp: ??????IHDR????????????????????r?B???????sBIT??O????? ??IDATx?4??mYv??}c?9???so?|W???R??2z?T%?8B?X? 
D/OkHttp: &?D$??B??r?D??w?!@????????{??H???[?!,?@ ?4h?P????>?A??&? ????B?[?,?fD?Mi?d?5)???5?{??-?MQt???&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298
D/OkHttp: Content-Disposition: form-data; name="files[1]"
D/OkHttp: Content-Transfer-Encoding: binary
D/OkHttp: Content-Type: multipart/form-data;
D/OkHttp: Content-Length: 44205
D/OkHttp: ?PNG
D/OkHttp: 
D/OkHttp: ??????IHDR????????????????????r?B???????sBIT??O????? ??IDATx?4??mYv??}c?9???so?|W???R??2z?T%?8B?X? 
D/OkHttp: &?D$??B??r?D??w?!@????????{??H???[?!,?@ ?4h?P????>?A??&? ????B?[?,?fD?Mi?d?5)???5?{??-?MQt???&
D/OkHttp: --64360751-a7f4-44c4-a008-f5de764c7298--
D/OkHttp: --> END POST (119325-byte body)
D/OkHttp: <-- 200 OK http://{api_address}/api/*****/new (3409ms)

I've tried to find what's wrong in my request by comparing both logs from postman vs retrofit, but I can't find it ! The random boundary strings are different which is normal, postman uses webkit, while retrofit does not ! I don't think this is an issue at all.

I've tried to use @Part List<MultipartBody.Part instead of @PartMap Map<String, RequestBody> for the files as suggested here, but it didn't work also.

What should I do for the files to be uploaded ?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

First of all, I've been doing one thing wrong which is using a @PartMap Map<String, RequestBody> in the ApiDefinitions interface.

In case you're doing a post request with multiple parameters and multiple files, always make sure when defining the api method to use RequestBody for non-file parameters, and use MultipartBody.Part for file parameters.

In my case I needed to send an array of files, so the parameter that has worked for me is MultipartBody.Part[].

This is the new api definition :

@Multipart
@POST("*******/new")
Call<ApiResponse> submitNew(@Header("Authorization") String authHeader,
                                          @Part("first-parameter") RequestBody firstParameter,
                                          @Part("first-parameter") RequestBody secondParameter,
                                          @Part MultipartBody.Part[] files);

The second mistake I made was not noticing this:

PostMan Log: Content-Disposition: form-data; name="files[0]"; filename=""

Retrofit Log: Content-Disposition: form-data; name="files[0]"

The filename was not included in the multipart request, which apparently is a known issue when uploading files to a php service !

I didn't make the api, and I don't have any php background so please don't judge me. As far as I know, I was successfully sending files to the service api, but the api didn't know how to save these files !

So when sending the request:

List<Uri> files; //These are the uris for the files to be uploaded
MediaType mediaType = MediaType.parse("");//Based on the Postman logs,it's not specifying Content-Type, this is why I've made this empty content/mediaType
MultipartBody.Part[] fileParts = new MultipartBody.Part[files.size()];
for (int i = 0; i < files.size(); i++) {
    File file = new File(files.get(i).getPath());
    RequestBody fileBody = RequestBody.create(mediaType, file);
    //Setting the file name as an empty string here causes the same issue, which is sending the request successfully without saving the files in the backend, so don't neglect the file name parameter.
    fileParts[i] = MultipartBody.Part.createFormData(String.format(Locale.ENGLISH, "files[%d]", i), file.getName(), fileBody);
}

Call<ApiResponse> call = api.submitNew("Auth Token", MultipartBody.create(mediaType, "first_parameter_values"), MultipartBody.create(mediaType, "second_parameter_values"), fileParts);

call.enqueue(new Callback<ApiResponse>() {

    @Override
    public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
    }

    @Override
    public void onFailure(Call<ApiResponse> call, Throwable t) {
    }
});

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...