Closed ea7kir closed 2 years ago
@ea7kir what's the full console output and description of the error produced?
The Client stalls on wait()
_ = try promise.futureResult.wait()
Thread 1: Fatal error: Error raised at top level: WebSocketKit.WebSocketClient.Error.invalidResponseStatus(HTTPResponseHead { version: HTTP/1.1, status: notFound, headers: [("Date", "Wed, 12 Jan 2022 17:48:52 GMT"), ("Server", "Apache/2.4.51 (Unix)"), ("Content-Length", "196"), ("Content-Type", "text/html; charset=iso-8859-1")] })
Thread 1: Fatal error: Error raised at top level: WebSocketKit.WebSocketClient.Error.invalidResponseStatus(HTTPResponseHead { version: HTTP/1.1, status: notFound, headers: [("Date", "Wed, 12 Jan 2022 17:48:52 GMT"), ("Server", "Apache/2.4.51 (Unix)"), ("Content-Length", "196"), ("Content-Type", "text/html; charset=iso-8859-1")] })
The Console output is...
Swift/ErrorType.swift:200: Fatal error: Error raised at top level: WebSocketKit.WebSocketClient.Error.invalidResponseStatus(HTTPResponseHead { version: HTTP/1.1, status: notFound, headers: [("Date", "Wed, 12 Jan 2022 17:48:52 GMT"), ("Server", "Apache/2.4.51 (Unix)"), ("Content-Length", "196"), ("Content-Type", "text/html; charset=iso-8859-1")] }) 2022-01-12 18:48:52.403021+0100 WsClient[6425:176104] Swift/ErrorType.swift:200: Fatal error: Error raised at top level: WebSocketKit.WebSocketClient.Error.invalidResponseStatus(HTTPResponseHead { version: HTTP/1.1, status: notFound, headers: [("Date", "Wed, 12 Jan 2022 17:48:52 GMT"), ("Server", "Apache/2.4.51 (Unix)"), ("Content-Length", "196"), ("Content-Type", "text/html; charset=iso-8859-1")] }) (lldb)
I hope this helps.
That's a 404 which means the server you're trying to hit doesn't have a route for the url
There are no routes on the server - see my GitHub test code WsClient and WsServer - both running on localhost (same machine).
However, I've just discovered the system will work for WS but not for a WSS connections.
WS now giving giving the console output at the client.
Swift/ErrorType.swift:200: Fatal error: Error raised at top level: NIOSSL.NIOSSLError.handshakeFailed(NIOSSL.BoringSSLError.sslError([Error: 268435703 error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER])) 2022-01-12 19:51:17.819758+0100 WsClient[7057:219945] Swift/ErrorType.swift:200: Fatal error: Error raised at top level: NIOSSL.NIOSSLError.handshakeFailed(NIOSSL.BoringSSLError.sslError([Error: 268435703 error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER])) (lldb)
Does it work with another websocket client? That error means you're trying to connect to a server with a version that Vapor does not support like SSLv3
@0xTim To be clear, both server and client are standalone WebSocket-Kit command-line apps.
But testing with Socket Debugger from the AppStore, I get... WsServer with Socket Debugger client: WS yes, WSS no. WsClient with Socket Debugger server: WS almost, WSS crash.
The code I'm using is adapted from a Medium article by Kristaps Grinbergs. It's the only example I can find. Do the WebSocket-Kit team have an example for standalone usage?
The tests are the best place to start. WSS should work for the client (assuming the server is configured correctly with a valid cert). For the server you'll need something in front (like Vapor) to do the TLS termination or put your own NIOSSL channel handler in front
@0xTim Are saying both WebSocket-Kit and Vapor use an SSL that is incompatible with that provided in Monterey - and most probably most other web servers out there?
It was you who suggested on swift.org that for use cases like mine, it would be better to use WebSocket-Kit in standalone instead of SwiftNIO.
The only examples for any type of networking solution are half-baked by bedroom iOS "experts". I don't pretend to be an expert, I just need a decent example of something that actually works.
What do you mean exactly by "an SSL provided by Monterey"? macOS doesn't provide any TLS/SSL certs, you need to generate them for yourself and then set up your web server to serve them when an HTTPS connection is received.
Vapor and NIO support all modern valid TLS certificates (1.1-1.3) both as a server and a client
The thing is; I can stream from an external https://wws.somedomain/fft (without knowing anything about SSl certificates) using a URLSession client, but not with a WebSocket-Kit client. I'm also trying to build my own server to do something similar. I have a non-SSL server for WS working, but not with WSS - so I'm stuck.
As you can guess, I have very little knowledge at this level.
Let's start with the client then. You can connect to a URL with URLSession client. I'm assuming you're using URLSessionWebSocketTask
? What does your code for both look like?
This works...
//
// WsClient.swift
// WebSocketURLSession
//
// Created by Michael Naylor on 12/01/2022.
//
// derived from https://kristaps.me/blog/websockets-ios-13-swift/
import Foundation
protocol WsNetworkDelegate: AnyObject {
func dateArrived(data: Data)
}
final class WsNetwork {
var webSocketTask: URLSessionWebSocketTask?
var urlSession: URLSession?
weak var delegate: WsNetworkDelegate?
// this works with wss://somedomain/fft - I'm reluctant to make the real domain public
func open(urlStr: String, delegate: WsNetworkDelegate) {
self.delegate = delegate
let url = URL(string: urlStr)!
urlSession = URLSession(configuration: .ephemeral)
webSocketTask = urlSession?.webSocketTask(with: url)
webSocketTask?.resume()
}
func sendPing() {
webSocketTask?.sendPing { (error) in
if let error = error {
print("Sending PING failed: \(error)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.sendPing()
}
}
}
func close() {
print("webSocketTask?.cancel")
// no idea which, if any, of these actually work
webSocketTask?.cancel(with: .goingAway, reason: nil)
// urlSession?.invalidateAndCancel()
// print("urlSession?.invalidateAndCancel")
print("DONE closing")
}
func sendSingle() {
let message = URLSessionWebSocketTask.Message.string("Hello World")
webSocketTask?.send(message) { error in
if let error = error {
print("WebSocket couldn’t send message because: \(error)")
}
}
}
func receive() {
webSocketTask?.receive { result in
switch result {
case .failure(let error):
print("Error in receiving message: \(error)")
case .success(let message):
switch message {
case .string(let text):
print("Received string: \(text)")
case .data(let data):
print("Received data: \(data)")
@unknown default:
print("unused websocket message: \(message)")
fatalError()
}
}
}
}
// so far, I'm only testing this one
func listen() {
webSocketTask?.receive { result in
switch result {
case .failure(let error):
print("Error in receiving message: \(error)")
case .success(let message):
switch message {
case .string(let text):
print("Received string: \(text)")
case .data(let data):
// DispatchQueue.main.async {
// print("Received data: \(data)")
self.delegate!.dateArrived(data: data)
// }
@unknown default:
print("unused websocket message: \(message)")
fatalError()
}
self.listen()
}
}
}
deinit {
// i'd like to call close() here, it dinit never gets called
print("deinit WsClient")
}
}
What about the WebsocketKit version?
import Foundation
import WebSocketKit
import NIOPosix
import NIOHTTP1
import NIOWebSocket
print("Server is starting...")
// Setting host to domain, IP Address or localhost make no difference.
let host = "localhost"
let port = 9999
// create the event loop groop
var eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
// do the the GET dance
let upgradePipelineHandler: ( Channel, HTTPRequestHead ) ->
EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket.")
ws.onText { ws, string in
print("Received: \(string)")
ws.send("The server got \(string)")
}
ws.onClose.whenSuccess { value in
print("onClose.")
}
}
}
print("Get dance completed.")
// WebSock is upgraded, so create promise in which to receive the events
let promise = eventLoopGroup.next().makePromise(of: String.self)
let server = try ServerBootstrap(group: eventLoopGroup).childChannelInitializer { channel in
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, reg in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: host, port: port).wait()
print("Server is listening on \(host):\(port)")
_ = try promise.futureResult.wait()
try server.close(mode: .all).wait()
The client - we're trying to fix the client problem first
Sorry
import Foundation
import WebSocketKit
import NIOPosix
// Setting host to domain, IP Address or localhost make no difference.
let host = "ws://localhost"
let port = 9999
let url = String("\(host):\(port)")
// create the event loop groop
var eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let promise = eventLoopGroup.next().makePromise(of: String.self)
WebSocket.connect(to: url, on: eventLoopGroup) { ws in
ws.send("Hello.")
ws.onText { ws, string in
print(string)
}
}.cascadeFailure(to: promise)
// This line bombs out with "Thread 1: Fatal error: blah, blah, blah..."
_ = try promise.futureResult.wait()
What's the full error output. Please provide as much information as possible, it's really hard to remotely debug issues without all the details
The errors linked above for the client are invalid certificate and 404, which one was it?
Tim, thanks for trying to help me, but I'm lost. Both client and server are here for you run run...
https://github.com/ea7kir/WsClient
https://github.com/ea7kir/WsServer
Please can play with them and figure out why they don't work?
When the client asks for ws.localhost.local
it works.
When the client asks for wss.localhost.local
it halts with this console output.
Swift/ErrorType.swift:200: Fatal error: Error raised at top level: NIOSSL.NIOSSLError.handshakeFailed(NIOSSL.BoringSSLError.sslError([Error: 268435703 error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER]))
2022-01-18 15:18:45.930354+0100 WsClient[8067:158760] Swift/ErrorType.swift:200: Fatal error: Error raised at top level: NIOSSL.NIOSSLError.handshakeFailed(NIOSSL.BoringSSLError.sslError([Error: 268435703 error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER]))
(lldb)
PS: Perhaps your Package.swift needs updating. For example, doesn't sift-no now include some of the product names you're asking for?
It also seems odd that I need to import anything more than WeSocketKit.
Whatever, Xcode is resolving...
swift-nio 2.36.0 swift-nio-ssl 2.17.1 websocket-kit 2.3.0
Also, your websocket-kit package is set for swift 5.2 and macOS(.v10_15) and things have moved on since then.
This works for me
let host = "wss://ws.ifelse.io"
let port = 443
let url = String("\(host):\(port)")
// create the event loop groop
var eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let promise = eventLoopGroup.next().makePromise(of: String.self)
WebSocket.connect(to: url, on: eventLoopGroup) { ws in
ws.send("Hello.")
ws.onText { ws, string in
print(string)
}
}.cascadeFailure(to: promise)
// This line bombs out with "Thread 1: Fatal error: blah, blah, blah..."
_ = try promise.futureResult.wait()
The server you're connecting to must have an old or invalid certificate
So is kit client incompatible with the kit server?
WebsocketKit's server does not support WSS (or TLS) by default. You need to add a TLS channel handler like https://github.com/vapor/vapor/blob/main/Sources/Vapor/HTTP/Server/HTTPServer.swift#330 to make it work and configure it with a valid certificate. Though at that point it's probably just easier to use Vapor anyway
Finally, we now know WebSocket-Kit can't be used standalone - as advertised. When I started my big project 6 months ago, I figured Vapor would be overkill and would probably be more trouble that it's worth. My project is a real time client/server application, running on a Raspberry Pi with dozens of relays, sensors and two more Pi s over UDP. It is definitely not a typical iOS app. So, it's back to the drawing board. Why networking is so difficult after nearly half a century is beyond me. Anyway, thanks for your help.
Just to be clear, it does work as described. WebsocketKit is a low level websocket library to enable you to create WebSocket clients and servers. It is not intended to be a simple, easy to use library that you can configure in one line. If you want that you should use something like Vapor. It's the same as building a low level NIO web server - it you want to be able to serve HTTPS traffic, or WSS traffic in this case then you need to set up, configure and provide the TLS channel handler yourself. WebsocketKit deals with the WebSocket protocol, TLS is out of scope. However, if you need to do it, you can take the few lines linked above, integrate it with a few tweaks and it should work
Probable Cause
Lack of examples and documentation causes bad programming.
To Reproduce
Please try my 2 very small Swift packages, WsClient and WsServer, at GitHub.com/ea7kir
The server runs, but the client bombs out with a Fatal error.
Environment
WebSocket-Kit. STANDALONE (ie without Vapor) macOS 12.1, Xcode 13.2.1, Swift 5.5.2