Skip to content

Commit ef3896f

Browse files
committed
✨ Add support for web sockets.
1 parent 39ceaaa commit ef3896f

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

.swiftlint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ excluded:
1212
allow_zero_lintable_files: false
1313

1414
# If true, SwiftLint will treat all warnings as errors.
15-
strict: true
15+
strict: false
1616

1717
# If true, SwiftLint will check for updates after linting or analyzing.
1818
check_for_updates: true
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Connor Ricks
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
24+
import Vapor
25+
26+
// MARK: - Socket
27+
28+
public struct Socket: RouteComponent {
29+
30+
// MARK: Properties
31+
32+
@usableFromInline
33+
let route: Route
34+
35+
// MARK: Initializers
36+
37+
@inlinable
38+
public init(
39+
_ path: PathComponent...,
40+
maxFrameSize: WebSocketMaxFrameSize = .default,
41+
shouldUpgrade: @escaping (@Sendable (Request) async throws -> HTTPHeaders?) = { _ in [:] },
42+
onUpgrade: @Sendable @escaping (Request, WebSocket) async -> Void
43+
) {
44+
self.init(path, maxFrameSize: maxFrameSize, shouldUpgrade: shouldUpgrade, onUpgrade: onUpgrade)
45+
}
46+
47+
@inlinable
48+
public init<Path: Collection>(
49+
_ path: Path,
50+
maxFrameSize: WebSocketMaxFrameSize = .default,
51+
shouldUpgrade: @escaping (@Sendable (Request) async throws -> HTTPHeaders?) = { _ in [:] },
52+
onUpgrade: @Sendable @escaping (Request, WebSocket) async -> Void
53+
) where Path.Element == PathComponent {
54+
self.route = Route(.GET, path) { request -> Response in
55+
request.webSocket(maxFrameSize: maxFrameSize, shouldUpgrade: shouldUpgrade, onUpgrade: onUpgrade)
56+
}
57+
}
58+
59+
// MARK: Body
60+
61+
public var body: some RouteComponent {
62+
route
63+
}
64+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// MIT License
3+
//
4+
// Copyright (c) 2024 Connor Ricks
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files (the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be included in all
14+
// copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
// SOFTWARE.
23+
24+
import Testing
25+
import Vapor
26+
@testable import VaporRouteBuilder
27+
import XCTVapor
28+
29+
@Suite("Socket Tests") struct SocketTests {
30+
@Test func test_webSocketClient() throws {
31+
let app = Application(.testing)
32+
defer { app.shutdown() }
33+
try app.register {
34+
Socket("foo") { _, ws in
35+
ws.send("foo")
36+
ws.close(promise: nil)
37+
}
38+
}
39+
40+
app.environment.arguments = ["serve"]
41+
app.http.server.configuration.port = 0
42+
43+
try app.start()
44+
45+
XCTAssertNotNil(app.http.server.shared.localAddress)
46+
guard
47+
let localAddress = app.http.server.shared.localAddress,
48+
let port = localAddress.port
49+
else {
50+
XCTFail("couldn't get ip/port from \(app.http.server.shared.localAddress.debugDescription)")
51+
return
52+
}
53+
54+
let promise = app.eventLoopGroup.next().makePromise(of: String.self)
55+
WebSocket.connect(
56+
to: "ws://localhost:\(port)/foo",
57+
on: app.eventLoopGroup.next()
58+
) { ws in
59+
// do nothing
60+
ws.onText { _, string in
61+
promise.succeed(string)
62+
}
63+
}.cascadeFailure(to: promise)
64+
65+
try XCTAssertEqual(promise.futureResult.wait(), "foo")
66+
}
67+
}

0 commit comments

Comments
 (0)