swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.3k stars 1.14k forks source link

[SR-15284] SwiftFoundation URLProtocol does not have access to httpBody or httpBodyStream for upload tasks #3199

Open swift-ci opened 3 years ago

swift-ci commented 3 years ago
Previous ID SR-15284
Radar None
Original Reporter colincornaby (JIRA User)
Type Bug
Environment swift:latest under Docker
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: 4f4549269b510d966b4ce51d1a0e3c16

Issue Description:

This is a behavior difference from macOS Foundation. This bug is reproducible on Linux, but not on other Apple platforms like iOS or macOS.

URLProtocols typically have access to the httpBody or httpBodyStream. Below shows an example of how to retrieve the body of an upload task.

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

class TestProtocol: URLProtocol {
    override class func canInit(with task: URLSessionTask) -> Bool {
        return true
    }

    override class func canInit(with request: URLRequest) -> Bool {
        return true
    }

    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }

    override func startLoading() {
        //one of these should be populated with non nil data
        let body = request.httpBody
        let steam = request.httpBodyStream
        client?.urlProtocol(self, didFailWithError: NSError())
    }

    override func stopLoading() {
    }
}

let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [TestProtocol.self]
let session = URLSession(configuration: configuration)
var request = URLRequest(url: URL(string: "http://apple.com")!)
request.httpMethod = "POST"
let contents = "Hello Apple"
let task = session.uploadTask(with: request, from: contents.data(using: .utf8)!) { _, _, error in

}
task.resume()

On Linux, neither body or stream is ever populated.

This seems to be linked to how Swift Foundation handles body data. On macOS, the request passed to the URLProtocol is altered to include the body data, even if the body data originated from the uploadTask call and wasn't directly populated in the request.

In SwiftFoundation, the body data is shunted into an internal member of URLSessionTask named knownBody. knownBody is then accessible using the internal getBody function. HTTPURLProtocol makes use of the internal getBody function to upload data as part of a POST.

There are several ramifications of the body data not being available:

My proposal would be that the request given to the URLRequest should be populated to match Apple Foundation behavior. Changes could be introduced to classes HTTPURLProtocol to migrate over to a public supported URLRequest version of fetching data, instead of using the internal getBody function.

iDevPro commented 4 months ago

Hi, on iOS is this behavior same ? I can't access to httpBody in URLProtocol startLoading function too :)