Retrofit(2.0 beta2)多部分文件上传无法工作

5

我正在使用Square Retrofit 2.0 beta2版本。我尝试按照这个教程的步骤进行操作。我想要上传一个位图图片到服务器,但是代码似乎不能正常工作。我已经使用postman测试了我的服务器,我能够发布照片并成功检索到它。这是我的Flask控制器。

@app.route('/api/photo/user/<int:user_id>', methods=["POST"])
    def post_user_photo(user_id):
        app.logger.info("post_user_photo=> user_id:{}, photo: {}".format(
            user_id,
            request.files['photo'].filename,
        ))
        user = User.query.get_or_404(user_id)
        try:
            user.photo = request.files['photo'].read()
        except Exception as e:
            app.logger.exception(e)
            db.session.rollback()
            raise
        db.session.commit()
        return "", codes.no_content

我使用Postman测试了我的控制器,这里是Postman生成的请求。

POST /api/photo/user/5 HTTP/1.1
Host: blooming-cliffs-9672.herokuapp.com
Cache-Control: no-cache
Postman-Token: 8117fb79-4781-449d-7d22-237c49b53389
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="photo"; filename="sfsu.jpg"
Content-Type: image/jpeg


----WebKitFormBoundary7MA4YWxkTrZu0gW

我已经定义了Retrofit服务以及上传图像的部分,以下是我的Android代码。接口部分:

  @Multipart
    @POST("/api/photo/user/{userId}")
    Call<Void> uploadUserProfilePhoto(@Path("userId") Integer userId, @Part("photo") RequestBody photo);

这里是客户端构建器部分。
  public static BeamItService getService(){
        if (service == null) {
            OkHttpClient client = new OkHttpClient();
            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            HttpLoggingInterceptor interceptor2 = new HttpLoggingInterceptor();
            interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);

            client.interceptors().add(interceptor);
            client.interceptors().add(interceptor2);

            service =  new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build().create(BeamItService.class);
        }
        return service;
    }

以下是尝试上传位图的Android活动代码。

private void uploadProfilePhoto(){
        BeamItService service = BeamItServiceTransport.getService();

        MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpeg");
        byte [] data = BitmapUtility.getBitmapToBytes(((BitmapDrawable) ivProfilePhoto.getDrawable()).getBitmap());
        Log.d(TAG, String.format("Profile detals => user_id: %d, size of data: %d", 5, data.length));

        RequestBody requestBody1 = RequestBody.create(MEDIA_TYPE_PNG,
                                                    data);
        Log.d(TAG, "requestBody: " + requestBody1.toString());
        RequestBody requestBody2 = new MultipartBuilder()
                .type(MultipartBuilder.FORM)
                .addPart(Headers.of("Content-Disposition", "form-data; name=\"photo\"; filename=\"t.jpg\""),
                        requestBody1)
                .build();
        Log.d(TAG, "requestBody: " + requestBody2.toString());
//        ProfileDetails profileDetails = new DBHelper(this).fetchProfileDetails();

        Call<Void> call = service.uploadUserProfilePhoto(5, requestBody2);
        call.enqueue(new ProfilePhotoUploadCallback());
    }

    private class ProfilePhotoUploadCallback implements Callback<Void> {

        @Override
        public void onResponse(Response<Void> response, Retrofit retrofit) {
            Log.d(TAG, String.format("ProfilePhotoUploadCallback=> code: %d", response.code()));
        }

        @Override
        public void onFailure(Throwable t) {

        }
    }

但是上传失败了,Flask应用程序每次都返回状态码400。我尝试在Flask应用程序中打断点,但请求甚至没有到达那里。 这是服务器日志。

2015-11-02T06:05:42.288574+00:00 heroku[router]: at=info method=POST path="/api/photo/user/5" host=blooming-cliffs-9672.herokuapp.com request_id=2cc8b6c8-f12a-4e4b-8279-cedfc39712f2 fwd="204.28.113.240" dyno=web.1 connect=1ms service=88ms status=400 bytes=347
2015-11-02T06:05:42.209347+00:00 app[web.1]: [2015-11-02 06:05:42 +0000] [11] [DEBUG] POST /api/photo/user/5

