Repository navigation
fix: keep URLSession capture from changing how requests behave - #20
Conversation
|
This introduces a cookie-policy regression: a .default session with httpShouldSetCookies = false now sends shared cookies when capture is enabled, whereas the previous implementation did not. The shared loader uses its own cookie policy instead of the originating session’s. Could we preserve the caller’s policy and add a regression test? Preserving the caller-provided configuration, as Pulse’s URLSessionProxy does, is one possible approach. |
The capture re-issued every request through a new ephemeral session and reported to the app only once the response was complete. With it installed: - cookies were neither sent nor stored, so cookie sessions stopped working; - data reached the app before the response did, which made URLSession.bytes(for:) fail and told delegates things out of order; - a streamed response arrived all at once when it ended; - every request opened a connection of its own. Requests now run in one delegate-driven session built from the default configuration, and the response and its data are handed over as they arrive. Only the head of a large response is kept for the record. Redirects are still followed by the capture. Handing them to the app's session was tried: told the load was cancelled for the redirect, the session sometimes reported that cancellation instead of following. install() covers URLSession.shared only, so the comment and the setup guide now say how to capture a session built from a configuration, and what capturing one changes.
The shared re-issuing session handled cookies through the shared store for every captured request, so a session whose configuration turned cookies off, or kept a store of its own, sent cookies it never agreed to. What a configuration says about cookies cannot be seen from a URLProtocol, so the protocol an app puts into its own configuration now re-issues through a session that handles none, as before, and only the protocol registered for URLSession.shared — whose policy is the shared store's — keeps cookies working. Tasks are now keyed by identity in the loader: a task identifier is only unique within one session, and the loader serves two.
9bd5092 to
982ee2b
Compare
|
Thanks, that's a real regression and I should have caught it. Fixed in a commit on top of a rebase onto What changed. A Regression test. Also fixed while there: the loader keyed observers by On preserving the caller's configuration. I agree that is the right end state — a session built from a copy of the caller's configuration would carry cookies, cache, timeouts and proxy settings faithfully. Since
|
heoblitz
left a comment
There was a problem hiding this comment.
Thank you for your contribution.
Summary
NectoURLSessionCapturere-issued every captured request through a new ephemeralURLSessionwith a completion-handler data task, and told the app's session about the response only once it was complete. Measured against a loopback HTTP server, that changes how the app's requests behave while the capture is installed:URLSession.shared.Set-Cookiefrom a captured response is not stored and no cookie is sent on the next request, so cookie-based sessions break. (A session the app configured itself keeps this behaviour, by design: its cookie policy cannot be seen from aURLProtocol, so no cookie is sent or stored on its behalf — see the review discussion below.)didLoadbeforedidReceive(response).URLSession.bytes(for:)fails withNSURLErrorDomain -1, and aURLSessionDataDelegateseesdata, response, complete.This PR runs captured requests through one shared delegate-driven session built from the default configuration, hands the response and each piece of data to the client as they arrive, and keeps only the head of a large body for the record (
Body.captureLimit).Redirects are still followed inside the capture, as before. I tried handing them to the app's session with
urlProtocol(_:wasRedirectedTo:redirectResponse:)(the Apple CustomHTTPProtocol pattern): told the load was cancelled for the redirect, the client sometimes reported that cancellation instead of following (2 failures in 16 runs of 200 redirects). AURLProtocolis also never told when a delegate refuses a redirect, so holding the response behind a timer was the only alternative, and that changes behaviour for slow delegates. The limitation is documented instead.Two documentation corrections come with it:
install()coversURLSession.sharedonly (the comment claimed sessions built fromURLSessionConfiguration.defaultwere covered; they are not), anddocs/setup.mdnow says how to capture a session built from a configuration and what capturing one changes (cookies handled only forURLSession.shared, no cache, redirects and authentication challenges not passed to the delegate).Changelog
Fix
URLSessionNetworkPluginbreaking cookie sessions,URLSession.bytes(for:)and streamed responses in the app it captures.Test Plan
New test target
NectoURLSessionCaptureTestswith a small loopback HTTP/1.1 server (LocalHTTPServer.swift). Every behavioural test above fails onmainand passes here; the cookie and delegate-order tests run both with and without the capture, so the harness itself is checked.swift test: 178 tests in 12 suites passed. The capture suite was then run 20 more times with--skip-build(10,000 redirects followed) without a failure.xcodebuild -project Necto.xcodeproj -scheme ExampleApp -destination 'generic/platform=iOS Simulator' build: BUILD SUCCEEDED, so the module compiles for iOS.script/test e2ewas not run; this machine has no iOS 26.2 runtime.