I am trying to intercept a particular request inside my interceptor. I want to catch 401 http status, which is easy to do with
response.code()
However I also need to check a particular field in the payload and I cannot find how to retrieve this payload.
Here is my interceptor:
Interceptor authorizationInterceptor = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
if (response.code() == 401) {
// I need to access the payload here
return response;
}
return response;
}
};
The debugger reveals that the payload is available inside the response.
Is it possible to retrieve it ?
As suggested by Selvin, the body can be retrieved (as a String) using
response.body().string()
Related
I need to build a traffic monitor on my Android app, and I need to have stored the size of all json that I'm sending and receiving through retrofit. Using log I can see the actual size of it, but I haven't find a way to get this information so I could save it. I can't get the response.raw either since it's already been parsed to my classes. Is there any way to achieve that?
EDIT: Marked vadkou answer as the best one.
Instead of creating a new interceptor, I passed the lamda expression:
httpClient.addInterceptor( chain -> {
okhttp3.Request request = chain.request();
okhttp3.Response response = chain.proceed(request);
if(request.body()!=null) {
long requestLength = request.body().contentLength();
Log.e("SERVICE GENERATOR", " CONTENT LENGTH" + requestLength);
}
long responseLength = response.body().contentLength();
Log.e("SERVICE GENERATOR", " RESPONSE LENGTH" + responseLength);
return response;
});
Retrofit 2 uses OkHttp internally, and you could configure OkHttp without having to resort to getting raw HTTP response as in Vaiden's answer by adding a custom Interceptor while building an adapter as follows:
private Retrofit createRetrofit() {
return new Retrofit.Builder()
.baseUrl(END_POINT)
// .addConverterFactory(...)
// .addCallAdapterFactory(...)
.client(createClient())
.build();
}
private OkHttpClient createClient() {
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.addInterceptor(createYourInterceptor());
return okHttpClientBuilder.build();
}
The Interceptor interface among other things allows you to access request body for every request you make.
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// do what you want with request.body().contentLength();
return chain.proceed(request);
}
For this you need to create custom interecptor
please reffere below example
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class CustomIntercepter implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();///
Response response = chain.proceed(request);
// for request size
long requestLength = request.body().contentLength();
// for response size
long responseLength = response.body().contentLength();
return response;
}
}
`
Now Create Retrofit object
OkHttpClient provideOkHttpClient(CustomIntercepter customIntercepter) {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
okHttpClient.cache(cache);
okHttpClient.addInterceptor(customIntercepter);
return okHttpClient.build();
}
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(SERVER_URL)
.client(okHttpClient)
.build();
return retrofit;
}
You should try accessing the raw HTTP response (Get raw HTTP response with Retrofit):
You begin with a Response object.
This object has a .raw() method that returns the actual HTTP layer's reponse,
in the form of an okhttp3.Response object. Calling .body() would give you a ResponseBody object, which encapsulates the raw response.
You can get the length of the response by calling .contentLength().
I tried to make oauth2 for android application. it has little bug.
My bug is It doesn't have header like Authorization when I redirect
MyCookieCode. It send Authorization when I was login. but It doesn't work when I redirect
public static Retrofit getLoginRetrofitOnAuthz() {
Retrofit.Builder builder = new Retrofit.Builder().baseUrl(ServerValue.AuthServerUrl).addConverterFactory(GsonConverterFactory.create());
if (LoginRetrofitAuthz == null) {
httpClientAuthz.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
String str = etUsername.getText().toString() + ":" + etPassword.getText().toString();
String Base64Str = "Basic " + Base64.encodeToString(str.getBytes(), Base64.NO_WRAP);
System.out.println(Base64Str);
Request request = chain.request().newBuilder().addHeader("Authorization", Base64Str).build();
return chain.proceed(request);
}
});
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
httpClientAuthz.cookieJar(new JavaNetCookieJar(cookieManager));
LoginRetrofitAuthz = builder.client(httpClientAuthz.build()).build();
}
return LoginRetrofitAuthz;
}
Server Result (Top-Login, Bottom, Redirect)
Do you know how to staying header on redirect ?
in fact the sinner is OkHttp, but not Retrofit.
OkHttp removes all authentication headers on purpose:
https://github.com/square/okhttp/blob/7cf6363662c7793c7694c8da0641be0508e04241/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(url)) {
requestBuilder.removeHeader("Authorization");
}
Here is the discussion of this issue: https://github.com/square/retrofit/issues/977
You could use the OkHttp authenticator. It will get called if there is a 401 error returned. So you could use it to re-authenticate the request.
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
return response.request().newBuilder()
.header("Authorization", "Token " + DataManager.getInstance().getPreferencesManager().getAuthToken())
.build();
}
});
However in my case server returned 403 Forbidden instead of 401. And I had to get
response.headers().get("Location");
in-place and create and fire another network request:
public Call<Response> getMoreBills(#Header("Authorization") String authorization, #Url String nextPage)
The REST API I connect to uses cookie-based authorization. Basically, issuing a call, I get a cookie, which then I use in all my requests. That cookie has an expiry time on the Server side. Once cookie expired, I get notified, I have to acquire a new cookie to proceed.
To achieve this, I am using OkHttp3 client, to which I attached a PersistentCookieJar. Somewhere in between, I also have an Interceptor that catches the session expired responses, and tries to renew the cookie. After my Interceptor does the call to renew the cookie, it re-tries the previously failed call. Which, fails again. I suppose it is because the retry of the call is done with old cookie.
Any suggestions?
Some code snippets below:
//OkHttp client with the interceptor and the cookiejar
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(sessionExpiryInterceptor);
if (cache != null) {
builder.cache(cache);
}
return builder
.cookieJar(cookieJar)
.connectTimeout(1, TimeUnit.MINUTES)
.build();
The PersistentCookieJar is used as described here: https://github.com/franmontiel/PersistentCookieJar
And last, the Interceptor:
public class SessionExpiryInterceptor implements Interceptor {
#Override public Response intercept(Chain chain) throws IOException {
final Request request = chain.request();
final Response response = chain.proceed(request);
//analyze to see if it is "session expired"
String bodyString = response.body().string();
Response copyResponse = copyResponse(response, bodyString);
//attempt to parse the original body
if (isRefreshRequired(bodyString)) {
//we send the response copy from here on out, as the original no longer as a "body" - extracted and parsed earlier
return refreshData(request, copyResponse, chain);
} else {
//return the copy since body was extracted earlier
return copyResponse;
}
}
private boolean isRefreshRequired(String responseBody) throws IOException {
//does some parsing of the responseBody to extract a "status" and "message"
return status == RESPONSE_CODE_NO_SESSION || message.contains("session");
}
private Response refreshData(Request request, Response responseCopy, Chain chain) throws IOException {
//does some checks before attempting to actually refresh the cookie,
mAuthService.login(user, pass)
.subscribeOn(Schedulers.immediate())
.observeOn(Schedulers.immediate())
.subscribe(createLoginSubscriber());
if (mDataRefreshed) {
// rebuild the request with the new acquired cookie
Request retry = createRetryRequest(request);
return chain.proceed(retry);// !!! this call fails again - I get "session expired" response message.
} else {
//for whatever reason the refresh failed - send back the original response's copy
return responseCopy;
}
}
private Subscriber<LoginResponse> createLoginSubscriber() {
return new Subscriber<LoginResponse>() {
#Override public void onCompleted() {
}
#Override public void onError(Throwable e) {
LOG.e(e, "login failed" + e.getMessage());
}
#Override public void onNext(LoginResponse response) {
if(response.isSuccess()) {
mDataRefreshed = true;
} else {
mDataRefreshed = false;
//some cleanup code here
}
}
};
}
private Request createRetryRequest(Request source) {
//I suspect this copy might be the issue?
return source.newBuilder().build();
}
//copies the given response rewriting the body with the given one with character set UTF-8
private Response copyResponse(Response original, String body) {
return original.newBuilder()
.body(new RealResponseBody(original.headers(), new Buffer().writeUtf8(body)))
.build();
}
}
I'm using Retrofit library version 2 with OkHttpClient.
I want to get some header from all responses.
I found one solution with OkClient:
public class InterceptingOkClient extends OkClient{
public InterceptingOkClient()
{
}
public InterceptingOkClient(OkHttpClient client)
{
super(client);
}
#Override
public Response execute(Request request) throws IOException
{
Response response = super.execute(request);
for (Header header : response.getHeaders())
{
// do something with header
}
return response;
}
}
But how i can do this if i'm using OkHttpClient?
Yes, this is old question.. but still found to answer because myself too was searching similar one.
okHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "auth-value"); // <-- this is the important line, to add new header - replaces value with same header name.
Request request = requestBuilder.build();
Response response = chain.proceed(request);
Headers allHeaders = response.headers();
String headerValue = allHeaders.get("headerName");
return response;
}
});
Hope, this helps!
P.S: no error handled.
You can use the logging interceptor for that. Add it as an interceptor to your OkHttpClient builder while building the client, set the log level and voila! You will have all the information regarding the request as well as the response.
Here's how you can add the interceptor -
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpBuilder.addInterceptor(loggingInterceptor);
client = okHttpBuilder.build();
There are four options when it comes to what you want to Log - NONE,BASIC,HEADERS, and BODY.
Now build the the retrofit instance with the above defined client and you will have all the data you need.
I'm trying to use this interceptor for authentication:
public class CustomInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = chain.proceed(request);
if (response shows expired token) {
// get a new token (I use a synchronous Retrofit call)
// create a new request and modify it accordingly using the new token
Request newRequest = request.newBuilder()...build();
// retry the request
return chain.proceed(newRequest);
}
// otherwise just pass the original response on
return response;
}
The problem is that my check (response shows expired token) is not status-related, I need to check the actual response (body content).
So after the check, the response is "consumed" and any attempt to ready the body will fail.
I've tried to "clone" the response buffer before read, like:
public static String responseAsString(Response response ){
Buffer clonedBuffer = response.body().source().buffer().clone();
return ByteString.of(clonedBuffer.readByteArray()).toString();
}
but it doesn't work, the clonedBuffer is empty.
Any help will be appreciated.
I just had the same problem myself, and the solution I found was to consume the response's body and build a new response with a new body. I did it like so:
...
Response response = chain.proceed(request);
MediaType contentType = response.body().contentType();
String bodyString = response.body().string();
if (tokenExpired(bodyString)) {
// your logic here...
}
ResponseBody body = ResponseBody.create(contentType, bodyString);
return response.newBuilder().body(body).build();