Retrofit 2 appending post to requestbody in intercept - android

I have this:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.httpUrl().newBuilder()
.addQueryParameter("platform", "android")
.addQueryParameter("app_version", com.package.BuildConfig.VERSION_NAME)
.build();
Request newRequest = chain.request().newBuilder().url(url).build();
return chain.proceed(newRequest);
}
});
but would also like to append an additional post key-value to the request body containing the userkey. This would look something like
RequestBody newBody = RequestBody.create(request.body().contentType(),request.body().content+ request.addPost("sUserKey","3254345kdskf");
...
...
Request newRequest = chain.request()
.newBuilder()
.url(url)
.post(newBody)
.build();

Appending to the RequestBody is not straight forward. Here is a sketch of a custom RequestBody that will add a post parameter. Couple of caveats -- you will want to add some error checking, like making sure the existing body is not null. The given code also assumes all calls coming to this interceptor are POSTs. If that is not that case, you'll need to check the incoming request type before applying the new body. Also, since this just copies the new parameter into the body, you'll need to make sure the name and body are already url encoded if needed.
class AddPostParamRequestBody extends RequestBody {
final RequestBody body;
final String parameter;
AddPostParamRequestBody(RequestBody body, String name, String value) {
this.body = body;
this.parameter = "&" + name + "=" + value;
}
#Override
public long contentLength() throws IOException {
return body.contentLength() + parameter.length();
}
#Override
public MediaType contentType() {
return body.contentType();
}
#Override
public void writeTo(BufferedSink bufferedSink) throws IOException {
body.writeTo(bufferedSink);
bufferedSink.writeString(parameter, Charset.forName("UTF-8"));
}
}
Then you can use in your interceptor --
client.interceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.httpUrl().newBuilder().addQueryParameter("added", "param").build();
AddPostParamRequestBody newBody = new AddPostParamRequestBody(request.body(), "sUserKey","3254345kdskf");
Request newRequest = request.newBuilder().post(newBody).url(url).build();
return chain.proceed(newRequest);
}
});
Your other option is to the include an extra Field annotation in your retrofit definition and pass it in on every call, but I assume you were trying to avoid that.

You can do it without creating additional class.
client.interceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
String parameter = "&" + name + "=" + value;
Request newRequest = interceptRequest(request, parameter)
return chain.proceed(newRequest);
}
});
This is simple method that create new request.
public static Request interceptRequest(#NotNull Request request, #NotNull String parameter)
throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Sink sink = Okio.sink(baos);
BufferedSink bufferedSink = Okio.buffer(sink);
/**
* Write old params
* */
request.body().writeTo(bufferedSink);
/**
* write to buffer additional params
* */
bufferedSink.writeString(parameter, Charset.defaultCharset());
RequestBody newRequestBody = RequestBody.create(
request.body().contentType(),
bufferedSink.buffer().readUtf8()
);
return request.newBuilder().post(newRequestBody).build();
}
Also you can get it from Gist

Related

Android RetroFit: 400 Response

I am plugging Retrofit into my android app.
Here is how I build retrofit, notice the interceptor for the logging and headers.
public void buildRetrofit(String token){
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.header("api-version", "1")
.method(chain.request().method(), chain.request().body())
.build();
return chain.proceed(newRequest);
}
});
httpClient.addInterceptor(logging);
Retrofit.Builder buidler = new Retrofit.Builder()
.baseUrl("XXX_HIDDEN_FORSTACKOVERFLOW")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build());
retroFit = buidler.build();
}
I make the call like so
OrderApi orderApi = mainActivity.retroFit.create(OrderApi.class);
Call<Order> call = orderApi.getOpenOrder();
call.enqueue(new Callback<Order>() {
#Override
public void onResponse(Call<Order> call, Response<Order> response) {
Order a = response.body();
int b = 1;
}
#Override
public void onFailure(Call<Order> call, Throwable t) {
}
});
And here is how the actual request tag
public interface OrderApi {
#POST("/HIDDEN")
Call<Order> getOpenOrder();
}
Lastly, here is the order class
public class Order {
private String orderId;
private OrderStatus orderStatus;
public String getOrderId(){
return orderId;
}
public OrderStatus getOrderStatus() {
return orderStatus;
}
}
I get a response of 400. I have no idea why, and It works in postman etc. Something to note is that the response contains a lot more properties than just the ones in the class. I just want a proof on concept, but that shouldn't break things right?
.................
Managed to fix it. Had to send an empty body request as it was a post but I wasn't posting anything. API is dumb.
See here to send empty request Send empty body in POST request in Retrofit

