Swift WebSocket连接传参问题:iOS客户端与Vapor服务端
在WebSocket连接阶段传递用户参数的两种方案
要在WebSocket握手阶段就把username和userID传给服务端,不用等到后续发消息,推荐以下两种实现方式:
方案1:URL查询参数
直接把参数拼在WebSocket连接URL的查询串里,服务端在握手时从请求中提取。
iOS客户端修改
用URLComponents安全构建带查询参数的URL,避免特殊字符编码问题:
private var webSocketTask: URLSessionWebSocketTask? = nil func connect(completion: @escaping ()->() = { }) { guard webSocketTask == nil else { return } self.username = "Name" self.userID = UUID().uuidString // 构建带查询参数的URL var components = URLComponents(string: "ws://localhost:8080/connect")! components.queryItems = [ URLQueryItem(name: "username", value: username), URLQueryItem(name: "userID", value: userID) ] guard let url = components.url else { return } webSocketTask = URLSession.shared.webSocketTask(with: url) webSocketTask?.receive(completionHandler: onReceive) webSocketTask?.resume() }
Vapor服务端修改
从请求的查询参数中提取值,同时把用户信息和WebSocket连接关联(用字典替代原Set,方便后续管理):
// 用字典存储连接和对应的用户信息,key为userID var clientConnections: [String: (socket: WebSocket, username: String)] = [:] app.webSocket("connect") { req, client in client.pingInterval = .seconds(10) // 提取查询参数 guard let username = req.query[String.self, at: "username"], let userID = req.query[String.self, at: "userID"] else { // 参数缺失可直接关闭连接 client.close(code: .invalidPayloadData) return } // 存储连接和用户信息 clientConnections[userID] = (socket: client, username: username) client.onClose.whenComplete { _ in clientConnections.removeValue(forKey: userID) } // 原消息处理逻辑可复用,还能直接用已存储的用户信息 client.onText { ws, text in do { guard let data = text.data(using: .utf8) else { return } let incomingMessage = try JSONDecoder().decode(SubmittedChatMessage.self, from: data) // 这里可以直接用之前存储的username和userID,不用从消息里解析 let outgoingMessage = ReceivingChatMessage( message: incomingMessage.message, user: username, userID: userID, gender: incomingMessage.user.gender) let json = try JSONEncoder().encode(outgoingMessage) guard let jsonString = String(data: json, encoding: .utf8) else { return } for (_, connection) in clientConnections { connection.socket.send(jsonString) } } catch { print(error) } } }
方案2:HTTP握手自定义头部
在WebSocket握手的HTTP请求中添加自定义头部,服务端从请求头中提取参数,比URL参数更隐蔽。
iOS客户端修改
创建URLRequest并添加自定义头部,再用该请求创建WebSocket任务:
private var webSocketTask: URLSessionWebSocketTask? = nil func connect(completion: @escaping ()->() = { }) { guard webSocketTask == nil else { return } self.username = "Name" self.userID = UUID().uuidString let url = URL(string: "ws://localhost:8080/connect")! var request = URLRequest(url: url) // 添加自定义头部,前缀用X-避免和标准头部冲突 request.setValue(username, forHTTPHeaderField: "X-Username") request.setValue(userID, forHTTPHeaderField: "X-User-ID") webSocketTask = URLSession.shared.webSocketTask(with: request) webSocketTask?.receive(completionHandler: onReceive) webSocketTask?.resume() }
Vapor服务端修改
从请求头部提取参数,后续逻辑和方案1类似:
var clientConnections: [String: (socket: WebSocket, username: String)] = [:] app.webSocket("connect") { req, client in client.pingInterval = .seconds(10) // 从请求头提取参数 guard let username = req.headers.first(name: "X-Username"), let userID = req.headers.first(name: "X-User-ID") else { client.close(code: .invalidPayloadData) return } clientConnections[userID] = (socket: client, username: username) client.onClose.whenComplete { _ in clientConnections.removeValue(forKey: userID) } // 消息处理逻辑同方案1,可直接用已获取的用户信息 client.onText { ws, text in // ... 原逻辑不变,或替换为存储的用户信息 } }
注意事项
- URL查询参数会出现在服务端日志、代理记录中,适合非敏感信息;
- 自定义HTTP头部要注意部分代理或防火墙可能会过滤非标准头部,需测试兼容性;
- 两种方案都要在服务端做参数校验,避免非法连接。
内容的提问来源于stack exchange,提问作者George Valentine
相关产品推荐
相关产品推荐

