3

I want to intercept all network calls (request and response) in an application, so I'm implementing a concrete URLProtocol subclass.

Most online examples simply create a new URLSession with the default configuration for each task. Which means that any configuration from the originating session is lost.

One approach is to store the task from init(task: URLSessionTask, cachedResponse: CachedURLResponse?, client: URLProtocolClient?) and resume it in startLoading(), but then there is no way to get notified of the task results.

It seems I can either create a new session and loose the configuration, or reuse the session but not get notified of the task events, but not both.

Question: how do I implement URLProtocol, more precisely startLoading(), so that the session configuration is not lost, and I still get the task's delegate callbacks?

Ideas:

  1. Extract the session (and the configuration) from the task. Cons: exposing a private property not guaranteed to exist.
  2. Give up on URLProtocol and swizzle a bunch of URLSession and URLSessionTask methods. Cons: requires a lot of swizzling.
  3. ?
Nikola Lajic
  • 3,985
  • 28
  • 34
  • What do you mean by "all network calls" here? There are a lot of network calls that URLProtocol cannot access, including requests from WKWebView, but numerous other things that don't use the URL loading system, and also background sessions that do use the URL loading system. Are you limiting this to just network calls that go through URLSession? Are you including UIWebViews, or just URLSessionTasks you control directly? Everything you can configure inside an URLProtocol should be knowable from the request. Is there a field you're missing? – Rob Napier Dec 12 '18 at 16:41
  • @RobNapier sorry for the poor wording, by "all network calls" I meant network calls made with URLSession. My main concern is if a call is made with an ephermal/background/custom session that the configuration will be ignored if I simply pass the URLRequest to a "default" session. For example, a background session configuration has the property "isDiscretionary", which I don't see being passed trough the URLRequest. My question would be, if a session creates a URLRequest how will it be executed if you pass it to a differently configured session for execution? – Nikola Lajic Dec 12 '18 at 20:08
  • You cannot capture background sessions with URLProtocol (at least not reliably), so `isDiscretionary` is not relevant (which is why it's not there). All the relevant settings for ephemeral sessions should be in the request. Background sessions may run entirely outside your process (sometimes when your app isn't even running), so you have no access there through the URL loading system. If you need that level of network control, you'd have to explore building a network extension (i.e. a VPN). – Rob Napier Dec 12 '18 at 20:56
  • See https://developer.apple.com/documentation/foundation/urlsessionconfiguration/1407496-background for more info on how background sessions work. They're very different from other session types. – Rob Napier Dec 12 '18 at 21:00
  • @RobNapier thanks for the info. I can confirm that URLProtocol is not even consulted for background tasks. But while testing this I ran into a strange issue. Requests created by a custom session report default values, for example `timeoutInterval` always reports the default value `60` even tough I can confirm that `timeoutIntervalForResource` on the session configuration has been set and the request actually times out. How is the data getting passed to my new session then if not trough the request? Or is this some bug? – Nikola Lajic Dec 13 '18 at 16:05
  • Are you sure that you mean to use `timeoutIntervalForResource` rather than `timeoutIntervalForRequest`? The default `timeoutIntervalForResource` is 7 days. – Rob Napier Dec 13 '18 at 16:20
  • @RobNapier that is one example (I tried it with `timeoutIntervalForRequest` also), the same issue happens with other properties such as `allowsCellularAccess` and `networkServiceType`. I created a gist to demonstrate the issue https://git.io/fpdr8. I feel you have answered my original question, so if you wish you can post a formal answer so I can accept it or I can create a summary answer. If you wish we can continue the discussion on the gist comments. In either case thank you very much for the help :) – Nikola Lajic Dec 13 '18 at 18:33
  • 1
    @RobNapier after more testing, your statement `Everything ... should be knowable from the request.` doesn't seem to be true. A simple test with `allowsCellularAccess = false` configured session, shows that passing a created request to a new session will still perform it on cellular networks. The request always reports `allowsCellularAccess == true`. Apple's open source version of `URLSession/Configuration` shows that the session only sets the requests cookie header. So my question still stands, since the request always reports the same values, and just passing it to a new session doesn't work. – Nikola Lajic Dec 14 '18 at 10:48
  • @NikolaLajic where you able to find a solution here? I have the same problem where we needed to ensure that all our calls to our server has certain headers ..etc, so NSURLProtocol seemed natural, and as you mentioned once we wanted to implement startLoading() we had to create new session that did not have the same delegate of the original session that created that task, so delegate like didSendBodyData stopped working. – AsH May 08 '19 at 19:24
  • @Ash83 I went with option 1. by exposing the private `session` of the task and then extracting its configuration. – Nikola Lajic May 10 '19 at 13:10
  • Thanks @NikolaLajic, do you mind if you share code snippet of this? I am guessing you got the task from `func canInit(with task: URLSessionTask) -> Bool` where you get in parameter because during `override func startLoading()` you only have access to the request – AsH Jun 07 '19 at 00:06
  • @Ash83 sorry I no longer have access to this code. As far as I remember I got the task in the `canInit` method. The whole thing was in Obj-C since I had to call private methods. – Nikola Lajic Jun 10 '19 at 08:46

0 Answers0