我还尝试启用Retrofit拦截器并记录请求和响应,但我没有获得完整的POST请求正文。这是Android日志。

    11-02 00:24:22.119 3904-4382/com.contactsharing.beamit D/OkHttp: --> POST /api/photo/user/5 HTTP/1.1
11-02 00:24:22.119 3904-4382/com.contactsharing.beamit D/OkHttp: Content-Type: multipart/form-data; boundary=4031e177-0e4b-4f16-abe8-20c54e506846
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: Content-Length: 17171
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: Host: blooming-cliffs-9672.herokuapp.com
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: Connection: Keep-Alive
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: Accept-Encoding: gzip
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: User-Agent: okhttp/2.6.0-SNAPSHOT
11-02 00:24:22.120 3904-4382/com.contactsharing.beamit D/OkHttp: --> END POST
11-02 00:24:22.179 3904-4537/com.contactsharing.beamit I/DBHelper: Updated row: 1
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: <-- HTTP/1.1 400 BAD REQUEST (195ms)
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Connection: keep-alive
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Server: gunicorn/19.3.0
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Date: Mon, 02 Nov 2015 08:24:22 GMT
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Content-Type: text/html
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Content-Length: 192
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: Via: 1.1 vegur
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: OkHttp-Selected-Protocol: http/1.1
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: OkHttp-Sent-Millis: 1446452662120
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: OkHttp-Received-Millis: 1446452662316
11-02 00:24:22.316 3904-4382/com.contactsharing.beamit D/OkHttp: <-- END HTTP

请帮忙,我卡住了,无法取得任何进展。

1个回答

15

您在这里嵌套了一个多部分请求体(一个多部分内嵌另一个多部分)。

最近实现了类似的东西,不使用@Multipart@Part,可以使用@BodyMultipartBuilder

@POST("/api/photo/user/{userId}")
Call<Void> uploadUserProfilePhoto(@Path("userId") Integer userId, @Body RequestBody photo);

那么,不要使用MultipartBuilder.addPart(...),而是使用MultipartBuilder.addFormDataPart(name, filename, requestBody)

private void uploadProfilePhoto() {
    BeamItService service = BeamItServiceTransport.getService();

    MediaType MEDIA_TYPE_PNG = MediaType.parse("image/jpeg");
    byte [] data = BitmapUtility.getBitmapToBytes(((BitmapDrawable) ivProfilePhoto.getDrawable()).getBitmap());
    Log.d(TAG, String.format("Profile detals => user_id: %d, size of data: %d", 5, data.length));

    RequestBody requestBody1 = RequestBody.create(MEDIA_TYPE_PNG, data);
    Log.d(TAG, "requestBody: " + requestBody1.toString());
    RequestBody requestBody2 = new MultipartBuilder()
            .type(MultipartBuilder.FORM)
            .addFormDataPart("photo", "t.jpg", requestBody1)
            .build();
    Log.d(TAG, "requestBody: " + requestBody2.toString());
//  ProfileDetails profileDetails = new DBHelper(this).fetchProfileDetails();

    Call<Void> call = service.uploadUserProfilePhoto(5, requestBody2);
    call.enqueue(new ProfilePhotoUploadCallback());
}

哇,太好了,我发现使用MultipartBuilder和接口中的@Body构造RequestBody的替代方法了!我的REST服务很挑剔,当您使用@Part@PartMap方法发送它的多部分请求时,由于添加了Content-Transfer-Encoding标头到各个部分以及Content-Type到每个多部分项,会抛出500错误。我甚至尝试了使用8位编码并调整Content-Type,但没有任何效果。结果发现,MultipartBuilder生成的多部分请求体比较“简单”,这正是服务所喜欢的。真是想不到! - core24
我尝试了所有可能的解决方案,但都没有起作用。感谢@raniejade提供的解决方案。你是救星。 - Aparna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接