vadymmarkov / Malibu

:surfer: Malibu is a networking library built on promises

Home Page:https://vadymmarkov.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ETag still present even after calling Malibu.clearStorage()

guilhermearaujo opened this issue · comments

I noticed that my requests are being dispatched with the header If-None-Match: "Some ETag hash" even after calling Malibu.clearStorage()

I created a sample project demonstrating it: https://github.com/guilhermearaujo/malibu-etag
Its README contains more details.

The weird thing is that at this line:
https://github.com/hyperoslo/Malibu/blob/26657c5e46339749aa6cc178ed2a4111fe6841ce/Sources/Networking.swift#L200-L212
before the return, I added the code:

print(urlRequest.allHTTPHeaderFields!)

And the If-None-Match header is not present, but somehow it ends being sent anyway.

Looks like a bug to me.
Tested on versions 6.2.0 and 6.4.1

Hi @guilhermearaujo and thank you for the detailed description of your problem. I've tested your demo project and I see that there is an Etag header in the response but I can't find it in the request headers. Are you sure If-None-Match is present in your tests? You could also try to set Etag policy to be disabled: Request.get("/endpoint", etagPolicy: .disabled) and play with cachePolicy to see if it has any affect on your requests.

@guilhermearaujo

UPDATE: I tested more and found ETag present in the server logs. It seems like there is no need to explicitly add the cache validation header, such as If-None-Match in requests, as NSURLSession will automatically add those headers if needed. It means we can remove custom Etag handling from Malibu to avoid further confusion.

To override default NSURLSession behaviour you can either create request with different cache policy or pass custom URLSessionConfiguration to apply it to every request made by your instance of Networking class:

var request: Request {
    switch self {
    case .course:
      return Request.get("/endpoint", cachePolicy: .reloadIgnoringLocalCacheData)
    }
}

// or
let configuration = URLSessionConfiguration.default.copy() as! URLSessionConfiguration
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData

let networking = Networking<Api>(sessionConfiguration: .custom(configuration))

Thanks @vadymmarkov!

I tested both your solutions, but they didn't work entirely.

The first approach did resolve the header issue: if I clean the cache at the app start, the first request will never contain the If-None-Match header, while the subsequent requests will. So far so good. The problem is that these requests will fail with noDataInResponse.

The second method didn't seem to make any difference when compared to my original code.

I added two branches with those attempts, called method-request and method-session-config

You could also try Request.get("/endpoint", etagPolicy: .disabled, cachePolicy: .reloadIgnoringLocalCacheData) since there's still a custom Etag handling that will try to store and then set Etag header.

What are you trying to achieve in your app btw? Because usually it's not a problem to use Etags if the backend supports this and responds accordingly.

But if I disable ETags at all, I'll take no advantage of the local cache. My goal is to effectively be able to clear the local cache whenever I think I need/should.

Currently, I "clean the cache", but the backend still responds with 304 and no content. Then the app uses some local cache instead. Instead, I expected the request not to contain the ETag, so the server can send fresh data.

But I do want to have those requests cached and using ETags when the app is able to reuse the cached data.

But shouldn't this logic be put on the backend? Etag is an identifier for a specific version of a resource usually cached on the client. If it's been outdated over time the backend should respond with fresh data, not 304, and send new Etag back in the response. Doing this way you don't really need to clear the local cache in your app in order to be up-to-date.

The server already does that. If the new content doesn't match the ETag, it will respond with this new content and a new tag.

The issue here is that I am forcing the app to clean its cache, and it's not working. If I call Malibu.clearStorage(), I should expect that the following requests would not have a If-None-Match header, and this is not happening.

Yes, you're right @guilhermearaujo. Malibu.clearStorage() doesn't work as expected because the custom Etag handling in Malibu is currently in conflict with the automatic cache management from URLSession.

There is a pull request that removes ETag policy in favour of URLRequest.CachePolicy https://github.com/hyperoslo/Malibu/pull/105 because it doesn't make sense to have them both. Also, Malibu.clearStorage() has been renamed to Malibu.clearOfflineRequestStorage as it doesn't deal with ETags anymore.

While this change doesn't really solve the problem you're facing, I think it's the right thing to do anyway. As far as I know there is no easy way to clear URLSession local cache since it's something you normally control on a request or configuration level, but I would assume there are some workarounds.

@vadymmarkov that's great. I tested the new version, and it's indeed better to have a single variable to control caching.

And with further testing, I discovered that URLSession requests are also cached on URLCache. And if I need to force-clean the cache, I can call URLCache.shared.removeAllCachedResponses()

Looking forward to the new release.

Updating!