Retrofit2: Modifying request body in OkHttp Interceptor - android

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());
}
}

Related

How to pass Method type like POST, GET in retrofit dynamically in Android

#GET
fun getAccountInfo(
#Url url: String, #HeaderMap headers: Map<String, String>?
): Call<AccountInfoModel.Response>
How to pass this get method dynamically instead of declaring static
You cannot use dynamic http methods in retrofit.
You can use okhttp for this to achieve please find my answer below
package in.silentsudo.test;
import com.google.gson.JsonObject;
import okhttp3.*;
import java.io.IOException;
public class OkHttpMain {
public static void main(String[] args) throws IOException {
OkHttpClient client = new OkHttpClient();
final String host = "https://reqres.in/";
System.out.println(get(host + "api/users/1", client));
JsonObject postBody = new JsonObject();
postBody.addProperty("name", "morpheus");
postBody.addProperty("job", "leader");
System.out.println(post(host + "api/users", postBody.toString(), client));
}
static String get(String url, OkHttpClient client) throws IOException {
Request request = new Request.Builder()
.get()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
static String post(String url, String json, OkHttpClient client) throws IOException {
final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
OkHttp Reference: https://github.com/square/okhttp
You can add custom HTTP request method type (like GET, POST, etc). Please find below answer to understand it.
Kotlin
interface RetrofitService {
#HTTP(method = "CUSTOM_TYPE", path = "custom/urlwithendpoint/")
fun methodName() : Call<RequestBody>
// if you want to call with a request body
#HTTP(method = "CUSTOM_TYPE", path = "custom/urlwithendpoint/", hasBody = true)
fun methodName(#Body params: RequestBody) : Call<RequestBody>
}
JAVA
interface RetrofitService {
#HTTP(method = "CUSTOM_TYPE", path = "custom/urlwithendpoint/")
Call<RequestBody> methodName();
// if you want to call with a request body
#HTTP(method = "CUSTOM_TYPE", path = "custom/urlwithendpoint/", hasBody = true)
Call<RequestBody> methodName(#Body RequestBody params);
}

Android Retrofit and JWT authentication

I am using JWT authentication and storing the auth token in the shared preference. I am not able to find a way to add authorization header to the retrofit client . That's why I am getting 401 errors for my network call the first time , from the second time it works. How to solve it ?
#Module
public class AppRetrofitModule {
private static final String TAG = "AppRetrofitModule";
private static Retrofit.Builder builder
= new Retrofit.Builder()
.baseUrl(Config.REST_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = builder.addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();
private static OkHttpClient.Builder httpClient
= new OkHttpClient.Builder();
private static HttpLoggingInterceptor logging
= new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BASIC);
#Singleton
#Provides
public Retrofit provideRetrofit(AppPreferencesHelper appPreferencesHelper) {
String authToken = "Bearer " + appPreferencesHelper.getAccessToken();
Log.d(TAG, "provideRetrofit: " + authToken);
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Response response = chain.proceed(original);
Request request = original.newBuilder()
.header("Authorization", authToken)
.method(original.method(), original.body()).build();
return chain.proceed(request);
}
});
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
httpClient.connectTimeout(60, TimeUnit.SECONDS);
httpClient.callTimeout(60, TimeUnit.SECONDS);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit;
}
}
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Response response = chain.proceed(original);
String authToken = "Bearer " + appPreferencesHelper.getAccessToken();
Request request = original.newBuilder()
.header("Authorization", authToken)
.method(original.method(), original.body()).build();
return chain.proceed(request);
}
});
if (!httpClient.interceptors().contains(logging)) {
httpClient.addInterceptor(logging);
httpClient.connectTimeout(60, TimeUnit.SECONDS);
httpClient.callTimeout(60, TimeUnit.SECONDS);
builder.client(httpClient.build());
retrofit = builder.build();
}
return retrofit;
Previously I was making the mistake of getting the authToken outside the interceptor. But it needs to be fetched inside the interceptor such that we can get the token at the first time too. It was working before for the second API call because the authToken value gets refreshed.

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();

How do I post data using okhttp library with content type x-www-form-urlencoded?

I have used this method https://stackoverflow.com/a/31744565/5829906 but doesnt post data.
Here is my code
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addFormDataPart("rating", "5").addFormDataPart("comment", "Awesome")
.build();
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
try {
Response response = client.newCall(request).execute();
String responseString = response.body().string();
response.body().close();
}catch (Exception e) {
e.printStackTrace();
}
I tried DefaultHttpClient , that seems to be working, but it shows deprecated, so thought of trying something different..Cant figure out what is wrong in this
You select MediaType MultipartBuilder.FORM
which is for uploading the file/image as multipart
public static final MediaType FORM = MediaType.parse("multipart/form-data");
try to send like this as
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
RequestBody formBody = new FormBody.Builder().add("search", "Jurassic Park").build();
Request request = new Request.Builder().url("https://en.wikipedia.org/w/index.php").post(formBody).build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
For those that may still come here, using with Retrofi2 and passing your data correctly to the request body. Even if you set "application/x-www-form-urlencoded" and you did not pass your data properly, you will still have issue. That was my situstion
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.addHeader("ContentType", "application/x-www-form-urlencoded");
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
OkHttpClient client = httpClient.build();
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
Api api = retrofit.create(Api.class);
Then make sure you pass your data to your api endpoint as shown below. NOT as JSON, or class object or string but as request body.
RequestBody formBody = new FormBody.Builder()
.addEncoded("grant_type", "password")
.addEncoded("username", username)
.addEncoded("password", password)
.build();
call your api service
Call<Response> call = api.login(formBody);
I hope this helps somebody

Retrofit 2 appending post to requestbody in intercept

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

Categories

Resources