You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 05:24:26