I'm trying to use Caching using Retrofit and OkHttp.
I'm adding this interceptor to OkHttp builder like.
#Provides
#Singleton
#TokenHttpClient
internal fun provideTokenOkHttpClient(
#BaseHttpClient okHttpClient: OkHttpClient,
cacheInterceptor: NetworkCacheInterceptor): OkHttpClient {
return okHttpClient.newBuilder()
.addInterceptor(cacheInterceptor)
.build()
And the interceptor looks like this:
class NetworkCacheInterceptor(val context: Context): Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
var response = chain.request()
response = if (hasNetwork(context))
response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control",
"public, max-age=${CacheControl.Builder().maxAge(30, TimeUnit.DAYS).build()}")
.build()
else {
response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control",
"public, only-if-cached, max-stale=${CacheControl.Builder().maxAge(30, TimeUnit.DAYS).build()}")
.cacheControl(CacheControl.FORCE_CACHE)
.build()
response
}
return chain.proceed(response)
}
}
This way of implementation it's saving responses into cache folder. But it's not working when offline. I've tired to separate interceptors and add the caching as networkInterceptor and the offline interceptor as normal interceptor but it didn't work. On this case no cache was saved.
When I check cached files I see these headers:
GET
0
HTTP/1.1 200
15
Access-Control-Expose-Headers: value
Cache-Control: no-cache
ETag: "01785cb0981d28aa96d58e491dbd6873e"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: SAMEORIGIN
Referrer-Policy: no-referrer
Content-Type: application/json;charset=UTF-8
Date: Tue, 21 May 2019 07:50:51 GMT
Transfer-Encoding: chunked
Content-Encoding: gzip
OkHttp-Sent-Millis: 1558425050896
OkHttp-Received-Millis: 1558425050928
Any idea? What I'm missing?
Related
I want to cache the response for 2 hours and hence, tried adding cache to my okhttp client, every time hitting API with the network it works fine but next time, enabling airplane mode, we try fetching data in Offline mode, it gives retrofit2.HttpException: HTTP 504 Unsatisfiable Request (only-if-cached)
I have tried every solution related to this exception posted here on SO. Please help me find out what am I doing wrong...been stuck at this for really long.
interface TrendingRepoService {
#GET("/repositories?language=&since=daily")
suspend fun getTrendingRepos() : List<Repo>
}
object RetrofitClient {
private fun getClient(context: Context): Retrofit {
val cacheSize = (5 * 1024 * 1024).toLong() //cache size is 5mb
val myCache = Cache(File(context.cacheDir, "responses"), cacheSize)
val okHttpClient = OkHttpClient.Builder()
.cache(myCache)
.addInterceptor { chain ->
var request = chain.request()
if (hasNetwork(context)!!) {
request.newBuilder().removeHeader("Pragma")
.addHeader("Cache-Control", "public, max-age=" + 5)
} else {
request = request.newBuilder()
.removeHeader("Pragma")
.addHeader("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 2) //cache should expire after 2 hours
.build()
}
println("network: " + chain.proceed(request).networkResponse())
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + myCache.directory().lastModified())
println("cache: " + chain.proceed(request).cacheResponse())
chain.proceed(request)
}
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://github-trending-api.now.sh")
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.build()
}
fun getTrendingService(context: Context): TrendingRepoService {
return getClient(context).create(TrendingRepoService::class.java)
}
}
I can see the following files in my app cache folder
The contents of file stored .0.tmp ending file in cache
https://github-trending-api.now.sh/repositories?language=&since=daily
GET
0
HTTP/1.1 200
14
date: Mon, 04 Nov 2019 19:27:43 GMT
content-type: application/json; charset=utf-8
x-powered-by: Express
access-control-allow-origin: *
etag: W/"4fcb-tcvKxcVttStpWpvGjIHTyk/TMbU"
cache-control: max-age=120
x-now-trace: hel1,bru1,sfo1
x-now-id: hel1:rv96m-1572895662521-476640337170
strict-transport-security: max-age=63072000
x-now-instance: 4008292682
content-encoding: gzip
server: now
OkHttp-Sent-Millis: 1572895661313
OkHttp-Received-Millis: 1572895662894
TLS_AES_256_GCM_SHA384
2
MIIFTzCCBDegAwIBAgISA4Bu+MYBP......
0
TLSv1.3
How max-age is 120?? Every time I am getting 504 error when I want to fetch data with cache.
When I hit API with no network only journal file exists in my cache folder the other two files are not there the next time.
I'm building an Android app, which uses Retrofit for the networking.
Here's the code:
private val loggingInterceptor by lazy {
HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message -> Log.d(TAG, message) }).apply {
level = if(BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
}
}
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
Retrofit.Builder()
.baseUrl(" https://mybaseurl.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
I use an HttpLoggingInterceptor, however logs are too verbose because it logs all the headers..
content-language: en-GB
content-type: application/json
date: Sun, 07 Jul 2019 12:52:36 GMT
p3p: CP="NON CUR OTPi OUR NOR UNI"
transfer-encoding: chunked
cache-control: no-cache,no-store,must-reva
x-powered-by: Servlet/3.0
expires: Thu, 01 Dec 1994 16:00:00 GMT
pragma: no-cache
and more....
If I switch to LogLevel Basic though, I don't get the request and response, but just the URLs.
Btw, looking inside HttpLoggingInterceptor code:
boolean logHeaders = logBody || level == Level.HEADERS;
So it seems that there is no actual way to log body without the headers.
Is there any hacky way or workaround?
In my android app, I tried to send a POST request to Google Forms, but an error 400 is returned:
400 https://docs.google.com/forms/d/e/[form-id-number-here]/formResponse (114002ms)
content-type: text/html; charset=utf-8
x-robots-tag: noindex, nofollow, nosnippet
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Sun, 17 Mar 2019 05:03:05 GMT
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
x-chromium-appcache-fallback-override: disallow-fallback
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
server: GSE
....
Here is my code:
interface FormApi {
#Headers("Content-Type: application/json", "Accept: text/html", "Cache-Control: no-cache")
#POST("{key}/formResponse")
fun formResponseJson(#Path("key") key: String, #Body json: JsonObject): Call<ResponseBody>
}
Usage:
val gson = GsonBuilder().setLenient().create()
val client = OkHttpClient.Builder()
.readTimeout(timeout.toLong(), TimeUnit.SECONDS)
.connectTimeout(timeout.toLong(), TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build()
val api = retrofit.create(FormApi::class.java)
val json = dataObj.toJson()
val call = api.formResponseJson(key, json)
call.enqueue(CallBackResult(resultReceiver))
Note that the json object has the key-value format like: {"entry.xxxxx1":"Test"}
Does anyone have an idea why this error code is returned? Has anyone successfully sent a POST to a Google Form in Android?
Thanks.
I have made the following modifications, and the code is now able to post to Google forms:
In the FormApi:
POST("{key}/formResponse")
#FormUrlEncoded
fun formResponse(#Path("key") key: String, #FieldMap hashmap: HashMap<String, Any>): Call<ResponseBody>
Usage:
val call = api.formResponse(key, hash)
where "hash" is a HashMap with the actual values of the fields (not URLEncoded)
I have been using retrofit and auth for a while with no problem.
But from this API call while everything is exactly the same as my other projects which works, I'm constantly getting error code 401
here is my API calling code:
#GET("workers")
fun getWorkersList(): Observable<BaseResponseModel<WorkerAttributes>>
companion object Factory {
fun create(): WebServices {
val okHttpClientBuilder = OkHttpClient.Builder()
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
okHttpClientBuilder.addInterceptor(httpLoggingInterceptor)
okHttpClientBuilder.addInterceptor { chain ->
val request = chain.request().newBuilder()
request.addHeader("Authorization", "Bearer eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAiLCJraWQiOiIzaGltWWEyYmJHS18zSVdZMUxUUHdyTUl2ZkF3ZVhZQmRNLTU5XzN6c0MwIn0.DIwk0Q215_M7rt3iGfyRVrImhKCXvEB5NBoLQj6iqBnN9yCWWBhGW9fmX6beOajtWgC8gpupe1hJ-iurFRbo2Eg52Nh9ljkkO5Mz8ulSpH7YlfP8Yi-TBrNoPIf_IvHrr1Rt8bG0w2Ie1Jo3BBOguMQ6Q2WI4fQDY-oFIluxOQ5t9E0_XvDhna7maiQnWevLbu80Mj7g643v5nvNWOdpSAXW91rZXIXdal_A_ffY-IUyvYhEJuOkyHWwNonuGjiGHWKPTjTR16w3seoSs4srkacOIN1cmMdXKlkvlWSYCv5Nd8xJ_rTqA4GIyKLRSALYcuiTFMOIxeMIpPB5DjpMTOmOD72QnD8MHMx-SNHk2Yva5iPs__h_WXIKzjME6wRQxTBayfWcXuA6loTdSXnV5Tfl63y62nYZUesh2ElcF5ZK_if3R70jTh-_dxAc8-EcxagAgeCAYNGx-i0fokA4Crfz0A9MKJv5XKn8hY-_4_AT-tsIwBQ6YWYISPBGqyUFoBX0LGDEojIywQBXcLSqHRoah45IDW87TTjVTtPjKsxrIr9rl661dYKRE4ViTLGON0hRBmmNAOokcTNv4tEPBWDS5OPIVtnqkCruAV1YXGKMWpbGOlH6pi2sLY5FtNP9h7nNKnK59gX_FMwXyLjzdPU7HkEEv2svTHN_vBFDUeE.UmwA_rn0FGSRXl4kVPrF3Q.lpZ6JQSke2wLCi3X18mNMAYs5x2nCk7LGrs65nOo-cv8FQ1zfYz9qj7TdH26BxmIrtJiBlLkLhJJnWmSjceNsQnCaOFt36cStJKTHbRkydezuBJyIgvlQH-8Y3nTXLpIbo3JQjxTJ-lUwmY8bLnyxS91DaKEjmip3DoXJLSa4uOivuC68npCCtPVFeHbdHrzS_wQTRsUEh1o0XtWI1TtW868mV3njXwtjHdKxp7Uk8wfY5i5R4qJHxXWgf8PuXJA99Hh1Jwzee_tuA67wnPxvKf95mFkZVYZdDqNKrFkDE9B7-r5FjLi3pQZdTMXjFBGAMv20jrM5BuvCZLP9p6ugbG5pOBRUjBT5RtQpw48_th6pTa4W36MkBkh5TbXS27yuOXvi-b3blcSCEiizEDcJvqm4DBwqCNlSR8iG_QgktFDzkG99--LHV4R956K6cAozVAeyv0Jo9mKfaTsRLlMNhfTFpPrZmR5hVb11CEmmLi9C84aK1eivlYOZenzdZKpYE2mBKjZXOl96PJ3jb-OwEO2MDw9fyGzez8NaVhWAGGw9rm1iXnQyGljOw_ufOr5GLPHXXMhjSGVpXbN65sCTv8p6XGZnJiW7otVUzzmxG_kYQ3tkFLTyG9p6SG3GGBDVYG_pkDpPWavWlQdplIDTsOrjoXu-8sAYTx6GO_rGL13haW7KY9pRi9v3324zkNYOYL8CWWZzjcm26607lTEhpVWvkP4QwisGjvrAMoyj-GnlKvyK1u-1sPcuRMUg-8AKRYzBdtlChoUOtdVwYsOHxiLAkRMBH4PRIqWyUNILmlugXGNA7umZxj8uWy2mKjpZ7M8D_YlR66tj4duARp0lJYfeuvDqbXABVuSoLs8qaEFuYXLfiTNH4zItNeMQjfzT_eh9S_ligNALdOb82FgZzYv5ZKbFMo2s9kwzAb4PdwyaZjIP4_UJiJBqOazslWJYpdMLY2ZHGwfg0ZFP3adV06hbR1i9bJ7lyew2Dalqh9Sq-cFPg32-6rbZXYJnFFzfMks4FOJCgPOhCz2QODdbZU8_nQp_EQ4mIXnD2BR6BcCa3odD8rliM3IYOWnq3AhCHen55FwygCw6-u61Q03m4httHFozE-x4YiQivTQ70YUmdYNMyU_chQ3WfiJzaOgEwjq8vIP0hasxiaE1fBG7PncjGCUNB9sFiyVYjmSbP68iIQFqQ10oo9hnMYVxs_nSdem23fDNOutoikzjSHPo4_qxg-hNV0GDIrzBPLGsGw_W9agnUQOpwtpYycZ9v472FWhI5z0c8-8U7D9cV9s0ELyO9U7_vHhSoZJCq1edIFVVKTHsVfR2-vOlCHuqlbH4TzqGtIzf-5nUuY7HQ4iIxdjOrfU2GtMaU_tB8v3ZsC2Nr7IMYeyeBhItOXccNnqHUhMyZ-LdHkoafendmWTnnMOlkK2l91Jil4tPrJyVn1YFlsbzobQOurrsz2MAW3Ew1ibTCsRvlKWRs8dOSAxoUBcQL-r9g5_BcF0wsV7xzWvIHFm7fOuqLGsjsrCMAuXKImA2tFYECpr6vqCq8ORdqapUOlMb0_K4Iae-AhEkbMVERgLS3O05tRhQATMl2jnqA71DImzDoQMC_c45ELXToqX7x0oiINsOLQKBFQjiQ4-G9C1DJlZ67ZVtSN2PyKVNqMNegEtSz1uieOmkoXa1Vq62CrDp7KORToTHMZEV9uWe7KAOzqYm7jwhEvFs2jgvSga9Au2YgzqxbBjpC8RDSQqhUZTQeg2gecFLRpIObQ._YXzEw0gT4HbLqcFfdWoFQ")
request.addHeader("Content-Type", "application/json")
request.addHeader("X-Requested-With", "XMLHttpRequest")
request.addHeader("Cookie", "XMLHttpRequest")
chain.proceed(request.build())
}
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClientBuilder.build())
.baseUrl(Constants.BASE_URL)
.build()
return retrofit.create(
WebServices::
class.java
)
}
}
and here is my error:
D/OkHttp: --> GET http://pilot.wakecap.com/api/sites/10010001/workers
D/OkHttp: --> END GET
I/art: Do partial code cache collection, code=60KB, data=57KB
I/art: After code cache collection, code=58KB, data=56KB
I/art: Increasing code cache capacity to 256KB
D/OkHttp: <-- 401 Unauthorized https://pilot.wakecap.com/api/sites/10010001/workers (2645ms)
D/OkHttp: Server: nginx/1.14.0 (Ubuntu)
D/OkHttp: Date: Wed, 16 Jan 2019 20:16:44 GMT
D/OkHttp: Content-Type: application/json; charset=utf-8
D/OkHttp: Content-Length: 83
D/OkHttp: Connection: keep-alive
D/OkHttp: X-DNS-Prefetch-Control: off
D/OkHttp: X-Frame-Options: SAMEORIGIN
D/OkHttp: Strict-Transport-Security: max-age=15552000; includeSubDomains
D/OkHttp: X-Download-Options: noopen
D/OkHttp: X-Content-Type-Options: nosniff
D/OkHttp: X-XSS-Protection: 1; mode=block
D/OkHttp: Access-Control-Allow-Origin: *
D/OkHttp: ETag: W/"53-ocmggiYI6s3nDDtb9FHiqlrJJYU"
D/OkHttp: {"code":"unauthenticated","message":"Server Error. Try again later!!","status":401}
D/OkHttp: <-- END HTTP (83-byte body)
PS: I have also used Cookies but it didn't help
PS2: it says try again later, but it works perfectly fine with Postman
PS3: For some reasons, Volley works just fine :|
So I've found out what was the problem.
The problem was I had to use "https" and I was using http, apparently Volley and Postman both automatically add the s in the end but Retrofit didn't for some reasons.
I use retrofit and picasso library. Picasso manages caching itself. But when I view logcat, I see log below. What does it mean? Doesn't the webservice and backend send cache info correctly? How can I solve this to make Retrofit cache correctly. So, Does this data make sense?
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Type: application/json
Date: Wed, 14 Dec 2016 07:15:48 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
OkHttp-Received-Millis: 1481597410805
OkHttp-Response-Source: NETWORK 200
OkHttp-Selected-Protocol: http/1.1
OkHttp-Sent-Millis: 1481597409021
Pragma: no-cache
Server: Apache
Transfer-Encoding: chunked
You can set cache option to OkHttp with using Interceptor.
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new CachingControlInterceptor());
Retrofit restAdapter = new Retrofit.Builder()
.client(client)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
and CachingControlInterceptor is:
public class CachingControlInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Add Cache Control only for GET methods
if (request.method().equals("GET")) {
if (ConnectivityUtil.checkConnectivity(YaootaApplication.getContext())) {
// 1 day
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
} else {
// 4 weeks stale
request = request.newBuilder()
.header("Cache-Control", "public, max-stale=2419200")
.build();
}
}
Response originalResponse = chain.proceed(request);
return originalResponse.newBuilder()
.header("Cache-Control", "max-age=600")
.build();
}
}
See this:
https://stackoverflow.com/a/34401686/850347