Skip to content

Commit 9a1cd1d

Browse files
bjqianzackliuyannisalexiou
authored
Prepare Release 1.0.0.4-preview (#79)
* Add action for dev (#72) * Add message buffer (#73) * Add message buffer * Some improvement * Improve again * Add client to server stream (#74) * Add client to server stream * increase timeout * Add public initializer to HttpConnectionOptions to allow external instantiation (#76) * hide stateful reconnect, prepare release for 1.0.0-preview.4 (#78) * Hide stateful reconnect options * Add public init for MsgpackTimestamp * Release 1.0.0-preview.4 * Update readme --------- Co-authored-by: Chenyang Liu <zackliu1995@hotmail.com> Co-authored-by: Yannis Alexiou <yannisalexiou@users.noreply.github.com>
1 parent 11b229f commit 9a1cd1d

File tree

15 files changed

+566
-24
lines changed

15 files changed

+566
-24
lines changed

.github/workflows/swift.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ name: Swift
55

66
on:
77
push:
8-
branches: [ "main" ]
8+
branches: [ "main", "dev" ]
99
pull_request:
10-
branches: [ "main" ]
10+
branches: [ "main", "dev" ]
1111

1212
jobs:
1313
build:

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
# Release History
2+
## 1.0.0-preview.4
3+
4+
### Bugs Fixed
5+
6+
- Fix the accessbility of HttpConnectionOptions
7+
8+
### Features Added
9+
10+
- Support client to server streaming
211

312
## 1.0.0-preview.3
413

@@ -15,4 +24,4 @@
1524
## 1.0.0-preview.1
1625

1726
### Features Added
18-
- The initial preview release of the library
27+
- The initial preview release of the library

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import PackageDescription
2121
let package = Package(
2222
name: "signalr-client-app",
2323
dependencies: [
24-
.package(url: "https://github.com/dotnet/signalr-client-swift", .upToNextMinor(from: "1.0.0-preview.3"))
24+
.package(url: "https://github.com/dotnet/signalr-client-swift", .upToNextMinor(from: "1.0.0-preview.4"))
2525
],
2626
targets: [
2727
.executableTarget(name: "YourTargetName", dependencies: [.product(name: "SignalRClient", package: "signalr-client-swift")])
@@ -113,6 +113,18 @@ for try await item in stream.stream {
113113
}
114114
```
115115

116+
## Client-to-server streaming
117+
To send a stream of data from the client to the server, use the `AsyncStream`:
118+
119+
```swift
120+
let (clientStream, continuation) = AsyncStream.makeStream(of: Int.self)
121+
try await connection.send("UploadStream", arguments: clientStream)
122+
for i in 1...100 {
123+
continuation.yield(i)
124+
}
125+
continuation.finish()
126+
```
127+
116128
## Handle lost connection
117129

118130
### Automatic reconnect
@@ -201,7 +213,7 @@ let connection = HubConnectionBuilder()
201213
| Automatic Reconnection ||
202214
| Stateful Reconnect ||
203215
| Server to Client Streaming ||
204-
| Client to Server Streaming ||
216+
| Client to Server Streaming ||
205217
| Long Polling ||
206218
| Server-Sent Events ||
207219
| WebSockets ||
@@ -211,4 +223,4 @@ let connection = HubConnectionBuilder()
211223

212224
# License
213225

214-
.NET (including the runtime repo) is licensed under the MIT license.
226+
.NET (including the runtime repo) is licensed under the MIT license.

Sources/SignalRClient/HttpConnection.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public struct HttpConnectionOptions {
2828
var webSocket: AnyObject? // Placeholder for WebSocket type
2929
var eventSource: EventSourceAdaptor?
3030
var useStatefulReconnect: Bool? // Not supported yet
31+
32+
public init() {}
3133
}
3234

3335
// MARK: - Models

Sources/SignalRClient/HubConnection.swift

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Foundation
66
public actor HubConnection {
77
private static let defaultTimeout: TimeInterval = 30
88
private static let defaultPingInterval: TimeInterval = 15
9+
private static let defaultStatefulReconnectBufferSize: Int = 100_000_000 // bytes of messages
10+
911
private var invocationBinder: DefaultInvocationBinder
1012
private var invocationHandler: InvocationHandler
1113

@@ -17,6 +19,7 @@ public actor HubConnection {
1719
private let retryPolicy: RetryPolicy
1820
private let keepAliveScheduler: TimeScheduler
1921
private let serverTimeoutScheduler: TimeScheduler
22+
private let statefulReconnectBufferSize: Int
2023

2124
private var connectionStarted: Bool = false
2225
private var receivedHandshakeResponse: Bool = false
@@ -39,9 +42,12 @@ public actor HubConnection {
3942
hubProtocol: HubProtocol,
4043
retryPolicy: RetryPolicy,
4144
serverTimeout: TimeInterval?,
42-
keepAliveInterval: TimeInterval?) {
45+
keepAliveInterval: TimeInterval?,
46+
statefulReconnectBufferSize: Int?) {
4347
self.serverTimeout = serverTimeout ?? HubConnection.defaultTimeout
4448
self.keepAliveInterval = keepAliveInterval ?? HubConnection.defaultPingInterval
49+
self.statefulReconnectBufferSize = statefulReconnectBufferSize ?? HubConnection.defaultStatefulReconnectBufferSize
50+
4551
self.logger = logger
4652
self.retryPolicy = retryPolicy
4753

@@ -107,29 +113,77 @@ public actor HubConnection {
107113
}
108114

109115
public func send(method: String, arguments: Any...) async throws {
110-
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(arguments), streamIds: nil, headers: nil, invocationId: nil)
116+
let (nonstreamArguments, streamArguments) = splitStreamArguments(arguments: arguments)
117+
let streamIds = await invocationHandler.createClientStreamIds(count: streamArguments.count)
118+
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(nonstreamArguments), streamIds: streamIds, headers: nil, invocationId: nil)
111119
let data = try hubProtocol.writeMessage(message: invocationMessage)
112120
logger.log(level: .debug, message: "Sending message to target: \(method)")
113121
try await sendMessageInternal(data)
122+
launchStreams(streamIds: streamIds, clientStreams: streamArguments)
123+
}
124+
125+
private func splitStreamArguments(arguments: Any...) -> ([Any], [any AsyncSequence]) {
126+
var nonstreamArguments: [Any] = []
127+
var streamArguments: [any AsyncSequence] = []
128+
for argument in arguments {
129+
if let stream = argument as? (any AsyncSequence) {
130+
streamArguments.append(stream)
131+
} else {
132+
nonstreamArguments.append(argument)
133+
}
134+
}
135+
return (nonstreamArguments, streamArguments)
114136
}
115137

138+
private func launchStreams(streamIds: [String], clientStreams: [any AsyncSequence]) {
139+
for i in 0 ..< streamIds.count {
140+
Task {
141+
let stream = clientStreams[i]
142+
var err: String? = nil
143+
do {
144+
for try await item in stream {
145+
let streamItem = StreamItemMessage(invocationId: streamIds[i], item: AnyEncodable(item), headers: nil)
146+
let data = try hubProtocol.writeMessage(message: streamItem)
147+
try await sendMessageInternal(data)
148+
}
149+
} catch {
150+
err = "\(error)"
151+
logger.log(level: .error, message: "Fail to send client stream message :\(error)")
152+
}
153+
do {
154+
let completionMessage = CompletionMessage(invocationId: streamIds[i], error: err, result: AnyEncodable(nil), headers: nil)
155+
let data = try hubProtocol.writeMessage(message: completionMessage)
156+
try await sendMessageInternal(data)
157+
} catch {
158+
logger.log(level: .error, message: "Fail to send client stream complete message :\(error)")
159+
}
160+
}
161+
}
162+
}
163+
116164
public func invoke(method: String, arguments: Any...) async throws -> Void {
165+
let (nonstreamArguments, streamArguments) = splitStreamArguments(arguments: arguments)
166+
let streamIds = await invocationHandler.createClientStreamIds(count: streamArguments.count)
117167
let (invocationId, tcs) = await invocationHandler.create()
118-
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(arguments), streamIds: nil, headers: nil, invocationId: invocationId)
168+
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(nonstreamArguments), streamIds: streamIds, headers: nil, invocationId: invocationId)
119169
let data = try hubProtocol.writeMessage(message: invocationMessage)
120170
logger.log(level: .debug, message: "Invoke message to target: \(method), invocationId: \(invocationId)")
121171
try await sendMessageInternal(data)
172+
launchStreams(streamIds: streamIds, clientStreams: streamArguments)
122173
_ = try await tcs.task()
123174
}
124175

125176
public func invoke<TReturn>(method: String, arguments: Any...) async throws -> TReturn {
177+
let (nonstreamArguments, streamArguments) = splitStreamArguments(arguments: arguments)
178+
let streamIds = await invocationHandler.createClientStreamIds(count: streamArguments.count)
126179
let (invocationId, tcs) = await invocationHandler.create()
127180
invocationBinder.registerReturnValueType(invocationId: invocationId, types: TReturn.self)
128-
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(arguments), streamIds: nil, headers: nil, invocationId: invocationId)
181+
let invocationMessage = InvocationMessage(target: method, arguments: AnyEncodableArray(nonstreamArguments), streamIds: streamIds, headers: nil, invocationId: invocationId)
129182
do {
130183
let data = try hubProtocol.writeMessage(message: invocationMessage)
131184
logger.log(level: .debug, message: "Invoke message to target: \(method), invocationId: \(invocationId)")
132185
try await sendMessageInternal(data)
186+
launchStreams(streamIds: streamIds, clientStreams: streamArguments)
133187
} catch {
134188
await invocationHandler.cancel(invocationId: invocationId, error: error)
135189
invocationBinder.removeReturnValueType(invocationId: invocationId)
@@ -144,13 +198,16 @@ public actor HubConnection {
144198
}
145199

146200
public func stream<Element>(method: String, arguments: Any...) async throws -> any StreamResult<Element> {
201+
let (nonstreamArguments, streamArguments) = splitStreamArguments(arguments: arguments)
202+
let streamIds = await invocationHandler.createClientStreamIds(count: streamArguments.count)
147203
let (invocationId, stream) = await invocationHandler.createStream()
148204
invocationBinder.registerReturnValueType(invocationId: invocationId, types: Element.self)
149-
let StreamInvocationMessage = StreamInvocationMessage(invocationId: invocationId, target: method, arguments: AnyEncodableArray(arguments), streamIds: nil, headers: nil)
205+
let StreamInvocationMessage = StreamInvocationMessage(invocationId: invocationId, target: method, arguments: AnyEncodableArray(nonstreamArguments), streamIds: streamIds, headers: nil)
150206
do {
151207
let data = try hubProtocol.writeMessage(message: StreamInvocationMessage)
152208
logger.log(level: .debug, message: "Stream message to target: \(method), invocationId: \(invocationId)")
153209
try await sendMessageInternal(data)
210+
launchStreams(streamIds: streamIds, clientStreams: streamArguments)
154211
} catch {
155212
await invocationHandler.cancel(invocationId: invocationId, error: error)
156213
invocationBinder.removeReturnValueType(invocationId: invocationId)
@@ -672,6 +729,14 @@ public actor HubConnection {
672729
}
673730
return (id, stream)
674731
}
732+
733+
func createClientStreamIds(count: Int) -> [String] {
734+
var streamIds: [String] = []
735+
for _ in 0 ..< count {
736+
streamIds.append(nextId())
737+
}
738+
return streamIds
739+
}
675740

676741
func setResult(message: CompletionMessage) async {
677742
if let invocation = invocations[message.invocationId!] {

Sources/SignalRClient/HubConnectionBuilder.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class HubConnectionBuilder {
1212
private var keepAliveInterval: TimeInterval?
1313
private var url: String?
1414
private var retryPolicy: RetryPolicy?
15+
private var statefulReconnectBufferSize: Int?
1516
private var httpConnectionOptions: HttpConnectionOptions = HttpConnectionOptions()
1617

1718
public init() {}
@@ -79,6 +80,16 @@ public class HubConnectionBuilder {
7980
return self
8081
}
8182

83+
// public func withStatefulReconnect() -> HubConnectionBuilder {
84+
// return withStatefulReconnect(options: StatefulReconnectOptions())
85+
// }
86+
//
87+
// public func withStatefulReconnect(options: StatefulReconnectOptions) -> HubConnectionBuilder {
88+
// self.statefulReconnectBufferSize = options.bufferSize
89+
// self.httpConnectionOptions.useStatefulReconnect = true
90+
// return self
91+
// }
92+
8293
public func build() -> HubConnection {
8394
guard let url = url else {
8495
fatalError("url must be set with .withUrl(String:)")
@@ -94,7 +105,8 @@ public class HubConnectionBuilder {
94105
hubProtocol: hubProtocol,
95106
retryPolicy: retryPolicy,
96107
serverTimeout: serverTimeout,
97-
keepAliveInterval: keepAliveInterval)
108+
keepAliveInterval: keepAliveInterval,
109+
statefulReconnectBufferSize: statefulReconnectBufferSize)
98110
}
99111
}
100112

0 commit comments

Comments
 (0)