Retrofit 2 & OKHttp 3 caching response not working in my case - retrofit

From the answer given for this question Can Retrofit with OKHttp use cache data when offline i was able to come up with this, but the code seems not to cache. What could i be doing wrong?
This my okhttp client
long SIZE_OF_CACHE = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(getDirectory(), SIZE_OF_CACHE);
if (cache == null) {
Toast.makeText(AppController.getInstance().getApplicationContext(), "could n0t set cache", Toast.LENGTH_SHORT).show();
}
client = new OkHttpClient
.Builder()
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.cache(cache)
.build();
Add my network interceptor is as below:
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (isConnected()) {
int maxAge = 60; // read from cache for 1 minute
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24; // tolerate 1-day stale
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
}
};
Am adding to retrofit like this:
public static Retrofit getClient() {
createCacheForOkHTTP();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
//Add in my activity:
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<MovieResponse> call = apiService.getPopularMoviesDetails(ApiKey, page);
call.enqueue(new Callback<MovieResponse>() {
#Override
public void onResponse(Call<MovieResponse> call, Response<MovieResponse> response) {
progressBar.setVisibility(View.GONE);
mErrorView.setVisibility(View.GONE);
if (response.isSuccessful()) {
movies = response.body().getResults();
movieAdapter.setMovieList(movies);
mRecyclerView.setAdapter(movieAdapter);
} else {
Toast.makeText(getActivity(), "header" + response.headers() + "code" + response.code() + "errorbody" + response.errorBody() + "errorbody" + response.message(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<MovieResponse> call, Throwable t) {
progressBar.setVisibility(View.GONE);
// Log error here since request failed
Log.e(TAG, t.toString());
mErrorView.setVisibility(View.VISIBLE);
}
});
//interface
#GET("movie/popular")
Call<MovieResponse> getPopularMoviesDetails(#Query("api_key") String apiKey, #Query("page") int page);

You shouldn’t rewrite responses from the server to facilitate caching. It’s better to ask your server’s administrators to include cache headers when they serve responses. That way the cache works for all clients – not just OkHttp.
That said, you’re an adult and you’re entitled to take technical shortcuts to avoid talking to your server team.
First you need to change your code to use both a network interceptor and an application interceptor. Why two? Well, when you’re making requests into the cache the network interceptors haven’t run yet. And when you’re writing responses into the cache the application interceptors haven’t run yet.
Your application interceptor will rewrite your request headers to include this when you prefer the cache, and to permit the cache to serve stale responses:
Cache-Control: only-if-cached, max-stale=86400
Your network interceptor will rewrite your server’s response headers to include this so that all responses are cached for 24 hours:
Cache-Control: max-age=86400

Not sure if you already fix this. I found a great/short solution hope it helps.
I create a CacheControlInterceptor. This has 24 hours of cache, if there you are offline and your response is less than 24 hours old you will get the cache response.
public class CacheControlInterceptor implements Interceptor {
private static final String CACHE_CONTROL_HEADER = "Cache-Control";
private static final String MAX_AGE_HEADER_VALUE = "public, max-age=";
private static final String MAX_STALE_HEADER_VALUE = "public, only-if-cached, max-stale=";
// Specifies the maximum amount of time a resource will be considered fresh.
private static final int MAX_AGE = 60;
// Indicates that the client is willing to accept a response that has exceeded its expiration time.
private static final int MAX_STALE = 60 * 60 * 24; // 24 hours cache.
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (WatchItApplication.hasNetwork()) {
request = request.newBuilder()
.header(CACHE_CONTROL_HEADER, MAX_AGE_HEADER_VALUE + MAX_AGE).build();
} else {
request = request.newBuilder()
.header(CACHE_CONTROL_HEADER, MAX_STALE_HEADER_VALUE + MAX_STALE).build();
}
return chain.proceed(request);
}
}
On this method I create the OKHttpCliente . As you notice I am adding the CacheControlInterceptor and cache.
private OkHttpClient createDefaultOkHttpClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
return new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.connectTimeout(TIMEOUT_MILLIS, TIMEOUT_UNIT)
.readTimeout(TIMEOUT_MILLIS, TIMEOUT_UNIT)
.writeTimeout(TIMEOUT_MILLIS, TIMEOUT_UNIT)
.addNetworkInterceptor(new HeaderInterceptor())
.cache(new Cache(WatchItApplication.getInstance().getCacheDir(), 10 * 1024 * 1024))
.addInterceptor(new CacheControlInterceptor())
.addInterceptor(interceptor)
.build();
}
Finally retrofit:
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(createDefaultOkHttpClient())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
Let me know if that works.

I was able to resolve the issue.This is how i did it.
private static OkHttpClient getCacheClient(final Context context) {
Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response originalResponse = chain.proceed(chain.request());
if (isConnected()) {
// Internet available; read from cache for 0 day
// Why? Reduce server load, better UX
int maxAge = 0;
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
// No internet; tolerate cached data for 1 week
int maxStale = 60 * 60 * 24 * 7;
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
}
};
File httpCacheDirectory = new File(context.getCacheDir(), "cachedir");
int size = 5 * 1024 * 1024;
Cache cache = new Cache(httpCacheDirectory, size);
return new OkHttpClient.Builder()
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
.cache(cache)
.build();
}
And usage:
public static Retrofit getClient(final Context context) {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.client(getCacheClient(context))
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}

Related

Problem reading FullHttpRequest request content with Proxy

I'm trying to develop code that can filter requests, modify them, etc. The Proxy intercepts requests correctly and is able to display the correct Uri for example. However, I cannot read the contents of the http package. In fact, the request.content (). ReadableBytes () function returns 0.
below the code:
HttpProxyServer server =
DefaultHttpProxyServer.bootstrap()
.withAddress(new InetSocketAddress("localhost", 8080))
.withTransparent(true)
.withFiltersSource(new HttpFiltersSourceAdapter() {
#Override
public int getMaximumRequestBufferSizeInBytes() {
return 512 * 1024;
}
#Override
public int getMaximumResponseBufferSizeInBytes() {
return 50 * 1024 * 1024;
}
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx) {
return new HttpFiltersAdapter(originalRequest) {
#Override
public HttpResponse clientToProxyRequest(HttpObject httpObject) {
FullHttpRequest request = (FullHttpRequest) httpObject;
CompositeByteBuf contentBuf = (CompositeByteBuf) request.content();
System.out.println(request.getUri());
System.out.println(request.content().readableBytes());
So the question is: why i can't read the body of the request?

How to read HTTP 500 using a Spring RestTemplate client

A simple Spring Boot REST Controller
#PostMapping(path = "check-and-submit", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<MyOutput> checkAndSave(#RequestBody #Valid MyInput input, Errors errors){
ResponseEntity<MyOutput> result = null;
if (errors.hasErrors()) {
result = new ResponseEntity<>(MyOutput.buildErrorResponse(errors), HttpStatus.INTERNAL_SERVER_ERROR);
} else {
myDao.save(input.buildEntity());
result = new ResponseEntity<>(MyOutput.buildSuccessResponse(), HttpStatus.OK);
}
return result;
}
And the test class for it
public static void main(String[] args) {
MyInput dto = new MyInput();
// set properties
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Content-Type", "application/json");
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
HttpEntity<MyInput> request = new HttpEntity<MyInput>(dto, headers);
try {
ResponseEntity<MyOutput> result = restTemplate.postForEntity(URL, request, MyOutput.class);
System.out.println(result);
} catch(Exception e) {
e.printStackTrace();
}
}
For success scenario this works fine. But, for exception scenrio, i.e. HTTP 500 this fails
org.springframework.web.client.HttpServerErrorException: 500 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:97)
As suggested in one of the posts, I created a error-handler that can successfully read the response
public class TestHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
Scanner scanner = new Scanner(response.getBody());
String data = "";
while (scanner.hasNext())
data += scanner.next();
System.out.println(data);
scanner.close();
}
}
But how can I let RestTemplate read and deserialize the response JSON even in case of HTTP 500.
Before any other human-question-flagging-bot marks this as duplicate, here's a humble explanation on how this is different from the others.
All other questions address how to handle HTTP 500, at max read the response-body. This questions is directed at if it is possible to deserialize the response as JSON as well. Such functionality is well established in frameworks such as JBoss RESTEasy. Checking how same can be achieved in Spring.
This should work.
try {
ResponseEntity<MyOutput> result = restTemplate.postForEntity(URL, request, MyOutput.class);
} catch(HttpServerErrorException errorException) {
String responseBody = errorException.getResponseBodyAsString();
// You can use this string to create MyOutput pojo using ObjectMapper.
}

Retrofit timeout not changing despite being set to the OkHttp client

I have a singleton class for network connections. I want the timeout increased and have written the following code.
BizAnalystApiv2 provideBizAnalystApiV2(final ObjectMapper mapper) {
final HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
} else {
logging.setLevel(HttpLoggingInterceptor.Level.NONE);
}
final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("instanceId", FirebaseInstanceId.getInstance().getId())
.build();
return chain.proceed(request);
}
})
.readTimeout(70, TimeUnit.SECONDS)
.build();
return new Retrofit.Builder()
.addConverterFactory(JacksonConverterFactory.create(mapper))
.client(client)
.baseUrl(BizAnalystServicev2.getServerUrl())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build()
.create(BizAnalystApiv2.class);
}
The timeout remains at 15 seconds which is the default timeout. Can you suggest what is the issue.
I am using okhttp(v 3.4.1) and retrofit (v 2.1.0)

