Retrofit - Upload Image to S3 using presigned URL - android

I am getting the presigned URL from Lambda, but not sure why when I make a PUT request to that generated URL, my image is not being uploaded to S3.
I've followed these two answers exactly, but it's still not working for me
https://stackoverflow.com/a/46177449/11110509
https://stackoverflow.com/a/46579224/11110509
Could someone point out what I'm doing wrong?
public interface IUploadImages{
#PUT
Call<String> listRepos(#Url String url, #Body RequestBody image);
}
And my call to upload the image:
//Already checked to make sure mImageUri is not null
File file = new File(StringUtils.getPath(this, mImageUri));
RequestBody requestFile = RequestBody.create(MediaType.parse("image/jpeg"), file);
RetrofitInterfaces.IUploadImages service = RetrofitClientInstance.getRetrofitInstance()
.create(RetrofitInterfaces.IUploadImages.class);
//Also checked to make sure mGeneraredUrl is also not null
Call<String> call = service.listRepos(mGeneratedUrl, requestFile);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if(response.isSuccessful()){
Log.d(TAG, "onResponse: Success?");
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
Log.d(TAG, "onFailure: Failed: " + t);
}
});
I've made the bucket public. The call is not failing, but in onResponse I'm getting a response code of 403.

Finally figured it out.. your presigned url function needs to have the same content type as
RequestBody.create(MediaType.parse("image/jpeg"), file);
So in my params
var params = {
Bucket: 'my-bucket-name',
Key: 'test-key',
Expires: signedUrlExpireSeconds,
ContentType: "image/jpeg"
};
Content type needs to be the same client side and in function side otherwise you get a 403 error...

Related

how can I pass header using #Part in retrofit?