Android - OKHttp: how to enable gzip for POST

In our Android App, I'm sending pretty large files to our (NGINX) server so I was hoping to use gzip for my Retrofit POST message.
There are many documentations about OkHttp using gzip transparently or changing the headers in order to accept gzip (i.e. in a GET message).
But how can I enable this feature for sending gzip http POST messages from my device?
Do I have to write a custom Intercepter or something? Or simply add something to the headers?
According to the following recipe:
The correct flow for gzip would be something like this:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new GzipRequestInterceptor())
.build();
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
static class GzipRequestInterceptor implements Interceptor {
#Override public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
#Override public MediaType contentType() {
return body.contentType();
}
#Override public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
#Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
in addition to the accepted answer: First, declare this class (from #Jason)
// to compress the body of request which is injected as interceptor.
public class GzipInterceptor implements Interceptor {
#Override public okhttp3.Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// do not set content encoding in negative use case
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
#Override public MediaType contentType() {
return body.contentType();
}
#Override public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
#Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
then :
//Encryption Interceptor
GzipInterceptor gzipInterceptor = new GzipInterceptor();
// OkHttpClient. Be conscious with the order
OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
//httpLogging interceptor for logging network requests
.addInterceptor(gzipInterceptor)
.build();
Finally add it to your retrofit as a client: (No need for further changes!)
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl(Constants.serveraddress)
.addConverterFactory(GsonConverterFactory.create())
.build();
Serverinterface serverinterface = retrofit.create(Serverinterface.class);

Android Retrofit 2.0 adding headers with interceptor doesn't work

I have Singleton dagger module for OkHttp client and I am trying to add header using Interceptor
#Provides
#Singleton
OkHttpClient provideOkhttpClient(Cache cache, final LocalData localData) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(logging);
client.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader("Hp-Application", "Android");
Request request = requestBuilder.build();
Response originalResponse = chain.proceed(request);
try {
if (originalResponse.code() == 200) {
localData.setLastUpdateTime(System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
return originalResponse;
}
});
client.connectTimeout(60, TimeUnit.SECONDS);
client.cache(cache);
return client.build();
}
But looking to the logs I can't see expected header. Also I receive error, as specific call don't work without required header.
I also tried to add it with addInterceptor()/addNetworkInterceptor() using different class
public class HeaderInterceptor
implements Interceptor {
#Override
public Response intercept(Chain chain)
throws IOException {
Request request = chain.request();
request = request.newBuilder()
.addHeader("Hp-Application", "Android")
.build();
return chain.proceed(request);
}
}
But this way didn't work for me too.
How can I add this header to each call of application having only one implementation?
The order you add the interceptors matters. Your logging interceptor runs first, and only after that is the header-adding interceptor run.
For best logging experience, make the logging interceptor the last one you add.
Hey #Igor try this snippet this might help
public class RetrofitClient {
private static String BASE_URL = "http://192.168.0.100/rest/main.php/";
private static Retrofit retrofit = null;
public static Retrofit getRetroftInstance() {
if (retrofit == null) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addNetworkInterceptor(new SessionRequestInterceptor());
httpClient.addNetworkInterceptor(new ReceivedCookiesInterceptor());
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
}
return retrofit;
}}
public class ReceivedCookiesInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (!originalResponse.headers("Set-Cookie").isEmpty()) {
HashSet<String> cookies = new HashSet<>();
for (String header : originalResponse.headers("Set-Cookie")) {
cookies.add(header);
if(header.startsWith("XSRF-TOKEN")) {
String newCookie[]=header.split(";");
System.out.println("newCookie Length: "+newCookie.length);
for(String ss:newCookie) {
if(ss.startsWith("XSRF-TOKEN")) {
System.out.println("Cookies ss: " + ss);
sharedPrefs.setToken(ss);
}
}
}
}
}
return originalResponse;
}
}
public class SessionRequestInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder request = original.newBuilder();
request.header("Cookie",ServiceSharedPrefs.getInstance().getToken()));
request.method(original.method(), original.body());
return chain.proceed(request.build());
}
}
new OkHttpClient.Builder()
.addInterceptor(
new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder().
header(AUTHENTICATION_HEADER, AUTHENTICATION_KEY).
method(original.method(), original.body());
Request request = requestBuilder.build();
//System.out.println(request.toString());
return chain.proceed(request);
}
}).addInterceptor(logging)
.build();

