`URLSessionConfiguration.httpAdditionalHeaders` ignored on LInux?

Given the following code:

#if canImport(FoundationEssentials) && canImport(FoundationNetworking)
import FoundationEssentials
import FoundationNetworking
#else
import Foundation
#endif

#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif

class MockURLProtocol: URLProtocol {
  override class func canInit(with request: URLRequest) -> Bool {
    print("canInit received, header fields are \(request.allHTTPHeaderFields ?? [:])")
    return true 
  }
  override class func canonicalRequest(for request: URLRequest) -> URLRequest { 
    print("canonicalRequest received, header fields are \(request.allHTTPHeaderFields ?? [:])")
    return request 
  }

  override func startLoading() {
    print("startLoading, got request: \(self.request)")
    print("all header fields: \(self.request.allHTTPHeaderFields ?? [:])")
    print("header for foo: \(self.request.value(forHTTPHeaderField: "Foo"))")

    exit(0)
  }
}

var config = URLSessionConfiguration.ephemeral
config.protocolClasses = [MockURLProtocol.self]
config.httpAdditionalHeaders = ["Foo" : "Bar"]

let session = URLSession(configuration: config)
let request = URLRequest(url: URL(string: "http://something.com")!)

print("data is \(try await session.data(for: request))")

If I run this on macOS, the httpAdditionalHeaders make it to URLProtocol.startLoading():

$ swift httptest.swift
canInit received, header fields are ["Foo": "Bar"]
canonicalRequest received, header fields are ["Foo": "Bar"]
startLoading, got request: http://something.com
all header fields: ["Foo": "Bar"]
header for foo: Optional("Bar")

However, if I run the same code on Linux, it does not:

$ container run --rm -v $(pwd):/opt/src -it swift swift /opt/src/httptest.swift
canInit received, header fields are [:]
startLoading, got request: http://something.com
all header fields: [:]
header for foo: nil

Is this expected, am I doing something wrong, or is this a bug? Same behavior results if I start with URLSessionConfiguration.default instead of .ephemeral. I also find it interesting that canonicalRequest() gets called on Darwin, but not on Linux.

1 Like

swift-corelibs-foundation's URLSession is a pale imitation of macOS', but you can inspect the source to see where the issue may be. httpAdditionalHeaders property.

1 Like

Yeah, I’d poked around in there last night, but I hadn’t had time to be thorough about it, and so I hadn’t found anything. Now, I think I’ve worked out what happened here.

This bug was actually reported back in 2016. It was supposedly fixed in 2017. That fix put the code to propagate the additional headers in _HTTPURLProtocol's configureEasyHandle, here. But if you use a custom URLProtocol (say, for unit test mocking purposes), it’s not going to get there.

Obvious workarounds are thwarted by access control; the session property on URLSessionTask is internal, preventing a custom URLProtocol from implementing this same logic, and _HTTPURLProtocol is of course also internal, so we can’t just subclass it to inherit this functionality.

Anyway, in my opinion, this spot is where the code to add the additional header fields from the config should be, in order to match the behavior of the “real” URLSession. Do you know whether swift-corelibs-foundation takes bugfix PRs from all comers, or would this have to go through a process similar to swift-evolution?

1 Like

It takes PR from anywhere, especially since this is a bug fix, but I have no idea whether it'll be merged and released in a timely manner.