Is there a WebSocketTask in FoundationNetworking?

On my Mac in the Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53) REPL this works:

import Foundation
let task = URLSession.shared.webSocketTask(with: URL(string: "wss://example")!)

But on Ubuntu in Swift for Tensorflow version 5.3-dev (LLVM 55d27a5828, Swift 6a5d84ec08), this does not work:

import Foundation
import FoundationNetworking
let task = URLSession.shared.webSocketTask(with: URL(string: "wss://example")!)

Compiler error: value of type 'URLSession' has no member 'webSocketTask'

I see this is the discussion on splitting FoundationNetworking out of Foundation: Pitch: Move URLSession to new FoundationNetworking module

Is there no WebSocket support in FoundationNetworking, despite its presence in Foundation?

Typically I've been referring to the Apple Developer Swift docs as an API reference: Apple Developer Documentation
Is there anywhere I can find a FoundationNetworking-specific API reference?

I believe the library underlying Foundation's WebSocket functionality is Apple's SwiftNIO (?) found here: GitHub - apple/swift-nio: Event-driven network application framework for high performance protocol servers & clients, non-blocking.. It indicates on its GitHub page that it supports Ubuntu. Perhaps it will be necessary to install this library as a dependency and drop down to the NIO layer to get WebSocket functionality in linux?

I was able to build both SwiftNIO and the WebSocketKit library on my Ubuntu machine. I was able to get WebSocketKit working as a websockets client.

I would still be interested in knowing why URLSession.webSocketTask didn't make it into FoundationNetworking, and where I can find the API docs for FoundationNetworking so I can know where it diverges from mac's Foundation.

1 Like

I’m not aware of any API docs for the open source Foundation specifically. You can always look at the source itself though: swift-corelibs-foundation/Sources/FoundationNetworking/URLSession at main · apple/swift-corelibs-foundation · GitHub

There’s also an implementation status page here: swift-corelibs-foundation/Status.md at main · apple/swift-corelibs-foundation · GitHub

I’m not sure if work on WebSockets is already planned but I’m sure they’d be happy to accept PRs to implement any missing functionality.

URLSessionWebSocketTask is written on top of Apple's Network.framework, not SwiftNIO. Network.framework is only available on Apple systems and won't compile on Linux. Any library that uses SwiftNIO should work though.

@tanner0101 Thanks for the FoundationNetworking links, these will come in handy!

@wtroughton Fascinating, thank you. I didn't realize it was written on top of the Apple-only Network.framework.

All of Apple's Foundation is written on top of Apple's libraries. swift-corelibs-foundation is a Swift implementation of those APIs on top of various open source libraries and so is cross platform. If you want to build additional functionality you'll need to implement it in terms of FoundationNetwork's underlying networking library, (libcurl). It's unlikely we'll get this functionality any time soon.

We should really replace swift-corelibs-foundation's dependency on cURL with a namespaced copy of SwiftNIO.

4 Likes

Is there anything holding this back? AFAIK, nio doesn’t use Foundation, so there shouldn’t be any layering issues.

I’ve recently been experimenting with a networking API, built on async-http-client and with a goal to replace dependencies on Foundation (unlike nio, it does use parts of it, although it could easily be refactored not to). The only things I’ve had to change are URL (which I’ve replaced with a new, pure Swift implementation based on the latest WHATWG spec), and base64 encoding (nio also doesn’t use Foundation for this, but also doesn’t expose any utility function for async-http-client to use).

@Karl as far as I know the hardest part will be pulling in NIO's source code and namespacing it. You can't just drop the NIO, NIOHTTP1, NIOSSL, etc modules into Foundation privately since their symbols would clash with packages that rely on Foundation and SwiftNIO via SPM.

It would be straightforward enough to copy in the source code and rename modules, but you'd probably want to automate this process so that you can keep the NIO version up to date with bug fixes. I imagine this would work similarly to how NIOSSL and Swift Crypto vendor BoringSSL: swift-crypto/vendor-boringssl.sh at main · apple/swift-crypto · GitHub

Note that this would mean Foundation would be including a private, namespaced implementation of SwiftNIO and BoringSSL (for TLS).

I don't see any reason why this can't / shouldn't be done though. I don't know the details of libcurl but I imagine a copy of NIO and BoringSSL statically linked would be a hell of a lot lighter (and faster).

2 Likes

This seems like a common-enough problem that we should have support for it in the compiler. It‘s not just Foundation - it would also affect any networking API which used nio under the hood but also wanted to be usable as part of appa which may include a different version of nio.

I’m guessing this would look something like @_implementationOnly imports, but for package dependencies. We’d build those dependencies like normal, use objcopy to prefix the symbols, then when compiling the dependent libraries (e.g. Foundation, MyNetworkAPI), we’d tell the swift compiler to use that prefixed version of the library. Just like implementationOnly imports, you wouldn’t be able to expose types or protocol conformances from those prefixed libraries.

1 Like

Remember that the support for this would need to be several levels deep, as we must also do this for assembly, C, and C++ (all of which are used by SwiftNIO).

This is not a reason not to pursue this work: I’d like this support for swift-nio-ssl itself, which currently has to manually namespace the BoringSSL symbols with huge risks for failure or regression.

I wonder if FoundationNetworking could be made into a package as well as be part of the swift distribution.

Ie if using SwiftPM then FoundationNetworking is just another package and would be compiled by swift build and would use NIO as normal, but also NIO would be built manually as part of the full swift build and installed in the swift distribution libdirs for non-SwiftPM usage ie in scripts etc.

This would mean more work in the build process but it might solve the namespacing issue?

Agreed. This seems tough to pull off but would be amazing. Vapor is already copying the vendor-boringssl.sh strategy that SwiftNIO SSL and SwiftCrypto use in our JWTKit repo with possibly more repos to come. If that ever breaks we're gonna be SOL. I'd also like to ship a vendored copy of SQLite with our SQLiteNIO package but I haven't had time to figure out writing an equivalent vendor script. This stuff is hard. SPM doing this would make it a lot easier to write great Swift packages.