通过便捷的观察者接收和发送 websocket 消息。甚至可以在不同的端点上设置多个观察者!
💡观察者类型:经典型、声明型、可绑定型。请阅读以下内容以了解所有这些类型。
专为 Vapor4 构建。
💡Vapor3 版本可在
vapor3
分支和1.0.0
标签中找到
如果您有改进此软件包的好主意,请在 Vapor 的 Discord 聊天中联系我 (@iMike#3049),或者直接发送 pull request。
编辑您的 Package.swift
//add this repo to dependencies
.package(url: "https://github.com/MihaelIsaev/AwesomeWS.git", from: "2.0.0")
//and don't forget about targets
.target(name: "App", dependencies: [
.product(name: "WS", package: "AwesomeWS"),
.product(name: "Vapor", package: "vapor")
]),
WS 库具有 .default
WSID,它代表 DeclarativeObserver
。
💡您可以使用另一种类型的观察者和您的自定义类来声明您自己的 WSID。
您可以这样轻松地开始使用它
app.ws.build(.default).serve()
在这种情况下,它将开始在 /
监听 websocket 连接,但您可以在调用 .serve()
之前更改它
app.ws.build(.default).at("ws").serve()
现在它正在 /ws
监听
您还可以使用中间件保护您的 websocket 端点,例如,您可以在建立连接之前检查身份验证。
app.ws.build(.default).at("ws").middlewares(AuthMiddleware()).serve()
好的,看起来不错,但是如何处理传入的数据呢?
由于我们使用代表 Declarative
观察者的 .default
WSID,我们可以像这样处理传入的数据
app.ws.build(.default).at("ws").middlewares(AuthMiddleware()).serve().onOpen { client in
print("client just connected \(client.id)")
}.onText { client, text in
print("client \(client.id) text: \(text)")
}
还有以下处理程序可用:onClose
、onPing
、onPong
、onBinary
、onByteBuffer
。
💡设置
app.logger.logLevel = .info
或app.logger.logLevel = .debug
以查看有关连接的更多信息
您应该创建一个从 ClassicObserver
继承的新类
import WS
class MyClassicWebSocket: ClassicObserver {
override func on(open client: AnyClient) {}
override func on(close client: AnyClient) {}
override func on(text: String, client: AnyClient) {}
/// also you can override: `on(ping:)`, `on(pong:)`, `on(binary:)`, `on(byteBuffer:)`
}
并且您必须为其声明一个 WSID
extension WSID {
static var myClassic: WSID<MyClassicWebSocket> { .init() }
}
然后开始服务它
app.ws.build(.myClassic).at("ws").serve()
这种观察者旨在以特殊格式发送和接收事件,例如 JSON
{ "event": "<event name>", "payload": <anything> }
或者只是
{ "event": "<event name>" }
💡默认情况下,lib 使用
JSONEncoder
和JSONDecoder
,但您可以在setup
方法中使用任何其他编码器/解码器替换它们。
首先,在 EID
扩展中声明任何可能的事件,如下所示
struct Hello: Codable {
let firstName, lastName: String
}
struct Bye: Codable {
let firstName, lastName: String
}
extension EID {
static var hello: EID<Hello> { .init("hello") }
static var bye: EID<Bye> { .init("bye") }
// Use `EID<Nothing>` if you don't want any payload
}
然后创建您的自定义可绑定观察者类
class MyBindableWebsocket: BindableObserver {
// register all EIDs here
override func setup() {
bind(.hello, hello)
bind(.bye, bye)
// optionally setup here custom encoder/decoder
encoder = JSONEncoder() // e.g. with custom `dateEncodingStrategy`
decoder = JSONDecoder() // e.g. with custom `dateDecodingStrategy`
}
// hello EID handler
func hello(client: AnyClient, payload: Hello) {
print("Hello \(payload.firstName) \(payload.lastName)")
}
// bye EID handler
func bye(client: AnyClient, payload: Bye) {
print("Bye \(payload.firstName) \(payload.lastName)")
}
}
声明一个 WSID
extension WSID {
static var myBindable: WSID<MyBindableWebsocket> { .init() }
}
然后开始服务它
app.ws.build(.myBindable).at("ws").serve()
💡在这里,您还可以提供自定义编码器/解码器,例如
app.ws.build(.myBindable).at("ws").encoder(JSONEncoder()).encoder(JSONDecoder()).serve()
数据发送通过具有多种方法的 Sendable
协议工作
.send(text: <StringProtocol>) // send message with text
.send(bytes: <[UInt8]>) // send message with bytes
.send(data: <Data>) // send message with binary data
.send(model: <Encodable>) // send message with Encodable model
.send(model: <Encodable>, encoder: Encoder)
.send(event: <EID>) // send bindable event
.send(event: <EID>, payload: T?)
所有这些方法都返回
EventLoopFuture<Void>
使用上面列出的方法,您可以将消息发送到一个或多个客户端。
client.send(...)
client.broadcast.send(...)
client.broadcast.exclude(client).send(...) // excluding himself
req.ws(.mywsid).broadcast.send(...)
client.broadcast.channels("news", "updates").send(...)
req.ws(.mywsid).broadcast.channels("news", "updates").send(...)
例如,您想查找当前用户的所有 ws 连接,以便向其所有设备发送消息
req.ws(.mywsid).broadcast.filter { client in
req.headers[.authorization].first == client.originalRequest.headers[.authorization].first
}.send(...)
您可以在 app.ws.observer(.mywsid)
或 req.ws(.mywsid).broadcast
或 client.broadcast
上访问 broadcast
对象。
此对象是一个构建器,因此使用它您应该像这样过滤收件人 client.broadcast.one(...).two(...).three(...).send()
可用方法
.encoder(Encoder) // set custom data encoder
.exclude([AnyClient]) // exclude provided clients from clients
.filter((AnyClient) -> Bool) // filter clients by closure result
.channels([String]) // filter clients by provided channels
.subscribe([String]) // subscribe filtered clients to channels
.unsubscribe([String]) // unsubscribe filtered clients from channels
.disconnect() // disconnect filtered clients
.send(...) // send message to filtered clients
.count // number of filtered clients
client.subscribe(to: ..., on: eventLoop) // will subscribe client to provided channels
要订阅 news
和 updates
,请像这样调用它 client.subscribe(to: "news", "updates")
client.unsubscribe(from: ..., on: eventLoop) // will unsubscribe client from provided channels
client.channels // will return a list of client channels
如果您的应用程序中只有一个观察者,您可以将其设置为默认值。它将使您能够使用它而无需一直提供其 WSID,因此您只需调用 req.ws()
而不是 req.ws(.mywsid)
。
// configure.swift
app.ws.setDefault(.myBindable)
您还可以为所有观察者设置自定义编码器/解码器
// configure.swift
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
app.ws.encoder = encoder
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
app.ws.decoder = decoder
正如您在每个处理程序中可能看到的那样,您始终拥有 client
对象。此对象符合 AnyClient
协议,该协议包含内部有用的内容
变量
id
- UUIDoriginalRequest
- 原始 Request
eventLoop
- 下一个 EventLoop
application
- 指向 Application
的指针channels
- 客户端订阅的频道数组logger
- 指向 Logger
的指针observer
- 此客户端的观察者sockets
- 客户端的原始套接字连接exchangeMode
- 客户端的观察者交换模式一致性
Sendable
- 因此您可以使用 .send(...)
Subscribable
- 因此您可以使用 .subscribe(...)
, .unsubscribe(...)
Disconnectable
- 因此您可以调用 .disconnect()
来断开该用户的连接原始请求使您能够例如确定连接的用户
let user = try client.originalRequest.requireAuthenticated(User.self)
请查看 此 Gist 示例
您可以从 iOS13 开始使用纯 URLSession
websockets 功能,或者例如您可以使用我的 CodyFire lib 或经典的 Starscream lib
使用任何支持纯 websockets 协议的 lib,例如不是 SocketIO,因为它使用自己的协议。
遗憾的是,目前还没有 Vapor 4 的示例。
请随时在 Vapor 的 Discord 中联系我,我的昵称是 iMike#3049
欢迎大家贡献!