Multipart Upload to Amazon S3 using Retrofit - android

I'm attempting to convert all my asynctasks and HttpPost code to use Retrofit, so far so good, but I'm having trouble uploading user files to an amazon s3 bucket.
The file upload has two parts:
Query api to get a upload_url and amazon upload params.
Upload File to specified location with params provided in first call.
Here's an example list of parameters provided to me from the first call. According to the documentation, these values can change or not be included, and the 2nd api call must call use these params in the exact order they were provided.
"AWSAccessKeyId": "some_id",
"key": "/users/1234/files/profile_pic.jpg",
"acl": "private",
"Filename": "profile_pic.jpg",
"Policy": "some_opaque_string",
"Signature": "another_opaque_string",
"Content-Type": "image/jpeg"
In order to deal with the dynamic content. I created a custom converter to have retrofit return me a LinkedHashMap in my first API call.
public class CustomConverter implements Converter {
#Override public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
...
Type mapType = new TypeToken<LinkedHashMap<String, String>>(){}.getType();
return new Gson().fromJson(JSON_STRING, mapType);
}
Then in the second api call, Once I have these values I create a FormUrlEncodedTypedOutput by iterating over the HashMap and adding each item.
FormUrlEncodedTypedOutput params = new FormUrlEncodedTypedOutput();
for (Map.Entry<String, String> entry : uploadParams.entrySet()) {
params.addField(KEY, VALUE);
}
Everything up to here seems to be working. I'm getting the necessary upload params and the ordering seems to be consistent. I'm a bit less sure about how I have my multipart retrofit call setup. I then use that inside a synchronous retrofit call inside an intentservice.
#Multipart
#POST("/")
Response uploadFile(#Part ("whatdoesthisdo?") FormUrlEncodedTypedOutput params, #Part("File") TypedFile file);
This results in a Amazon error.
"code" : "InvalidArgument"
"message" : "Bucket POST must contain a field named 'key'. If it is specified, please check the order of the fields."
I've been googling and it seems like Amazon prefers the "key" value to be first? However, if i put the "key" in front of "AWSAccessKeyId" I get a 403 unauthorized error. Do i have my retrofit call setup correctly? If someone could help me figure this out, I'd appreciate it. It's taken a few days to convert most of my uploading code over to retrofit and if I've been stuck on this for a while.
Thanks!

Solution was to use a #PartMap instead of the FormUrlEncodedTypedOutput.
#Multipart
#POST("/")
Response uploadFile(#PartMap LinkedHashMap<String,String> params, #Part("File") TypedFile file);

Related

Send optional field and multipart in retrofit

I want to send image , name and city for the server and all of these can be optional but i don't know how the interface will look like i found a solution with overloading the methods but it makes the code complicated
The http method is PUT also .
Use the annotation for Multipart. Create a Map of city and name to be used as PartMap. Pass the image as MultipartBody. Something like this:
#Multipart
#POST("")
Call<ReturnType> yourCall(#Url String url, #PartMap() Map<String, RequestBody> params, #Part MultipartBody.Part multipart, ...);

Why Post method of okhttp occur error [Content-Type header specified in HTTP request is not supported: application/x-www-form-urlencoded]?

I call a Rest API of salesforce by post method:
url = "https://test-dev-ed.my.salesforce.com/services/apexrest/AccountUsers/"
client = OkHttpClient()
val jsonIn = FormBody.Builder()
.add("email",URLEncoder.encode("dt1#gmail.com", "UTF-8"))
.add("password", URLEncoder.encode("1","UTF-8"))
.build()
request = Request.Builder()
.post(jsonIn)
.header("Authorization", "Bearer "+accesstoken)
.addHeader("Content-Type","application/x-www-form-urlencoded")
.url(url)
.build()
response = client.newCall(request).execute()
This is rest api:
#HttpPost
global static ID createUser(String email, String password) {
AccountUser__c us=new AccountUser__c();
us.Email__c=email;
us.Password__c=password;
us.Status__c=0;
insert us;
return us.Id;
}
But result return is error:
[{"errorCode":"UNSUPPORTED_MEDIA_TYPE","message":"Content-Type header specified in HTTP request is not supported: application/x-www-form-urlencoded"}]
I had try change application/json to application/x-www-form-urlencoded , but still can't resolve.
I try call a Get method, it is ok.
Why Post method occur error [Content-Type header specified in HTTP request is not supported]?
I would like to suggest a better resolution. Retrofit Library
Even though it is not mandatory to use Retrofit, these are few eye catchy aspects which makes it reliable and handy in similar use case of yours.
Why to use Retrofit?
Type-safe REST Adapter that makes common networking tasks easy
For POST operations, retrofit helps in assembling what needed to be submitted. Eg:- Generating URL encoded form.
Takes care of URL manipulation, requesting, loading, caching, threading, synchronization, sync/async calls
Helps to generate URL using type-aware generated code tied to specific REST API
Parsing JSON using GSON
Retrofit is an API adapter wrapped over OkHttp
The problem that you are facing can be resolved using retrofit like this.
public interface APIConfiguration{
#Headers({"Accept:application/json",
"Content-Type:application/x-www-form-urlencoded"})
#FormUrlEncoded
#POST("user/registration")
Observable<DataPojo> registrationAPI(#FieldMap(encoded = true) Map<String, String> params);
}
That's it, with few annotation the library takes care of Form URL
Encoding and related dependencies.
As it is inappropriate to start from corresponding Retrofit dependencies and sample code, you can go through Reference One and Reference Two for more details.
As per my understanding just checkout the difference the content type header "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.
The content "multipart/form-data" follows the rules of all multipart MIME data streams.
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
Also try your http request by setting your content type header as multipart/formdata.

Retrofit2: Use Body vs Query

I have a web-service method called change. I send UpdateStatusRequest objects to this web-service which defined as below:
public class UpdateStatusRequest {
private String Status;
public UpdateStatusRequest(String status) {
Status = status;
}
public String getStatus() {
return Status;
}
}
When I use below API deceleration:
#POST("StatusUpdate")
Call<Status> change(#Query("Status") String status);
and then calling statusApi.change(request.getStatus()), it works well. It will call http://server-url/StatusUpdate?Status=Ready, when I pass Ready as status.
But using below declaration
#POST("StatusUpdate")
Call<Status> change(#Body UpdateStatusRequest status);
and then calling statusApi.change(request), it will call http://server-url/StatusUpdate and sends Status in request body. This will lead to 404 status code with error prompt Not Found.
I want to know what's wrong with my second call (since I supposed #Body acts like several #Query parameters which bundled together in the same object)?
In Retrofit,
#Body doesn't same as #Query.
#Body – Sends Java objects as request body.
#Query- Query parameter appended to the URL.null values are ignored.
But #Field is almost similar to #Body tag.
#Field – Send data as form-urlencoded. The #Field parameter works only with a POST.
For Example:
#POST("StatusUpdate")
Call<Status> change(#Field("Status") String Status);
But in your case, Your server is expecting the params to be passed in
the URL(#Query).
Hope this explanation help.
#Body doesn't act like several #Query parameters. These are two different ways of sending data in a request.
The differences are pretty much already described in your question. With #Query,it will append the URL with the query params you pass, as in http://server-url/StatusUpdate?Status=Ready. Instead, if you use #Body, the params will be added to the body request, so your URL will have no params, as in http://server-url/StatusUpdate, and your body request will be Status=Ready.
Based on the results you got, your server is expecting the params to be passed in the URL(#Query).

Retrofit, how to parse JSON objects with variable keys

First I know my title is bad as I didn't come up with better, I'm opened to suggestion.
I'm using retrofit to get data from an api of this kind : #GET("users/{userid}")
It works fine and I'm happy with it, the problem is when I call the same api with #POST("users/widget") with a list of ids. I have the following answer :
{
"long_hash_id": {
"_id": "long_hash_id"
.......
},
"long_hash_id": {
"_id": "long_hash_id",
.....
},
........
}
the "long_hash_id" is typicaly "525558cf8ecd651095af7954"
it correspond to the id of the user attached to it.
When I didn't use retrofit, I used Gson in stream mode to get each user one by one. But I don't know how to tell retrofit.
Hope I'm clear and
Thank you in advance.
----------- Solution :
I made my interface this way :
#FormUrlEncoded
#POST(AppConstants.ROUTE_USER_GROUP)
Call<Map<String,User>> getUsers( #Field("ids") List<String> param, #QueryMap Map<String,String> options);
and I simply gave my ArrayList of ids. Thank you very much
Gson is able to deal with JSON objects with variable keys like the one you posted. What you have to do, in this case, is to declare a Map<String, ModelClass>, where ModelClass is the content of the JSONObject you want to represent

Where in Retrofit build the full JSON before sending it?

I am using retrofit an get Bad Request , I would want to know if there is a place in this library where builds the full JSON in string format before sending it.
If it's about inspecting the JSON at runtime for debugging purposes, you can call setLogLevel(LogLevel.FULL) on your RestAdapter.Builder.
FULL logs the headers, body and metadata for both requests and responses to logcat.
new String(((TypedByteArray) request.getBody()).getBytes());
In order to build a JSON formatted body, create an object with a class whose properties are the same that you want to send to the server. The GSON Library set up (or whichever library you are using) with the RestAdapter should send the request with the body in JSON format.
Also ensure that the call is #POST annotated and the parameter annotd with #Body Below is an example:
#POST("/login")
User login(#Body LoginUser loginUser);

Categories

Resources