AWS S3 Rest API with Android Retrofit V2 库,上传的图像已损坏

AWS S3 Rest API with Android Retrofit V2 library, uploaded image is damaged

我正在尝试将 Image 从我的 Android APP 上传到 Amazon AWS S3,我需要使用 AWS Restful API.

我正在使用 Retrofit 2 来满足请求。

我的应用程序与 Amazon S3 成功连接并按预期执行请求,但是当我尝试从 Bucket 查看 Image,图片打不开。我将 Image 下载到我的电脑并尝试打开,但不断收到图像已损坏的消息。

让我们在下面查看我的完整代码。

我的 Gradle 依赖项

compile 'com.squareup.retrofit:retrofit:2.0.0-beta1'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta1'
compile 'net.danlew:android.joda:2.8.2'

这里创建了一个文件并开始请求

File file = new File(mCurrentPhotoPath);
RequestBody body = RequestBody.create(MediaType.parse("image/jpeg"), file);
uploadImage(body, "photo_name.jpeg");

改造界面

public interface AwsS3 {

    @Multipart
    @PUT("/{Key}")
    Call<String> upload(@Path("Key") String Key,
                @Header("Content-Length") long length,
                @Header("Accept") String accept,
                @Header("Host") String host,
                @Header("Date") String date,
                @Header("Content-type") String contentType,
                @Header("Authorization") String authorization,
                @Part("Body") RequestBody body);
}

Utils class 挂载凭据

public class AWSOauth {

    public static String getOAuthAWS(Context context, String fileName)  throws Exception{

        String secret = context.getResources().getString(R.string.s3_secret);
        String access = context.getResources().getString(R.string.s3_access_key);
        String bucket = context.getResources().getString(R.string.s3_bucket);

        return gerateOAuthAWS(secret, access, bucket,fileName);
    }

    private static String gerateOAuthAWS(String secretKey, String accessKey, String bucket, String imageName) throws Exception {

        String contentType = "image/jpeg";

        DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z").withLocale(Locale.US);
        String ZONE = "GMT";
        DateTime dt = new DateTime();
        DateTime dtLondon = dt.withZone(DateTimeZone.forID(ZONE)).plusHours(1);
        String formattedDate = dtLondon.toString(fmt);

        String resource = "/" + bucket + "/" + imageName;

        String stringToSign = "PUT" + "\n\n" + contentType + "\n" + formattedDate + "\n" + resource;

        Mac hmac = Mac.getInstance("HmacSHA1");
        hmac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1"));

        String signature = ( Base64.encodeToString(hmac.doFinal(stringToSign.getBytes("UTF-8")), Base64.DEFAULT)).replaceAll("\n", "");

        String oauthAWS = "AWS " + accessKey + ":" + signature;

        return  oauthAWS;
    }
}