java.lang.NoClassDefFoundError: Failed resolution of: Lokio/Buffer;

when i use retrofit .I got the exception java.lang.NoClassDefFoundError: Failed resolution of: Lokio/Buffer;i use okhttpclient in order to set header for retrofit.get userList is a post method,and i need to send body in request.
private void getUserList(int startIndex){
final JSONObject audienceObj = ProtocolHelper.getProtocolUtils(mContext).getUserlistJsonObj(mRoomData.mUid, mRoomData.mRoomId, startIndex);
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(
new Interceptor() {
#Override
public okhttp3.Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("sessionId", CommonData.getUserInfo(mContext).sessionId);
Request request = requestBuilder.build();
return chain.proceed(request);
}
})
.build();
String baseUrl = ProtocolUtils.BASE_URL+"/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(okClient)
.build();
String audienceUrl = ProtocolHelper.getProtocolUtils(mContext).getProtocolUrl(ProtocolUtils.PROTOCOL_MSG_ID_MEMBER_LIST);
AudienceInterface audienceInterface = retrofit.create(AudienceInterface.class);
Call<String> call = audienceInterface.getAudienceList(audienceUrl,audienceObj);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
Log.d(TAG, "onResponse");
}
#Override
public void onFailure(Call<String> call, Throwable t) {
Log.d(TAG, "onFailure"+t.getMessage());
}
});
}
public interface AudienceInterface {
#POST("{url}")
Call<String>getAudienceList(#Path("url") String url,#Body JSONObject boder);
}
the log t.getMessage is :java.lang.NoClassDefFoundError: Failed resolution of: Lokio/Buffer;
I solved it by adding:
implementation 'com.squareup.okio:okio:2.1.0'
in dependencies under build.gradle(Module: app).
Alright.~I found this error last time too.
By this:
NoClassDefFoundError: Failed resolution of: Lokio/Buffer
You might lost another jar lib--Okio.
You can download the jar file from github:
https://github.com/square/okio
And add this lib to your project.

How to handle timeout of AsynchronousResponse object in RestEasy

i am implementing a jax-rs service with RestEasy on JBoss AS 7.1.2 an i would like to use asynchronous HTTP processsing as described here: http://docs.jboss.org/resteasy/docs/1.0.0.GA/userguide/html/Asynchronous_HTTP_Request_Processing.html
For thr AsynchronousResponse I define a timeout of 10 seconds. When this period expires, the request is responded with a 200 OK and an empty body. I would like to modify this behaviour so i need to be notified about the timeout event.
In my solution, I would like to handle the timeout event in a NotificationManager object, which keeps the AsycnhronousResponse for the time being. Please see the code below for details.
So far, i could not figure out how to do that. Does anyone have more experience with the RestEasy Asynchronous HTTP processing?
#POST
#Path("/blabla")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void subscribeLongPolling (
final #Suspend(10000) AsynchronousResponse response,
JAXBElement<LongPollingRequestParameters> rqParam,
#Context HttpServletRequest req) throws Exception {
//do some stuff with req
Thread t = new Thread("ThreadSubscribeTo:" + channelID)
{
#Override
public void run() {
//hand over to Notification Manager to return notifications in case some exist
try {
NotificationManager nm = new NotificationManager();
nm.setAsyncResponseObject(response);
logger.info("Response object registered in NotificationManager");
}catch (Exception e){
e.printStackTrace();
}
}
};
t.start();
logger.info("Releasing Thread");
}
public class NotificationManager {
private AsynchronousResponse response;
private NotificationList nList;
public synchronized void setAsyncResponseObject(AsynchronousResponse response) {
this.response = response;
if (nList.getAny().size() > 0) {
logger.info("Stored notification send to web client: " + nList.getAny().get(0).toString());
sendNotification(nList.getAny().remove(0));
}
}
public synchronized void sendNotification(Object message){
if (response != null){
logger.info("Response object found. Send notification immediately: " + message.toString());
Response responseObject = Response.ok(message, MediaType.APPLICATION_XML).build();
response.setResponse(responseObject);
response = null;
}else{
logger.info("Response object not found notification will be stored");
addNotification(message);
}
}
}
Thanks in advance,
Alex

Resources