diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa09d30..4ce72f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Select Xcode 14 - run: sudo xcode-select -s /Applications/Xcode_14.3.1.app + - name: Select Xcode 15 + run: sudo xcode-select -s /Applications/Xcode_15.0.app - name: Test run: swift test diff --git a/.swift-version b/.swift-version index 82544bb..dc28d50 100644 --- a/.swift-version +++ b/.swift-version @@ -1,2 +1,2 @@ -5.8.0 +5.9.0 diff --git a/Package.resolved b/Package.resolved index d15f3c9..5777d8d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/shareup/async-extensions.git", "state" : { - "revision" : "f8dec7a227bbbe15fd4df90c787d4c73e91451ba", - "version" : "4.2.1" + "revision" : "7e727e3b9009a5de429393691f9f499aedb7a109", + "version" : "4.3.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version" : "1.1.0" + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { @@ -32,8 +32,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307", + "version" : "1.0.5" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "1827dc94bdab2eb5f2fc804e9b0cb43574282566", + "version" : "1.0.2" } }, { @@ -41,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "cf281631ff10ec6111f2761052aa81896a83a007", - "version" : "2.58.0" + "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", + "version" : "2.62.0" } }, { @@ -50,8 +59,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "0e0d0aab665ff1a0659ce75ac003081f2b1c8997", - "version" : "1.19.0" + "revision" : "798c962495593a23fdea0c0c63fd55571d8dff51", + "version" : "1.20.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "3bd9004b9d685ed6b629760fc84903e48efec806", + "version" : "1.29.0" } }, { @@ -68,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version" : "1.19.0" + "revision" : "ebf8b9c365a6ce043bf6e6326a04b15589bd285e", + "version" : "1.20.0" } }, { diff --git a/Package.swift b/Package.swift index 532025b..a1eef1f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 import PackageDescription @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/shareup/async-extensions.git", - from: "4.1.0" + from: "4.3.0" ), .package( url: "https://github.com/shareup/dispatch-timer.git", @@ -24,11 +24,11 @@ let package = Package( ), .package( url: "https://github.com/vapor/websocket-kit.git", - from: "2.6.1" + from: "2.14.0" ), .package( url: "https://github.com/apple/swift-nio.git", - from: "2.0.0" + from: "2.62.0" ), ], targets: [ diff --git a/Sources/WebSocket/SystemWebSocket.swift b/Sources/WebSocket/SystemWebSocket.swift index e81825b..c0ef501 100644 --- a/Sources/WebSocket/SystemWebSocket.swift +++ b/Sources/WebSocket/SystemWebSocket.swift @@ -1,6 +1,6 @@ import AsyncExtensions @preconcurrency import Combine -@preconcurrency import Foundation +import Foundation import os.log import Synchronized @@ -292,28 +292,28 @@ private extension SystemWebSocket { var ws: URLSessionWebSocketTask? { switch self { case let .connecting(ws), let .open(ws): - return ws + ws case .unopened, .closed: - return nil + nil } } var description: String { switch self { - case .unopened: return "unopened" - case .connecting: return "connecting" - case .open: return "open" - case .closed: return "closed" + case .unopened: "unopened" + case .connecting: "connecting" + case .open: "open" + case .closed: "closed" } } var debugDescription: String { switch self { - case .unopened: return "unopened" - case let .connecting(ws): return "connecting(\(String(reflecting: ws)))" - case let .open(ws): return "open(\(String(reflecting: ws)))" - case let .closed(error): return "closed(\(error.description))" + case .unopened: "unopened" + case let .connecting(ws): "connecting(\(String(reflecting: ws)))" + case let .open(ws): "open(\(String(reflecting: ws)))" + case let .closed(error): "closed(\(error.description))" } } } diff --git a/Sources/WebSocket/WebSocketClose.swift b/Sources/WebSocket/WebSocketClose.swift index 5214600..70fc062 100644 --- a/Sources/WebSocket/WebSocketClose.swift +++ b/Sources/WebSocket/WebSocketClose.swift @@ -15,15 +15,15 @@ public struct WebSocketClose: Hashable, CustomStringConvertible, Sendable { public extension WebSocketClose { var isNormal: Bool { switch code { - case .normalClosure: return true - default: return false + case .normalClosure: true + default: false } } var isCancelled: Bool { switch code { - case .cancelled: return true - default: return false + case .cancelled: true + default: false } } } diff --git a/Sources/WebSocket/WebSocketCloseCode.swift b/Sources/WebSocket/WebSocketCloseCode.swift index 8faeff8..bda3c21 100644 --- a/Sources/WebSocket/WebSocketCloseCode.swift +++ b/Sources/WebSocket/WebSocketCloseCode.swift @@ -79,23 +79,23 @@ public enum WebSocketCloseCode: Int, CaseIterable, Sendable { extension WebSocketCloseCode: CustomStringConvertible { public var description: String { switch self { - case .invalid: return "invalid" - case .normalClosure: return "normalClosure" - case .goingAway: return "goingAway" - case .protocolError: return "protocolError" - case .unsupportedData: return "unsupportedData" - case .noStatusReceived: return "noStatusReceived" - case .abnormalClosure: return "abnormalClosure" - case .invalidFramePayloadData: return "invalidFramePayloadData" - case .policyViolation: return "policyViolation" - case .messageTooBig: return "messageTooBig" - case .mandatoryExtensionMissing: return "mandatoryExtensionMissing" - case .internalServerError: return "internalServerError" - case .tlsHandshakeFailure: return "tlsHandshakeFailure" - case .cancelled: return "cancelled" - case .alreadyClosed: return "alreadyClosed" - case .timeout: return "timeout" - case .unknown: return "unknown" + case .invalid: "invalid" + case .normalClosure: "normalClosure" + case .goingAway: "goingAway" + case .protocolError: "protocolError" + case .unsupportedData: "unsupportedData" + case .noStatusReceived: "noStatusReceived" + case .abnormalClosure: "abnormalClosure" + case .invalidFramePayloadData: "invalidFramePayloadData" + case .policyViolation: "policyViolation" + case .messageTooBig: "messageTooBig" + case .mandatoryExtensionMissing: "mandatoryExtensionMissing" + case .internalServerError: "internalServerError" + case .tlsHandshakeFailure: "tlsHandshakeFailure" + case .cancelled: "cancelled" + case .alreadyClosed: "alreadyClosed" + case .timeout: "timeout" + case .unknown: "unknown" } } } @@ -136,23 +136,23 @@ extension WebSocketCloseCode { var wsCloseCode: URLSessionWebSocketTask.CloseCode? { switch self { - case .invalid: return .invalid - case .normalClosure: return .normalClosure - case .goingAway: return .goingAway - case .protocolError: return .protocolError - case .unsupportedData: return .unsupportedData - case .noStatusReceived: return .noStatusReceived - case .abnormalClosure: return .abnormalClosure - case .invalidFramePayloadData: return .invalidFramePayloadData - case .policyViolation: return .policyViolation - case .messageTooBig: return .messageTooBig - case .mandatoryExtensionMissing: return .mandatoryExtensionMissing - case .internalServerError: return .internalServerError - case .tlsHandshakeFailure: return .tlsHandshakeFailure - case .cancelled: return nil - case .alreadyClosed: return nil - case .timeout: return nil - case .unknown: return nil + case .invalid: .invalid + case .normalClosure: .normalClosure + case .goingAway: .goingAway + case .protocolError: .protocolError + case .unsupportedData: .unsupportedData + case .noStatusReceived: .noStatusReceived + case .abnormalClosure: .abnormalClosure + case .invalidFramePayloadData: .invalidFramePayloadData + case .policyViolation: .policyViolation + case .messageTooBig: .messageTooBig + case .mandatoryExtensionMissing: .mandatoryExtensionMissing + case .internalServerError: .internalServerError + case .tlsHandshakeFailure: .tlsHandshakeFailure + case .cancelled: nil + case .alreadyClosed: nil + case .timeout: nil + case .unknown: nil } } } diff --git a/Sources/WebSocket/WebSocketMessage.swift b/Sources/WebSocket/WebSocketMessage.swift index 9f6d7d2..d969b05 100644 --- a/Sources/WebSocket/WebSocketMessage.swift +++ b/Sources/WebSocket/WebSocketMessage.swift @@ -11,8 +11,8 @@ public enum WebSocketMessage: CustomStringConvertible, Hashable, Sendable { public var description: String { switch self { - case let .data(data): return String(decoding: data.prefix(100), as: UTF8.self) - case let .text(text): return text + case let .data(data): String(decoding: data.prefix(100), as: UTF8.self) + case let .text(text): text } } } @@ -21,10 +21,10 @@ public extension WebSocketMessage { var stringValue: String? { switch self { case let .data(data): - return String(data: data, encoding: .utf8) + String(data: data, encoding: .utf8) case let .text(text): - return text + text } } } @@ -45,8 +45,8 @@ extension WebSocketMessage { var wsMessage: URLSessionWebSocketTask.Message { switch self { - case let .data(data): return .data(data) - case let .text(text): return .string(text) + case let .data(data): .data(data) + case let .text(text): .string(text) } } } diff --git a/Tests/WebSocketTests/Server/WebSocketServer.swift b/Tests/WebSocketTests/Server/WebSocketServer.swift index c2bcebb..7d767e1 100644 --- a/Tests/WebSocketTests/Server/WebSocketServer.swift +++ b/Tests/WebSocketTests/Server/WebSocketServer.swift @@ -15,7 +15,9 @@ enum WebSocketServerOutput: Hashable { private typealias WS = WebSocketKit.WebSocket final class WebSocketServer { - let port: UInt16 + var port: Int { _port! } + private var _port: Int? + let maximumMessageSize: Int // Publisher provided by consumers of `WebSocketServer` to provide the output @@ -30,12 +32,9 @@ final class WebSocketServer { private var channel: Channel? init( - port: UInt16, outputPublisher: P, - usesTLS _: Bool = false, maximumMessageSize: Int = 1024 * 1024 ) throws where P.Output == WebSocketServerOutput, P.Failure == Error { - self.port = port self.outputPublisher = outputPublisher.eraseToAnyPublisher() self.maximumMessageSize = maximumMessageSize @@ -45,8 +44,15 @@ final class WebSocketServer { on: eventLoopGroup, onUpgrade: onWebSocketUpgrade ) - .bind(host: "127.0.0.1", port: Int(port)) + .bind(to: SocketAddress( + ipAddress: "127.0.0.1", + port: 0 // random port + )) .wait() + + if let port = channel?.localAddress?.port { + _port = port + } } private func makeWebSocket( @@ -54,17 +60,21 @@ final class WebSocketServer { onUpgrade: @escaping (HTTPRequestHead, WS) -> Void ) -> ServerBootstrap { ServerBootstrap(group: eventLoopGroup) - .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) - .childChannelInitializer { (channel: Channel) in + .serverChannelOption( + ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), + value: 1 + ) + .childChannelInitializer { channel in let ws = NIOWebSocketServerUpgrader( shouldUpgrade: { channel, _ in channel.eventLoop.makeSucceededFuture([:]) }, upgradePipelineHandler: { channel, req in - WS.server(on: channel) { onUpgrade(req, $0) } + WebSocket.server(on: channel) { ws in + onUpgrade(req, ws) + } } ) - return channel.pipeline.configureHTTPServerPipeline( withServerUpgrade: ( upgraders: [ws], diff --git a/Tests/WebSocketTests/SystemWebSocketTests.swift b/Tests/WebSocketTests/SystemWebSocketTests.swift index 14ef82b..49103e4 100644 --- a/Tests/WebSocketTests/SystemWebSocketTests.swift +++ b/Tests/WebSocketTests/SystemWebSocketTests.swift @@ -3,8 +3,6 @@ import Synchronized @testable import WebSocket import XCTest -private var ports = (50_000 ... 52_000).map { UInt16($0) } - // NOTE: If `WebSocketTests` is not marked as `@MainActor`, calls to // `wait(for:timeout:)` prevent other asyncronous events from running. // Using `await waitForExpectations(timeout:handler:)` works properly @@ -360,16 +358,15 @@ private let empty: Empty = Empty( ) private extension SystemWebSocketTests { - func url(_ port: UInt16) -> URL { URL(string: "ws://127.0.0.1:\(port)/socket")! } + func url(_ port: Int) -> URL { URL(string: "ws://127.0.0.1:\(port)/socket")! } func makeServerAndClient( onOpen: @escaping @Sendable () -> Void = {}, onClose: @escaping @Sendable (WebSocketClose) -> Void = { _ in } ) async throws -> (WebSocketServer, SystemWebSocket) { - let port = ports.removeFirst() - let server = try WebSocketServer(port: port, outputPublisher: subject) + let server = try WebSocketServer(outputPublisher: subject) let client = try! await SystemWebSocket( - url: url(port), + url: url(server.port), options: .init(timeoutIntervalForRequest: 2), onOpen: onOpen, onClose: onClose @@ -381,10 +378,9 @@ private extension SystemWebSocketTests { onOpen: @escaping @Sendable () -> Void = {}, onClose: @escaping @Sendable (WebSocketClose) -> Void = { _ in } ) async throws -> (WebSocketServer, SystemWebSocket) { - let port = ports.removeFirst() - let server = try WebSocketServer(port: 52_001, outputPublisher: empty) + let server = try WebSocketServer(outputPublisher: empty) let client = try! await SystemWebSocket( - url: url(port), + url: url(19), options: .init(timeoutIntervalForRequest: 2), onOpen: onOpen, onClose: onClose @@ -396,10 +392,9 @@ private extension SystemWebSocketTests { onOpen: @escaping @Sendable () -> Void = {}, onClose: @escaping @Sendable (WebSocketClose) -> Void = { _ in } ) async throws -> (WebSocketServer, WebSocket) { - let port = ports.removeFirst() - let server = try WebSocketServer(port: port, outputPublisher: subject) + let server = try WebSocketServer(outputPublisher: subject) let client = try! await SystemWebSocket( - url: url(port), + url: url(server.port), options: .init(timeoutIntervalForRequest: 2), onOpen: onOpen, onClose: onClose