android - OkHttp interceptor - response already "consumed" - android

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

Related

How to retrieve JSON payload inside Http interceptor?

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

how to contain header when I redirect in retrofit

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)

How to refresh Cookies using okhttp3?

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

How to get headers from all responses using retrofit

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.

Debuggin Retrofit 2 - Expected BEGIN_OBJECT but was STRING at line 1 column 1 path

I'm getting this error:
Expected BEGIN_OBJECT but was STRING at line 1 column 1 path
In another stackoverflow question, they say I am receiving an array when it should be an object.
But my intuition is that I am receiving nothing, but I'm not able to see my json to debug.
I'm trying to log my received json to see what's happening, because I don't see the error in my code.
I coded is mentioned here:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Buffer buffer = new Buffer();
request.body().writeTo(buffer);
String body = buffer.readUtf8();
Request.Builder builder = chain.request().newBuilder();
String credential = Credentials.basic(user, pass);
builder.addHeader("Authorization", credential).build();
Response response = chain.proceed(builder.build());
return response;
}
});
return client;
But I get a NullPointerException in request.body()...
What's my next step for debugging???
I would setup the retrofit interceptor with a log statement like the following:
Request request = chain.request();
Log.e(String.format("Request: %s", request.body().toString()));
That should give you a error log with the json body.
You are modifying your request, but it sounds like you are trying to log the server response. To do that, you need to call chain.proceed() to get the response, and then log the body of that.
client.interceptors().add(new Interceptor() {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
com.squareup.okhttp.Response response = chain.proceed(chain.request());
String content = response.body().string();
Log.d("interceptor", "intercepted res = " + content);
return response.newBuilder()
.body(ResponseBody.create(response.body().contentType(), content))
.build();
}
});

Categories

Resources