最后是请求的方法

 public void uploadImage(RequestBody body, String fileName){

        String bucket = getString(R.string.s3_bucket);

        Retrofit restAdapter = new Retrofit.Builder()
                .baseUrl("http://" + bucket + ".s3.amazonaws.com")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        AwsS3 service = restAdapter.create(AwsS3.class);

        DateTimeFormatter fmt = DateTimeFormat.forPattern("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z").withLocale(Locale.US);
        String ZONE = "GMT";
        DateTime dt = new DateTime();
        DateTime dtLondon = dt.withZone(DateTimeZone.forID(ZONE)).plusHours(1);
        String formattedDate = dtLondon.toString(fmt);

        try {

            String oauth = AWSOauth.getOAuthAWS(getApplicationContext(), fileName);

            Call<String> call = service.upload(fileName, body.contentLength(), "/**", bucket + ".s3.amazonaws.com", formattedDate,  body.contentType().toString(), oauth, body);
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Response<String> response) {
                    Log.d("tag", "response : " + response.body());
                }

                @Override
                public void onFailure(Throwable t) {
                    Log.d("tag", "response : " + t.getMessage());
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

非常感谢任何帮助,在此先感谢!

我没有使用过 Retrofit 2,只使用过 Retrofit 1,所以 YMMV,但我相信做你想做的事情的典型方法是在你试图使用 RequestBody 的地方使用 TypedFile。

我猜测 Retrofit 在内部使用了 RequestBody。

您将创建类似于以下内容的 TypedFile:

TypedFile typedFile = new TypedFile("multipart/form-data", new File("path/to/your/file"));

您的界面将是:

   @Multipart
    @PUT("/{Key}")
    Call<String> upload(@Path("Key") String Key,
                @Header("Content-Length") long length,
                @Header("Accept") String accept,
                @Header("Host") String host,
                @Header("Date") String date,
                @Header("Content-type") String contentType,
                @Header("Authorization") String authorization,
                @Part("Body") TypedFile body);
}

有一个不错的例子 https://futurestud.io/blog/retrofit-how-to-upload-files/

您正在发送多部分负载,但强制 Content-type 为 image/jpeg。您的 jpg 已损坏,因为 S3 可能将 multipart headers 保存到您的 jpg 文件中,因为您告诉它整个消息都是 JPG。由于您实际上没有要发送的多个部分,您可以删除 Multipart 注释并使用 Body 而不是 Part 来表示 RequestBody

public interface AwsS3 {

    @PUT("/{Key}")
    Call<String> upload(@Path("Key") String Key,
                @Header("Content-Length") long length,
                @Header("Accept") String accept,
                @Header("Host") String host,
                @Header("Date") String date,
                @Header("Content-type") String contentType,
                @Header("Authorization") String authorization,
                @Body RequestBody body);
}

您还应该能够删除显式设置 Content-typeContent-length headers。

我也遇到了同样的问题,我用Fiddler查看HTTP请求内容,发现retrofit 2.0.0 beta1和1.9.0不一样。

在我的问题中,不同的 HTTP 请求内容阻止服务器获取正确的数据。

为了制作相同的 HTTP 请求内容,我使用改造 2.0.0 deta1 进行后续步骤。


在retrofit服务中,为http请求添加一个form-dataheader;

@Headers("Content-Type: multipart/form-data;boundary=95416089-b2fd-4eab-9a14-166bb9c5788b")

int retrofit 2.0.0 deta1,使用@Multipart的header会得到这样的数据:

Content-Type: multipart/mixed

因为默认值是混合的,没有边界标题。


不使用@Multipart上传文件,只使用@Body RequestBody

如果你使用@Multipart请求Server,你必须通过

传递param(file)

@Part(key),那么你会遇到一个新问题。可能是 retrofit 2.0.0beta1 有 BUG ...,@Multipart 使用 1.9.0 编译生成错误的 http 请求。


调用方法时需要将MultipartRequestBody传递给@BodyRequestBody

使用MultipartBuilder创建一个MultipartRequestBody,当你新建MultipartBuilder时,调用这个consturt:

new MultipartBuilder("95416089-b2fd-4eab-9a14-166bb9c5788b")

参数是你设置的int @headers(boundary=)

builder.addFormDataPart(String name, String filename, RequestBody value)

此方法将有助于形成如下数据 int HTTP 请求内容:

Content-Disposition: form-data; name="imgFile"; filename="IMG_20150911_113029.jpg" Content-Type: image/jpg Content-Length: 1179469

RequestBody 值是您在代码中生成的值。

我只是暂时解决了这个问题。

希望能帮到你!

您可以使用改造 2 上传 image/file

@Multipart
@POST("/api/attachment")
Call<JsonPrimitive> addAttachment(@Part MultipartBody.Part imageFile);

现在打电话:

 RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
    MultipartBody.Part imageFileBody = MultipartBody.Part.createFormData("file", file.getName(), requestBody);

注意:确保您使用的是 retrofit2,因为某些原因我无法使用 retrofit1 库上传图像。

我用过Retrofit 2 resolve 我在界面

中使用 Body 而不是 Part 作为你的 RequestBody
@PUT("")
Call<String> nameAPI(@Url String url, @Body RequestBody body);

和java代码

// Prepare image file
File file = new File(pathImg);
RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpeg"), file);

Call<String> call = SingletonApiServiceS3.getInstance().getService().nameAPI(
        path,
       requestBody
);
call.enqueue(new Callback<String>() {
    @Override
    public void onResponse(Call<String> call, final Response<String> response) {

        if (response.isSuccessful()) {
            // Your handling
        } else {
            // Your handling
       }
   }

   @Override
   public void onFailure(Call<String> call, Throwable t) {
       Toast.makeText(getContext(), "onFailure : "+t.getMessage().toString(),Toast.LENGTH_SHORT).show();
   }
});
RequestBody avatarBody = RequestBody.create(MediaType.parse("image"),file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), avatarBody);


@Multipart
@POST(url)
Call<ResponseBody> uploadImageAmazon(
            @Part MultipartBody.Part filePart);

我也有过同样的经历,用https://github.com/square/retrofit/issues/2424这个方法解决了