Swift Network.framework WebSocket 握手 nil 返回

Posted

技术标签:

【中文标题】Swift Network.framework WebSocket 握手 nil 返回【英文标题】:Swift Network.framework WebSocket handshake nil returned 【发布时间】:2018-10-08 03:49:37 【问题描述】:

我正在尝试使用新的 Network.framework 连接到 WebSocket,但面临来自服务器的零握手响应。

(是的,我知道红蜘蛛存在,但它不支持代理/移动用户在网络接口之间切换)

我的测试代码:

func beginTest() 

    let connection = NWConnection(host: "echo.websocket.org", port: 443, using: .tls)

    connection.stateUpdateHandler =  state in
        print("State:", state)
        switch state 
        case .ready:
            self.connectionReady(connection)
        default:
            break
        
    
    connection.start(queue: .main)


func connectionReady(_ connection: NWConnection) 

    let raw = """
    GET / HTTP/1.1
    Upgrade: websocket
    Connection: Upgrade
    Host: echo.websocket.org
    Origin: https://echo.websocket.org
    Sec-WebSocket-Key: s04nPqA7M6pQ3Lu2jRJLSQ==
    Sec-WebSocket-Version: 13
    """

    let rawData = raw.appending("\n\n\n").replacingOccurrences(of: "\n", with: "\r\n").data(using: .utf8)

    connection.send(content: rawData!, completion: .idempotent)

    connection.receiveMessage(completion: data, context, bool, error in
        if let data = data 
            print("Received:", String(data: data, encoding: .utf8))
        

        print("Error:", error)

        let hello = "Hello".data(using: .utf8)
        connection.send(content: hello, completion: .idempotent)
    )

这是零响应和连接断开,而不是从服务器获取升级握手响应,下面带有控制台日志:

State: preparing
State: ready
Received: nil
Error: nil
2018-10-08 11:38:57.314885+0800 SwiftNetworkTest[86448:3026660] [] nw_socket_handle_socket_event [C1.1:2] Socket SO_ERROR [54: Connection reset by peer]

谁能指导我如何使用 Apple 新的 Network.framework?将不胜感激!

更新1

对不起,我现在可以看到使用.ascii 编码而不是.utf8 的握手响应。

但我的连接仍然断开Connection reset by peer。升级到 WebSocket 后如何保持连接?

【问题讨论】:

【参考方案1】:

您应该遵循 Websocket 指南如何格式化 Websocket 发送的消息。

我认为这是一个很好的资源。我自己用过。

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

【讨论】:

【参考方案2】:

阅读文档后:https://developer.apple.com/documentation/security/certificate_key_and_trust_services/trust/evaluating_a_trust_and_parsing_the_result

我像这样设置经过身份验证的 TLS 连接:

    init(endpoint: NWEndpoint, interface: NWInterface?, passcode: String, delegate: BitfinexConnectionDelegate)
    
        self.delegate            = delegate
        self.initiatedConnection = false

        let host = "api.bitfinex.com"
        let port = 443
        
        let options = NWProtocolTCP.Options()
        options.connectionTimeout = 15
        
        let tlsOptions = NWProtocolTLS.Options()
        sec_protocol_options_set_verify_block(
            tlsOptions.securityProtocolOptions,
            
                (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
                
                let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
                
                let pinner = FoundationSecurity()
                
                pinner.evaluateTrust(trust: trust, domain: host, completion:
                
                    (state) in
                    
                    switch state
                    
                    case .success:
                        sec_protocol_verify_complete(true)
                    case .failed(_):
                        sec_protocol_verify_complete(false)
                    
                )
            , queue
        )
        
        let parameters = NWParameters(tls: tlsOptions, tcp: options)
        let conn = NWConnection(host: NWEndpoint.Host.name(host, nil),
                                port: NWEndpoint.Port(rawValue: UInt16(port))!,
                                using: parameters
        )
        self.connection = conn
        
        startConnection()
    

FoundationSecurity() 块是一个简单的 TLS 评估

if SecTrustEvaluateWithError(trust, &error)

    completion(.success)

else

    completion(.failed(error))

一旦我的连接准备好。我通过像这样创建的数据对象发送。这取决于您正在与之交互的 API。

    private func prepareWebSocket() throws -> Data
    
        let apiKey    = "API_KEY"
        let apiSecret = "API_SECRET"
        let authNonce = NonceProvider.sharedInstanse.nonce
        let authPayload = "AUTH\(authNonce)"
        
        
        let authenticationKey  = SymmetricKey(data: apiSecret.data(using: .ascii)!)
        let authenticationCode = HMAC<SHA384>.authenticationCode(for: authPayload.data(using: .ascii)!,
                                                                 using: authenticationKey
        )
        
        let authSig = authenticationCode.compactMap  String(format: "%02hhx", $0) .joined()
        
        let payload: [String : Any] =
        [
            "event":       "auth",
            "apiKey" :     apiKey,
            "authSig":     authSig,
            "authPayload": authPayload,
            "authNonce":   authNonce
        ]

        return try JSONSerialization.data(withJSONObject: payload, options: .fragmentsAllowed)
    

【讨论】:

以上是关于Swift Network.framework WebSocket 握手 nil 返回的主要内容,如果未能解决你的问题,请参考以下文章

Network.Framework的iOS后台使用

如何为 Network.framework 编写 NWProtocolFramer,使用分隔符将流拆分为帧?

Case1-basic network framework/Related organization‘s name

CIColorControls & UISlider w/Swift 4 [重复]

在 Swift 中使用特殊字符转义正则表达式

Braintree node.js w/iOS 9 Swift 2.1 - 客户端令牌错误