-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Return to using URLSessionWebSocketTask (#13)
- Replace NWConnection with URLSessionWebSocketTask because it was too hard to know what the correct NWConnection behavior should be given different server responses - Update to Swift 5.8 Note: The CI tests do not pass because GitHub Actions do not currently support Xcode 14.3.
- Loading branch information
Showing
23 changed files
with
978 additions
and
979 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,14 @@ | ||
name: Test | ||
name: CI | ||
|
||
on: | ||
push: | ||
on: push | ||
|
||
jobs: | ||
test: | ||
name: Test | ||
library: | ||
runs-on: macos-latest | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Build | ||
run: swift build -v | ||
- uses: actions/checkout@v3 | ||
- name: Select Xcode 14 | ||
run: sudo xcode-select -s /Applications/Xcode_14.3.app | ||
- name: Test | ||
run: swift test -v | ||
run: swift test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
5.3.0 | ||
5.8.0 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
--disable \ | ||
hoistAwait, \ | ||
hoistTry | ||
|
||
--decimalgrouping 3,5 | ||
--funcattributes prev-line | ||
--minversion 0.47.2 | ||
--maxwidth 96 | ||
--typeattributes prev-line | ||
--wraparguments before-first | ||
--wrapparameters before-first | ||
--wrapcollections before-first | ||
--xcodeindentation enabled |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
{ | ||
"pins" : [ | ||
{ | ||
"identity" : "async-extensions", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/shareup/async-extensions.git", | ||
"state" : { | ||
"revision" : "59504194f84b8c66a27503b5fd0640ac9b01f42a", | ||
"version" : "4.1.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "dispatch-timer", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/shareup/dispatch-timer.git", | ||
"state" : { | ||
"revision" : "2d8c304aa6f382a7a362cd5a814884f3930c5662", | ||
"version" : "3.0.1" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-atomics", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-atomics.git", | ||
"state" : { | ||
"revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", | ||
"version" : "1.1.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-collections", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-collections.git", | ||
"state" : { | ||
"revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", | ||
"version" : "1.0.4" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-nio", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-nio.git", | ||
"state" : { | ||
"revision" : "9b2848d76f5caad08b97e71a04345aa5bdb23a06", | ||
"version" : "2.49.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-nio-ssl", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-nio-ssl.git", | ||
"state" : { | ||
"revision" : "4fb7ead803e38949eb1d6fabb849206a72c580f3", | ||
"version" : "2.23.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "swift-nio-transport-services", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-nio-transport-services.git", | ||
"state" : { | ||
"revision" : "c0d9a144cfaec8d3d596aadde3039286a266c15c", | ||
"version" : "1.15.0" | ||
} | ||
}, | ||
{ | ||
"identity" : "synchronized", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/shareup/synchronized.git", | ||
"state" : { | ||
"revision" : "85653e23270ec88ae19f8d494157769487e34aed", | ||
"version" : "4.0.1" | ||
} | ||
}, | ||
{ | ||
"identity" : "websocket-kit", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/vapor/websocket-kit.git", | ||
"state" : { | ||
"revision" : "2b8885974e8d9f522e787805000553f4f7cce8a0", | ||
"version" : "2.7.0" | ||
} | ||
} | ||
], | ||
"version" : 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,59 @@ | ||
// swift-tools-version:5.3 | ||
// swift-tools-version:5.8 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "WebSocket", | ||
platforms: [ | ||
.macOS(.v11), .iOS(.v14), .tvOS(.v14), .watchOS(.v7), | ||
.macOS(.v12), .iOS(.v15), .tvOS(.v15), .watchOS(.v8), | ||
], | ||
products: [ | ||
.library( | ||
name: "WebSocket", | ||
targets: ["WebSocket"] | ||
), | ||
], | ||
dependencies: [], | ||
dependencies: [ | ||
.package( | ||
url: "https://github.com/shareup/async-extensions.git", | ||
from: "4.1.0" | ||
), | ||
.package( | ||
url: "https://github.com/shareup/dispatch-timer.git", | ||
from: "3.0.0" | ||
), | ||
.package( | ||
url: "https://github.com/vapor/websocket-kit.git", | ||
from: "2.6.1" | ||
), | ||
.package( | ||
url: "https://github.com/apple/swift-nio.git", | ||
from: "2.0.0" | ||
), | ||
], | ||
targets: [ | ||
.target( | ||
name: "WebSocket", | ||
dependencies: [] | ||
dependencies: [ | ||
.product(name: "AsyncExtensions", package: "async-extensions"), | ||
.product(name: "DispatchTimer", package: "dispatch-timer"), | ||
], | ||
swiftSettings: [ | ||
.unsafeFlags([ | ||
"-Xfrontend", "-warn-concurrency", | ||
"-Xfrontend", "-enable-actor-data-race-checks", | ||
]), | ||
] | ||
), | ||
.testTarget( | ||
name: "WebSocketTests", | ||
dependencies: ["WebSocket"] | ||
dependencies: [ | ||
.product(name: "NIO", package: "swift-nio"), | ||
.product(name: "NIOHTTP1", package: "swift-nio"), | ||
.product(name: "NIOWebSocket", package: "swift-nio"), | ||
"WebSocket", | ||
.product(name: "WebSocketKit", package: "websocket-kit"), | ||
] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import Foundation | ||
import Synchronized | ||
|
||
func webSocketTask( | ||
for url: URL, | ||
options: WebSocketOptions, | ||
onOpen: @escaping @Sendable () async -> Void, | ||
onClose: @escaping @Sendable (WebSocketCloseCode, Data?) async -> Void | ||
) -> URLSessionWebSocketTask { | ||
let session = session(for: options) | ||
|
||
let task = session.webSocketTask(with: url) | ||
task.maximumMessageSize = options.maximumMessageSize | ||
|
||
let delegate = session.delegate as! Delegate | ||
delegate.set(onOpen: onOpen, onClose: onClose, for: ObjectIdentifier(task)) | ||
|
||
return task | ||
} | ||
|
||
func cancelAndInvalidateAllTasks() { | ||
sessions.access { sessions in | ||
sessions.forEach { $0.value.invalidateAndCancel() } | ||
sessions.removeAll() | ||
} | ||
} | ||
|
||
private let sessions = Locked<[WebSocketOptions: URLSession]>([:]) | ||
|
||
private func session(for options: WebSocketOptions) -> URLSession { | ||
sessions.access { sessions in | ||
if let session = sessions[options] { | ||
return session | ||
} else { | ||
let session = URLSession( | ||
configuration: configuration(with: options), | ||
delegate: Delegate(), | ||
delegateQueue: nil | ||
) | ||
|
||
sessions[options] = session | ||
|
||
return session | ||
} | ||
} | ||
} | ||
|
||
private func configuration(with options: WebSocketOptions) -> URLSessionConfiguration { | ||
let config = URLSessionConfiguration.default | ||
config.waitsForConnectivity = false | ||
config.timeoutIntervalForRequest = options.timeoutIntervalForRequest | ||
config.timeoutIntervalForResource = options.timeoutIntervalForResource | ||
return config | ||
} | ||
|
||
private final class Delegate: NSObject, URLSessionWebSocketDelegate, Sendable { | ||
private struct Callbacks: Sendable { | ||
let onOpen: @Sendable () async -> Void | ||
let onClose: @Sendable (WebSocketCloseCode, Data?) async -> Void | ||
} | ||
|
||
// `Dictionary<ObjectIdentifier(URLWebSocketTask): Callbacks>` | ||
private let state: Locked<[ObjectIdentifier: Callbacks]> = .init([:]) | ||
|
||
func set( | ||
onOpen: @escaping @Sendable () async -> Void, | ||
onClose: @escaping @Sendable (WebSocketCloseCode, Data?) async -> Void, | ||
for taskID: ObjectIdentifier | ||
) { | ||
state.access { $0[taskID] = .init(onOpen: onOpen, onClose: onClose) } | ||
} | ||
|
||
func urlSession( | ||
_: URLSession, | ||
webSocketTask: URLSessionWebSocketTask, | ||
didOpenWithProtocol _: String? | ||
) { | ||
let taskID = ObjectIdentifier(webSocketTask) | ||
|
||
if let onOpen = state.access({ $0[taskID]?.onOpen }) { | ||
Task { await onOpen() } | ||
} | ||
} | ||
|
||
func urlSession( | ||
_: URLSession, | ||
webSocketTask: URLSessionWebSocketTask, | ||
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, | ||
reason: Data? | ||
) { | ||
let taskID = ObjectIdentifier(webSocketTask) | ||
|
||
if let onClose = state.access({ $0[taskID]?.onClose }) { | ||
Task { await onClose(WebSocketCloseCode(closeCode), reason) } | ||
} | ||
} | ||
|
||
func urlSession( | ||
_: URLSession, | ||
task: URLSessionTask, | ||
didCompleteWithError error: Error? | ||
) { | ||
let taskID = ObjectIdentifier(task) | ||
|
||
if let onClose = state.access({ $0[taskID]?.onClose }) { | ||
Task { [weak self] in | ||
if let error { | ||
await onClose( | ||
.abnormalClosure, | ||
Data(error.localizedDescription.utf8) | ||
) | ||
} else { | ||
await onClose(.normalClosure, nil) | ||
} | ||
|
||
self?.state.access { _ = $0.removeValue(forKey: taskID) } | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.