Retrofit2: Modifying request body in OkHttp Interceptor

I am using Retrofit 2 (2.0.0-beta3) with OkHttp client in Android application and so far everything going great. But currently I am facing issue with OkHttp Interceptor. The server I am communicating with is taking access token in body of request, so when I intercept the request to add auth token or in authenticate method of Authenticator when I need to add updated auth token, I need to modify body of request for this purpose. But it looks like I can only add data in headers but not in the body of ongoing request. The code I have written so far is as follows:
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (UserPreferences.ACCESS_TOKEN != null) {
// need to add this access token in request body as encoded form field instead of header
request = request.newBuilder()
.header("access_token", UserPreferences.ACCESS_TOKEN))
.method(request.method(), request.body())
.build();
}
Response response = chain.proceed(request);
return response;
}
});
Can anyone point me to the right direction as how to modify request body to add my access token (first time or updated after token refresh)? Any pointer to right direction would be appreciated.
I using this to add post parameter to the existing ones.
OkHttpClient client = new OkHttpClient.Builder()
.protocols(protocols)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
RequestBody formBody = new FormEncodingBuilder()
.add("email", "Jurassic#Park.com")
.add("tel", "90301171XX")
.build();
String postBodyString = Utils.bodyToString(request.body());
postBodyString += ((postBodyString.length() > 0) ? "&" : "") + Utils.bodyToString(formBody);
request = requestBuilder
.post(RequestBody.create(MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), postBodyString))
.build();
return chain.proceed(request);
}
})
.build();
public static String bodyToString(final RequestBody request){
try {
final RequestBody copy = request;
final Buffer buffer = new Buffer();
if(copy != null)
copy.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
catch (final IOException e) {
return "did not work";
}
}
OkHttp3:
RequestBody formBody = new FormBody.Builder()
.add("email", "Jurassic#Park.com")
.add("tel", "90301171XX")
.build();
I'll share my Kotlin implementation of #Fabian's answer using Dagger. I wanted origin=app added to the request url for GET requests, and added to the body for form-encoded POST requests
#Provides
#Singleton
fun providesRequestInterceptor() =
Interceptor {
val request = it.request()
it.proceed(when (request.method()) {
"GET" -> {
val url = request.url()
request.newBuilder()
.url(url.newBuilder()
.addQueryParameter("origin", "app")
.build())
.build()
}
"POST" -> {
val body = request.body()
request.newBuilder()
.post(RequestBody.create(body?.contentType(),
body.bodyToString() + "&origin=app"))
.build()
}
else -> request
})
}
fun RequestBody?.bodyToString(): String {
if (this == null) return ""
val buffer = okio.Buffer()
writeTo(buffer)
return buffer.readUtf8()
}
Since this cannot be written in the comments of the previous answer by #Fabian, I am posting this one as separate answer. This answer deals with both "application/json" as well as form data.
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
/**
* Created by debanjan on 16/4/17.
*/
public class TokenInterceptor implements Interceptor {
private Context context; //This is here because I needed it for some other cause
//private static final String TOKEN_IDENTIFIER = "token_id";
public TokenInterceptor(Context context) {
this.context = context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody requestBody = request.body();
String token = "toku";//whatever or however you get it.
String subtype = requestBody.contentType().subtype();
if(subtype.contains("json")){
requestBody = processApplicationJsonRequestBody(requestBody, token);
}
else if(subtype.contains("form")){
requestBody = processFormDataRequestBody(requestBody, token);
}
if(requestBody != null) {
Request.Builder requestBuilder = request.newBuilder();
request = requestBuilder
.post(requestBody)
.build();
}
return chain.proceed(request);
}
private String bodyToString(final RequestBody request){
try {
final RequestBody copy = request;
final Buffer buffer = new Buffer();
if(copy != null)
copy.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
catch (final IOException e) {
return "did not work";
}
}
private RequestBody processApplicationJsonRequestBody(RequestBody requestBody,String token){
String customReq = bodyToString(requestBody);
try {
JSONObject obj = new JSONObject(customReq);
obj.put("token", token);
return RequestBody.create(requestBody.contentType(), obj.toString());
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
private RequestBody processFormDataRequestBody(RequestBody requestBody, String token){
RequestBody formBody = new FormBody.Builder()
.add("token", token)
.build();
String postBodyString = bodyToString(requestBody);
postBodyString += ((postBodyString.length() > 0) ? "&" : "") + bodyToString(formBody);
return RequestBody.create(requestBody.contentType(), postBodyString);
}
}
You can edit the request body by below method, Pass the request and the parameter to edit.
private fun editBody(request: Request, parameter: String): RequestBody {
val oldBody = request.body //retrieve the current request body
val buffer = Buffer()
oldBody?.writeTo(buffer)
val strOldBody = buffer.readUtf8() // String representation of the current request body
buffer.clear()
buffer.close()
val strNewBody = JSONObject(strOldBody).put("parameter", parameter).toString()
return strNewBody.toRequestBody(request.body?.contentType()) // New request body with the encrypted/modified string of the current request body
}
Now you can request again with updated request body
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
return chain.proceed(requestWithUpdatedParameter(request, "parameter"))
}
private fun requestWithUpdatedParameter(req: Request, parameter: String): Request {
val newRequest: Request
val body = editBody(req, parameter)
newRequest = req.newBuilder().method(req.method, body).build()
return newRequest
}
private static class NetworkInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RequestBody oldBody = request.body(); //retrieve the current request body
Buffer buffer = new Buffer();
oldBody.writeTo(buffer);
String strOldBody = buffer.readUtf8(); // String representation of the current request body
buffer.clear();
buffer.close();
MediaType mediaType = MediaType.parse("application/json; charset=UTF-8");
String strNewBody = enDecService.encryptBody(strOldBody); // Your encryption/ modification logic
RequestBody body = RequestBody.create(mediaType, strNewBody); // New request body with the encrypted/modified string of the current request body
request = request.newBuilder()
.header("Content-Type", "application/json")
.header("Content-Length", String.valueOf(body.contentLength()))
.header("Authorization", "Bearer " + "your token")
.method(request.method(), body).build();
long t1 = System.nanoTime();
Log.d(TAG, String.format("Sending request %s on %s", request.url(), request.headers()));
Response response = chain.proceed(request); // sending req. to server. current req. body is a encrypted string.
int maxAge = 6000; // read from cache for 6000 seconds even if there is internet connection
response.header("Cache-Control", "public, max-age=" + maxAge);
response = response.newBuilder().removeHeader("Pragma").build();
long t2 = System.nanoTime();
Log.d(TAG, String.format("Received response for %s in %.1fms %s", response.request().url(), (t2 - t1) / 1e6d, response.toString()));
try {
String s = response.body().string(); // retrieve string representation of encrypted response assuming your response is encrypted.
ResponseBody responseBody = ResponseBody.create(mediaType, enDecService.decryptBody(s)); // decrypt the encrypted response or make other modifications.yor decryption/modifications logic goes here.
response = response.newBuilder().body(responseBody).build(); // build a new response with the decrypted response body.
} catch (JOSEException e) {
} catch (ParseException e) {
}
return response;
}
}
I'm using this way to verify my token
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
.writeTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
.readTimeout(30, TimeUnit.SECONDS) //retrofit default 10 seconds
.addInterceptor(logging.setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(new BasicAuthInterceptor())
.build();
Here i'm sending token through BasicAuthInterceptor
public class MyServiceInterceptor implements Interceptor {
private String HEADER_NAME="Authorization";
private String OBJECT_NAME="Bearer";
private String SPACE=" ";
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
String token= PreferenceManager.getInstance().getString(PreferenceManager.TOKEN);
if (token != null) { {
requestBuilder.addHeader(HEADER_NAME, OBJECT_NAME+SPACE+ token);
}
}
return chain.proceed(requestBuilder.build());
}
}

Retrofit set tag on request that gets returned with response

Basically, my question is simple. I'm using retrofit as a framework for communicating with a server which I don't control. I want to set some sort of tag on my request which gets returned in the response automatically. Any idea on how this could be accomplished?
I found a complex and not cool way to do this.
0. add a tag field in your request and response type
1. Custom okhttp3.RequestBody to add a tag field:
2. Custom okhttp3.ResponseBody to add a tag field:
3. Custom Converter.Factory to set and get tag:
for example I did some change to the GsonResponseBodyConverter and GsonRequestBodyConverter:
TagGsonRequestBodyConverter.java:
#Override public RequestBody convert(T value) throws IOException {
Buffer buffer = new Buffer();
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
JsonWriter jsonWriter = gson.newJsonWriter(writer);
adapter.write(jsonWriter, value);
jsonWriter.close();
TagRequestBody requestBody = TagRequestBody.create(MEDIA_TYPE, buffer.readByteString());
requestBody = value.tag;//just for example ,you will need to check type here
return requestBody;
}
TagGsonResponseBodyConverter.java:
#Override public T convert(ResponseBody source) throws IOException {
try {
//the ugly part,for that retrofit will wrap the responseBody with ExceptionCatchingRequestBody.(ExceptionCatchingRequestBody extends ResponseBody)
ResponseBody value = source;
if (value.getClass().getSimpleName().equals("ExceptionCatchingRequestBody")){
ResponseBody temp = null;
try {
Field f = source.getClass().getDeclaredField("delegate");
f.setAccessible(true);
temp = (T) f.get(value);
} catch (Exception e) {
e.printStackTrace();
}
value = temp != null?temp:value;
}
T t = adapter.fromJson(source.charStream());
if (value instanceof TagResponseBody) {
t.tag = ((TagResponseBody)value).tag;
}
return t;
} finally {
value.close();
}
}
4. add Interceptor when you create retrofit's okhttpClient,pass tag from TagRequestBody to TagResponseBody:
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (request.body() instanceof TagRequestBody) {
TagResponseBody responseBody = new TagResponseBody(response.body());
responseBody.arg = ((TagRequestBody) request.body()).arg;
response = response.newBuilder()
.body(responseBody)
.build();
}
return response;
}
})
So the tag was hold by :
RequestDataWithTag --CustGsonRequestBodyConverter--> RequestBodyWithTag --Interceptor--> ResponseBodyWithTag --CustGsonResponseBodyConverter--> ResponseDataWithTag

Categories

Resources