My interface is like this
#Multipart
#NonNull
#FormUrlEncoded
#POST("upload")
Call<GeneralResponse> uploadImage(#Header("Authorization") MultipartBody.Part token, #Part("image") MultipartBody.Part image, #Part("kilometer") MultipartBody.Part distance);
method for uploading image and data
public void upload(final String token,final String distance, final File image) {
InternetConnection internetConnection = new InternetConnection(TimeCard.this);
if (internetConnection.isConnectingToInternet()) {
showProgressDialog();
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
MultipartBody.Part bodykm;
RequestBody kilometer;
kilometer= RequestBody.create(MediaType.parse("text/plain"), distance);
bodykm =MultipartBody.Part.createFormData("kilometer", distance, kilometer);
Call<GeneralResponse> call = apiService.uploadImage(token,bodyImage,bodykm);
call.enqueue(new Callback<GeneralResponse>() {
#Override
public void onResponse(Call<GeneralResponse> call, final Response<GeneralResponse> response) {
try {
if(response.body().getStatus()){
meterImage = "";
distanceKm = "";
Toast.makeText(TimeCard.this,response.body().getMessage(),Toast.LENGTH_LONG).show();
}else {
Toast.makeText(TimeCard.this,response.body().getMessage(),Toast.LENGTH_LONG).show();
}
}catch (Exception e){
progressDialog.dismiss();
e.printStackTrace();
Toast.makeText(TimeCard.this,e.getMessage(),Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<GeneralResponse> call, Throwable t) {
progressDialog.dismiss();
Toast.makeText(TimeCard.this,"Server error please try again later",Toast.LENGTH_LONG).show();
}
});
} else {
Toast.makeText(TimeCard.this,"Please check your internet connection",Toast.LENGTH_LONG).show();
}
}
it gives error because only one type of annotation is allowed.so i change #Header to #Part
#Multipart
#NonNull
#FormUrlEncoded
#POST("upload")
Call<GeneralResponse> uploadImage(#Part("Authorization") MultipartBody.Part token, #Part("image") MultipartBody.Part image, #Part("kilometer") MultipartBody.Part distance);
now how can I pass header? (how to convert header to MultipartBody.Part?)
I'll start with answering your question
it gives error because only one type of annotation is allowed
Yes, it's intended behavior of Retrofit. You can not use multiple annotations at once. So, just remove #FormUrlEncoded annotation from uploadImage() method.
Now, you want post distance along with the image. For that you should use like below inside uploadImage() method (in service class).
#Part("kilometer") RequestBody distance // note: use RequestBody instead of MultipartBody.Part
And inside upload() function (in your Activity) do the changes.
RequestBody kilometer = RequestBody.create(MediaType.parse("text/plain"), distance);
Now come to the question
how can I pass header using #Part in retrofit?
I assume that you've dynamic authorization key (by looking into the sample code in question).
If you have dynamic Authorization key to be set to header at runtime, you could use Retrofit's #HeaderMap annotation.
Just do the below changes in uploadImage() method.
#HeaderMap Map<String, String> token
Inside Activity prepare header maps like below.
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", token);
If you want set other header parameters, set it in the same headers hash map.
Then pass this header map to your uploadImage() method.
So, the final changes in your code goes like this.
Your service interface
#Multipart
#NonNull
#POST("upload")
Call<GeneralResponse> uploadImage(#HeaderMap Map<String, String> token, #Part("image") MultipartBody.Part image, #Part("kilometer") RequestBody distance);
and in Activity
public void upload(final String token,final String distance, final File image) {
InternetConnection internetConnection = new InternetConnection(TimeCard.this);
if (internetConnection.isConnectingToInternet()) {
showProgressDialog();
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
// prepare distance body
RequestBody kilometer = RequestBody.create(MediaType.parse("text/plain"), distance);
// prepare headers
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", token);
Call<GeneralResponse> call = apiService.uploadImage(headers, bodyImage, kilometer);
call.enqueue(new Callback<GeneralResponse>() {
#Override
public void onResponse(Call<GeneralResponse> call, final Response<GeneralResponse> response) {
try {
if(response.body().getStatus()){
meterImage = "";
distanceKm = "";
Toast.makeText(TimeCard.this,response.body().getMessage(),Toast.LENGTH_LONG).show();
}else {
Toast.makeText(TimeCard.this,response.body().getMessage(),Toast.LENGTH_LONG).show();
}
}catch (Exception e){
progressDialog.dismiss();
e.printStackTrace();
Toast.makeText(TimeCard.this,e.getMessage(),Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<GeneralResponse> call, Throwable t) {
progressDialog.dismiss();
Toast.makeText(TimeCard.this,"Server error please try again later",Toast.LENGTH_LONG).show();
}
});
} else {
Toast.makeText(TimeCard.this,"Please check your internet connection",Toast.LENGTH_LONG).show();
}
}
Extra
If you've static token (token that never changes per user or API key), you don't need to use header map pattern here. Just skip #HeaderMap annotation/parameter. Make the changes like below.
#Headers("Authorization: your_token_key_here")
#Multipart
#NonNull
#POST("upload")
Call<GeneralResponse> uploadImage(#Part("image") MultipartBody.Part image, #Part("kilometer") RequestBody distance);
Again if you've more than one headers, enclose header parameters inside {} braces.
#Headers({
"Authorization: your_token_key_here",
"Content-Type: application/json", // just an example
"some other header here"
})
#Multipart
#NonNull
#POST("upload")
Call<GeneralResponse> uploadImage(#Part("image") MultipartBody.Part image, #Part("kilometer") RequestBody distance);

Retrofit - Error 400

I'm having issues trying to upload images and other details to my server using the below code:
public static final String APP_JSON = "Content-Type: application/json";
#Multipart
#Headers({ApiClient.APP_JSON})
#POST("relatorio/{token}")
Call<JsonObject> uploadJson(#Path("token") String token, #Part("json") JsonObject auditoria, #Part List<MultipartBody.Part> files);
It always return error 400 due to SyntaxError: Unexpected token <br>;
However, if I send only the details as RequestBody it works.
I need to construct an array of images to upload as defined in my server; thus, I'm using it:
private List<MultipartBody.Part> prepare(){
Set<String> keys = JsonFormFragmentPresenter.imagesList.keySet();
List<MultipartBody.Part> parts = new ArrayList<>();
for(String k : keys){
for(String path : JsonFormFragmentPresenter.imagesList.get(k)){
if(!path.equals(null) && !path.equals("")){
File image = new File(path);
RequestBody requestFile = RequestBody.create(
MediaType.parse("multipart/form-data"), image
);
Log.e("Test", (requestFile == null) + "");
parts.add(MultipartBody.Part.createFormData("imagens", image.getName(), requestFile));
}
}
}
JsonFormFragmentPresenter.imagesList.clear();
return parts;
}
After that I make the call that is always returning 400:
Call<JsonObject> call = ApiClient.get().uploadJson(details.get(KEY_TOKEN), json, prepare());
call.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.e("Status", new Gson().toJson(response));
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e("Status", "Faild");
Log.e("UPDATErr", new Gson().toJson(t));
}
});
Any hint on why it is not working?
Thank you,
You are using #Header annotation with Content-Type: application/json, but you need multipart POST request which uses different content type. Try to remove #Header annotation and replace it with #Multipart.

Android - Retrofit - File Uploaded to AWS S3 with pre-signed url is corrupt

In my application, I need to upload images directly to AWS S3. For this my server generates a pre-signed url and mobile client use that url to PUT file on. Though getting 200 in upload call, the file is not uploaded correctly i.e. it's corrupt and never loads back.
Following is the code being used to upload file to S3.
public static interface FileUploadService {
#PUT("/")
void upload(#Body() RequestBody body,
Callback<Object> callback);
}
ServiceGenerator.getUploadService(url).upload(
RequestBody.create(MediaType.parse("image/jpeg"), image),
new Callback<Object>() { });
I'm using Retrofit 1.8.
Hello Try this if it helps but I used Retrofit 2,
Gradle
compile 'com.squareup.retrofit2:retrofit:2.0.2'
The Interface
public static interface FileUploadService {
#Multipart
#POST(HttpConstants.FILEUPLOADJSON1)
Call<Result> uploadImage(#Part MultipartBody.Part file, #Part("stdID") int stdID);
}
The Call
RaytaApi service= RaytaServiceClass.getApiService();
File file = new File(imagePath);
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body =
MultipartBody.Part.createFormData("uploaded_file", file.getName(), requestFile);
Call<Result> resultCall=service.uploadImage(body,INT_STDID);
final Result[] result = {new Result()};
resultCall.enqueue(new Callback<Result>() {
#Override
public void onResponse(Call<Result> call, Response<Result> response) {
Log.v("###WWE","Respnse");
}
}
#Override
public void onFailure(Call<Result> call, Throwable t) {
Log.v("###WWE","Failure ");
Log.v("###WWE","MEssage "+t.getMessage());
}
});
For complete example referance please visit
https://github.com/pratikvyas1991/NetworkingExample-master
Fixed as following;
When using RequestBody, the Content-Length header was not carrying the correct info neither I was able to override it. So, I used TypedInput instead of RequestBody. My service interface looks like following.
public static interface FileUploadService {
#PUT("/")
void upload(#Body TypedInput body, Callback<Object> callback);
}
And image is uploaded as;
ServiceGenerator.getUploadService(url).upload(new TypedFile("image/*", file),
new Callback<Object>() { });

Retrofit 2 file upload does not attach file

I've written a upload action on server using asp core and I've tested that with ARC and files gets received.
But when I try to upload image with Retrofit, nothing gets send. Even the form is empty:
The source Code of interface is here. The interface:
public interface QuestionWebService {
#Multipart
#POST("questionapi/uploadfiles")
Call<ResponseBody> uploadSync(#Part("fileUpload") RequestBody paramTypedFile);
}
and the usage in async task:
#Override
protected Boolean doInBackground(String... params) {
File fileToSend = new File(params[0]);
// fileToSend.renameTo()
RequestBody typedFile = RequestBody.create(MediaType.parse("image/*"), fileToSend);
Response response = restClient.getQuestionService().uploadSync(typedFile).execute();
if (response == null){
Log.e(TAG, "success send server - failed");
return false;
}
if (response.isSuccessful()) {
Log.e(TAG, "success send server - 200 status");
} else {
Log.e(TAG, "success send server - fail status - " + response.toString());
}
} catch (Exception e) {
//throw new RuntimeException(e);
Log.e(TAG,e.getMessage().toString());
return false;
}
return true;
}
Any Idea about what should I try? Where am I Going Wrong.
TG.
Finally I found the solution. I don't know the reason about why this code doesn't work but as this link says, I changed the:
public interface QuestionWebService {
#Multipart
#POST("questionapi/uploadfiles")
Call<ResponseBody> uploadSync(#Part("fileUpload") RequestBody paramTypedFile);
}
to this one:
public interface QuestionWebService {
#Multipart
#POST("questionapi/uploadfiles")
Call<ResponseBody> uploadSync(#Part("UserId") RequestBody UserId, #Part("answer") RequestBody answer, #Part MultipartBody.Part file);
}
and the usage from this:
RequestBody typedFile = RequestBody.create(MediaType.parse("image/*"), fileToSend);
Response response = restClient.getQuestionService().uploadSync(typedFile).execute();
to this one:
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(MediaType.parse("multipart/form-data"), fileToSend);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body =
MultipartBody.Part.createFormData("fileUpload", fileToSend.getName(), requestFile);
RequestBody userId =
RequestBody.create(
MediaType.parse("multipart/form-data"), userIdString);
// add another part within the multipart request
String answerString = "hello, this is answer speaking";
RequestBody answer =
RequestBody.create(
MediaType.parse("multipart/form-data"), answerString);
Response response = restClient.getQuestionService().uploadSync(userId, answer, body).execute();
and now every thing goes right!!!
I hope this will the others encounter same problem.
Now the data on server is a form with 2 fields, UserId and Answer, and a file named fileUpload.
TG.

Multipart Retrofit 2.0 image upload

I am trying to upload image by POST multipart request which should have structure like this :
-----------------------------219391268715340
Content-Disposition: form-data; name="photos[]"; filename="DSCF0157-Laptop.JPG"
Content-Type: image/jpeg
(byte-data)
My code :
MediaType mediaType = MediaType.parse("image/jpeg");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
RequestBody file=RequestBody.create(mediaType, byteArray);
map.put("form-data; name=\"photos[]\"; filename=\""+filename+".jpg",file);
I use map because of #PartMap annotation - I want to upload multiple files. My server returns http code 200 - but no files are uploaded. Api call has been tested - it works correctly if used by our web application. Any idea what I am doing wrong
If you want to upload many files in a request using Retrofit 2, you can refer to my answer at the question below
Retrofit - Multipart request: Required MultipartFile parameter 'file' is not present
With some modifications:
WebAPIService.java:
#Multipart
#POST("/api/fileupload")
Call<ResponseBody> postFiles(#Part List<MultipartBody.Part> fileList);
FileActivity.java:
...
List<MultipartBody.Part> fileList = new ArrayList<>();
for (int i = 0; i < 2; i++){
fileList.add(body);
}
Call<ResponseBody> call = service.postFiles(fileList);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call,
Response<ResponseBody> response) {
Log.i(LOG_TAG, "success");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.e(LOG_TAG, t.getMessage());
}
});
Of course, with the above code, in fact, the same file (body) will be uploaded as 2 parts in the request. As a result, in web server, you will have 2 files with the same content, you can customize the fileList with many different files :)
Hope it helps!
Maybe you can do that like this.
YourAPIService
#Multipart
#POST("api/image/upload")
Call<ImageUploadResponse> uploadImage(#Part("photos") List<RequestBody> imageFiles);
YourActivity
//prepare request body
List<RequestBody> images = new ArrayList<>();
for (int i = 0; i < filePath.size(); i++){
RequestBody file = RequestBody.create(MediaType.parse("image/*"), filePath.get(i);
images.add(file);
}
//call api
Call<ImageUploadResponse> call = imageUploadService.uploadImage(images);
call.enqueue(new Callback<ImageUploadResponse>() {
#Override
public void onResponse(Call<ImageUploadResponse> call,
Response<ImageUploadResponse> response) {
}
#Override
public void onFailure(Call<ImageUploadResponse> call, Throwable t) {
}
});

Categories

Resources