I'm trying to upload a file to Amazon's S3 using a pre-signed URL. I get the URL from a server which generates the URL & sends it to me as part of a JSON object. I get the URL as a String, something like this:
https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/ImageName?X-Amz-Security-Token=xxfooxx%2F%2F%2F%2F%2F%2F%2F%2F%2F%2Fxxbarxx%3D&X-Amz-Algorithm=xxAlgoxx&X-Amz-Date=20170831T090152Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=xxcredxx&X-Amz-Signature=xxsignxx
Unfortunately, when I pass this to Retrofit2, it modifies the String attempting to make it into a URL. I've set encoding=true which took care of most of the problem but not completely. I know the String works as it is. I've tried it in Postman & get a successful response.
1st I tried just putting the String (except for what I cut out as baseUrl) as a whole into the Path
public interface UpdateImageInterface {
#PUT("{url}")
Call<Void> updateImage(#Path(value="url", encoded=true) String url, Body RequestBody image);
}
The calling code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is "ImageName..."
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
This works mostly except the the '?' (after "ImageName") get converted to "%3F". This causes a Bad Request / 400.
My next attempt was to create a query with Retrofit2 but then dump the whole String (with multiple queries) into the query.
public interface UpdateImageInterface {
#PUT("ImageName")
Call<Void> updateProfilePhoto(#Query(value="X-Amz-Security-Token", encoded = true) String token, #Body RequestBody image);
}
The calling code:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://com-example-mysite.s3-us-east-1.amazonaws.com/userFolder/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is "xxfooxx..."
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
This gets the '?' rendered correctly but all of the '&' get changed to "%26"
Lastly I tried passing the whole String in baseUrl() but that gives an IllegalArgumentException for not having '/' on the end.
I know that I could parse the pre-signed URL to make multiple queries & assemble them in Retrofit2 as queries should be done but I'd like to avoid that processing.
To restate the question:
Is there a way to easily (without heavy String parsing) upload a file to S3 with a pre-signed URL using Retrofit2?
With help from a colleague, this is the solution.
public interface UpdateImageInterface {
#PUT
Call<Void> updateImage(#Url String url, #Body RequestBody image);
}
Calling code:
String CONTENT_IMAGE = "image/jpeg";
File file = new File(localPhotoPath); // create new file on device
RequestBody requestFile = RequestBody.create(MediaType.parse(CONTENT_IMAGE), file);
/* since the pre-signed URL from S3 contains a host, this dummy URL will
* be replaced completely by the pre-signed URL. (I'm using baseURl(String) here
* but see baseUrl(okhttp3.HttpUrl) in Javadoc for how base URLs are handled
*/
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://www.dummy.com/")
.build();
UpdateImageInterface imageInterface = retrofit.create(UpdateImageInterface.class);
// imageUrl is the String as received from AWS S3
Call<Void> call = imageInterface.updateImage(imageUrl, requestFile);
Javadoc for info on #Url (class Url) &
baseUrl() (class Retrofit.Builder)
MediaType is a class in the OkHttp library that is often used with Retrofit (both from Square). Info about constants passed to the parse method can be found in the Javadoc.
Use the following while uploading directly to S3 using presigned URL.
#Multipart
#PUT
#Headers("x-amz-acl:public-read")
Call<Void> uploadFile(#Url String url, #Header("Content-Type") String contentType, #Part MultipartBody.Part part);
Related
I am trying to post some multipart form data via Retrofit in Android to a web service.
The web services's API expects the following parameters to be passed as field of a multipart form data:
Name Type
company_id text/plain
image image/*
It also expects an authorization token to be passed as a query string parameter.
So i have defined my API interface call like this:
#Multipart
#POST("/companies/uploadImage")
#Headers({
"Content-Type: multipart/form-data",
"Accept: application/json"
})
Call<ServerResponse> companyUploadImage( #Part("company_id") RequestBody companyId, #Part MultipartBody.Part file, #Query("token") String token);
Afterwards this is how i call the above API interface method in a custom class:
RequestBody companyId = RequestBody.create(MediaType.parse("text/plain"), LocalStorage.getInstance().getCompanyId());
File file = new File(postPath);
MultipartBody.Part image = MultipartBody.Part.createFormData("image", file.getName(), RequestBody.create(MediaType.parse("image/*"), file));
Call<ServerResponse> uploadProfileImage = router.companyUploadImage(companyId, image, token);
uploadProfileImage.enqueue(new Callback<ServerResponse>() {
#Override
public void onResponse(Call<ServerResponse> call, Response<ServerResponse> response) {
Log.e("Upload Profile Image: ", response.body().getMessage());
}
#Override
public void onFailure(Call<ServerResponse> call, Throwable t) {
Log.e("Upload Profile Image Error: ", t.getMessage());
}
});
The request is sent successfully to the server, so no networking exceptions occur, however the multipart form values, company_id and image, are received empty on the server side
Any idea?
Thank you!
I managed to solve the issue. It seems that the solution was as simple as removing the #Header annotation from the API interface method. So now it looks like this:
#Multipart
#POST("/companies/uploadImage")
Call<ServerResponse> companyUploadImage( #Part("company_id") RequestBody companyId, #Part MultipartBody.Part file, #Query("token") String token);
Maybe someone will find this post helpful.
I would like to understand how Retrofit works, but the official documentation is very weak.
I need to make a very simple GET request and get the response as a String.
Now I use standard HTTPUrlConnection and it works nicely, just request - response
Can anyone tell me how to get a String response without converting it to an object or something like that?
You can use ScalarsConverterFactory for strings and both primitives and their boxed types to text/plain bodies.
Add this dependency to your build.gradle file:
compile 'com.squareup.retrofit2:converter-scalars:2.1.0'
Try this:
public interface ExampleService {
#GET("/users/{user}/repos")
Call<String> listRepos(#Path("user") String user);
}
And add ScalarsConverterFactory to your builder:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
You can then retrieve this string like this:
Call<String> call = exampleService.listRepos(user);
Response<String> response = call.execute();
String value = response.body();
I am using the latest okhttp version: okhttp-2.3.0.jar
How to add query parameters to GET request in okhttp in java ?
I found a related question about android, but no answer here!
For okhttp3:
private static final OkHttpClient client = new OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
public static void get(String url, Map<String,String>params, Callback responseCallback) {
HttpUrl.Builder httpBuilder = HttpUrl.parse(url).newBuilder();
if (params != null) {
for(Map.Entry<String, String> param : params.entrySet()) {
httpBuilder.addQueryParameter(param.getKey(),param.getValue());
}
}
Request request = new Request.Builder().url(httpBuilder.build()).build();
client.newCall(request).enqueue(responseCallback);
}
Here's my interceptor
private static class AuthInterceptor implements Interceptor {
private String mApiKey;
public AuthInterceptor(String apiKey) {
mApiKey = apiKey;
}
#Override
public Response intercept(Chain chain) throws IOException {
HttpUrl url = chain.request().url()
.newBuilder()
.addQueryParameter("api_key", mApiKey)
.build();
Request request = chain.request().newBuilder().url(url).build();
return chain.proceed(request);
}
}
I finally did my code, hope the following code can help you guys. I build the URL first using
HttpUrl httpUrl = new HttpUrl.Builder()
Then pass the URL to Request requesthttp hope it helps .
public class NetActions {
OkHttpClient client = new OkHttpClient();
public String getStudentById(String code) throws IOException, NullPointerException {
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("https")
.host("subdomain.apiweb.com")
.addPathSegment("api")
.addPathSegment("v1")
.addPathSegment("students")
.addPathSegment(code) // <- 8873 code passthru parameter on method
.addQueryParameter("auth_token", "71x23768234hgjwqguygqew")
// Each addPathSegment separated add a / symbol to the final url
// finally my Full URL is:
// https://subdomain.apiweb.com/api/v1/students/8873?auth_token=71x23768234hgjwqguygqew
.build();
System.out.println(httpUrl.toString());
Request requesthttp = new Request.Builder()
.addHeader("accept", "application/json")
.url(httpUrl) // <- Finally put httpUrl in here
.build();
Response response = client.newCall(requesthttp).execute();
return response.body().string();
}
}
As mentioned in the other answer, okhttp v2.4 offers new functionality that does make this possible.
See http://square.github.io/okhttp/2.x/okhttp/com/squareup/okhttp/HttpUrl.Builder.html#addQueryParameter-java.lang.String-java.lang.String-
This is not possible with the current version of okhttp, there is no method provided that will handle this for you.
The next best thing is building an url string or an URL object (found in java.net.URL) with the query included yourself, and pass that to the request builder of okhttp.
As you can see, the Request.Builder can take either a String or an URL.
Examples on how to build an url can be found at What is the idiomatic way to compose a URL or URI in Java?
As of right now (okhttp 2.4), HttpUrl.Builder now has methods addQueryParameter and addEncodedQueryParameter.
You can create a newBuilder from existing HttoUrl and add query parameters there. Sample interceptor code:
Request req = it.request()
return chain.proceed(
req.newBuilder()
.url(
req.url().newBuilder()
.addQueryParameter("v", "5.60")
.build());
.build());
Use HttpUrl class's functions:
//adds the pre-encoded query parameter to this URL's query string
addEncodedQueryParameter(String encodedName, String encodedValue)
//encodes the query parameter using UTF-8 and adds it to this URL's query string
addQueryParameter(String name, String value)
more detailed: https://stackoverflow.com/a/32146909/5247331
I have one property in property file
appointments.deleteAppointmentwithReasonApi=api/appointment/{id}?reason={reason}
URL=http://xyz/etc/
in another file
public static final String DELETE_APPOINTMENT_REASON = PropertiesUtil.getPropertyValueFromKey(REST_WEBSERVICE_URLS_PROP_FILE,
"appointments.deleteAppointmentwithReasonApi"); // To get API name
public static final String URL = ServicesUtil.getURL(); // to get endpoint URL
In my java API call, I gave something like this
WebTarget target = client.target(CommonConstants.URL)
.path(CommonConstants.DELETE_APPOINTMENT_REASON)
.resolveTemplate("id", appointmentID).resolveTemplate("reason", reason);
System.out.println(target);
My response is printing like this...
JerseyWebTarget { http://xyz/etc/api/appointment/abc-123-ced-456%3Freason=Test }
which is not hitting the proper Web Services...I want it to be like this
JerseyWebTarget { http://xyz/etc/api/appointment/abc-123-ced-456?reason=Test }
I know i need to encode URL. I am not able to do it somehow. Any suggestion ?
I'm trying to pass a string of the format below as the body of a http post request.
param1=PARAM1¶m2=PARAM2¶m3=PARAM3
But retrofit encodes my body so that = becomes \u003d and & becomes \u0026. And I end up with a string which actually looks like this:
param1\u003dPARAM1\u0026param2\u003dPARAM2\u0026param3\u003dPARAM3
How can I prevent that?
My retrofit rest api is defined as follows.
public interface RestAPI {
#POST("/oauth/token")
public void getAccessToken(#Body String requestBody, Callback<Response> response);
}
If you have a serialized class (like a HashMap) in the request body and you want to prevent encoding that (like in vezikon's and my problem), you can create a custom Gson with disabled escaping using:
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
Pass this converter to your rest adapter:
yourRestAdapter = new RestAdapter.Builder()
.setEndpoint(.....)
.setClient(.....)
.setConverter(new GsonConverter(gson))
.build();
This way the "=" characters in the post body stay intact while submitting.
To answer the question directly, you can use TypedString as the method parameter type. The reason the value is being changed is because Retrofit is handing the String to Gson in order to encode as JSON. Using TypedString or any TypedOutput subclass will prevent this behavior, basically telling Retrofit you will handle creating the direct request body yourself.
However, that format of payload is called form URL encoding. Retrofit has native support for it. Your method declaration should actually look like this:
#FormUrlEncoded
#POST("/oauth/token")
void getAccessToken(
#Field("param1") String param1,
#Field("param2") String param2,
#Field("param3") String param3,
Callback<Response> callback);
Using Kotlin
For Retrofit 2 you can initialize retrofit with a Gson converter factory.
val builder = GsonBuilder().disableHtmlEscaping().create()
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(builder))
.client(monoOkHttpClient())
.build()
This builder should remove escaping from your json output.
Gradle file dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
This issue can be fixed with below workaround.
#POST("yourString")
Call<YourResponseModel> yourCallMethod(#Query("yourKey") String yourValue,
#Query("yourKey") String yourValue,
#Query("yourKey") String yourValue);
Note : Don't use "#FormUrlEncoded" for this case.
Reference Here - https://github.com/square/retrofit/issues/1407