使用Android Retrofit V2库的AWS S3 Rest API,上传的图片已损坏

时间:2015-09-19 01:45:51

标签: android rest amazon-s3 retrofit

我正在尝试从我的 Android APP上传Image到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);
}

使用类来安装凭据

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

我感谢任何帮助,提前谢谢!

6 个答案:

答案 0 :(得分:5)

我有同样的问题,当我使用Fiddler检查HTTP请求内容时,我发现改进2.0.0 beta1与1.9.0有所不同。

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

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

在改造服务中,为http请求添加表单数据标头;

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

int retrofit 2.0.0 deta1,使用@Multipart的标头会获得如下数据:

  

内容类型:multipart / mixed

因为聋人的价值是混合的,没有边界标题。

请勿使用@Multipart上传文件,只需使用@Body RequestBody

如果您使用@Multipart请求服务器,则必须通过

传递param(文件)

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

调用方法时,需要将MultipartRequestBody传递给@Body RequestBody

使用MultipartBuilder创建MultipartRequestBody,当您新MultipartBuilder时,请调用此consturt:

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

param是你设置int @headers(boundary=)

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

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

  

内容 - 处置:表单数据;名称=&#34; imgFile&#34 ;;   文件名=&#34; IMG_20150911_113029.jpg&#34;内容类型:image / jpg   内容长度:1179469

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

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

希望可以帮到你!

答案 1 :(得分:3)

您正在发送多部分有效内容,但强制内容类型为image/jpeg。您的jpg已损坏,因为S3可能已将多部分标头保存到您的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标题。

答案 2 :(得分:2)

I have used Retrofit 2 resolve
and I use Body instead of Part for your RequestBody in interface
@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()) {



            } else {

            }
        }

        @Override
        public void onFailure(Call<String> call, Throwable t) {

            Toast.makeText(getContext(),"onFailure : "+t.getMessage().toString(),Toast.LENGTH_SHORT).show();
        }
    });

答案 3 :(得分:1)

我没有使用Retrofit 2,只是改造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/

答案 4 :(得分:1)

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此解决方案

解决了这个问题

答案 5 :(得分:0)

您可以使用改装2上传图像/文件

@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库上传图片。