带有 Android Retrofit V2 库的 AWS S3 Rest API,上传的图像已损坏
Posted
技术标签:
【中文标题】带有 Android Retrofit V2 库的 AWS S3 Rest API,上传的图像已损坏【英文标题】:AWS S3 Rest API with Android Retrofit V2 library, uploaded image is damaged 【发布时间】:2015-12-16 06:25:41 【问题描述】:我正在尝试将 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 类挂载凭据
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();
感谢任何帮助,在此先感谢!
【问题讨论】:
没有抛出异常?请求的状态码是什么? 无异常,图片上传,响应200 OK。 你说图片上传成功,是不是也对比了md5sum?上传成功后,尝试比较你上传的对象的md5sum。这样你就可以确定对象根本没有改变。你也说when I try open the image but it is damaged
..你想在桌面上查看它吗?
我也尝试打开浏览器和桌面!
@A.Anderson 您是否有您尝试上传的图像/对象的样本(任何公共链接?)?
【参考方案1】:
我使用了 Retrofit 2 解决方案
我在界面中使用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();
);
【讨论】:
s3 PUT 调用需要这个,非常感谢 这有帮助,谢谢。我删除了@Multipart 并添加了 Body【参考方案2】:我有同样的问题,当我使用 Fiddler 检查 HTTP 请求内容时,我发现改造 2.0.0 beta1 与 1.9.0 不同。
在我的问题中,HTTP 请求内容的不同会阻止服务器获取正确的数据。
为了制作相同的 HTTP 请求内容,我使用改造 2.0.0 deta1 执行后续步骤。
在改造服务中,为http请求添加form-data header;
@Headers("Content-Type: multipart/form-data;boundary=95416089-b2fd-4eab-9a14-166bb9c5788b")
int retrofit 2.0.0 deta1
,使用@Multipart
的头部会得到这样的数据:
内容类型:多部分/混合
因为默认值是混合的,并且没有边界标题。
不要使用@Multipart
上传文件,只使用@Body
RequestBody
如果你使用@Multipart
请求服务器,你必须通过param(file)
@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")
参数是你设置的int @headers(boundary=)
builder.addFormDataPart(String name, String filename, RequestBody value)
此方法将帮助形成如下 int HTTP 请求内容的数据:
内容配置:表单数据;名称="imgFile"; 文件名="IMG_20150911_113029.jpg" 内容类型:image/jpg 内容长度:1179469
RequestBody
值是您在代码中生成的值。
我只是暂时解决了这个问题。
希望能帮到你!
【讨论】:
他们的方法似乎没问题,我发现 s3 AWS 需要在请求正文中发送内容,而不是作为多部分发送。 我对部件的 Content-Length 有疑问,Retrofit 为 RequestBody 和每个部件自动添加 Content-Length,服务器不允许这样做(我通过 Postman 测试了 API,它可以工作好吧),github.com/square/okhttp/issues/2604你有什么想法吗?还是我必须扔掉改造?【参考方案3】: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这个解决方案解决了
【讨论】:
【参考方案4】:您正在发送多部分有效负载,但强制 Content-type 为 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-type
和 Content-length
标头。
【讨论】:
有道理,我以后试试这个方法! 所以我试过了,但效果不好。图片发送不正确!【参考方案5】:我没有使用 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/
【讨论】:
感谢您的回答,我认为我的请求正文有些问题,因为请求执行正常,但我使用的是 Retrofit 2,TypedFile 已从 lib 中删除,我无法降级,我需要使用版本 2。 你是否考虑过放弃显式的 Content-Type 和 Content-Length 标头,让 Retrofit 从 RequestBody 中获取它们? 这不起作用 AWS api rest 要求发送此标头。【参考方案6】:您可以使用改造 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 库上传图片。
【讨论】:
以上是关于带有 Android Retrofit V2 库的 AWS S3 Rest API,上传的图像已损坏的主要内容,如果未能解决你的问题,请参考以下文章
带有 10 个标记的 Android 基本 API v2 MapActivity outOfMemory
如何在Android上通过retrofit2调用带有Cognito Credentials的API网关?