Upgrade to Pro — share decks privately, control downloads, hide ads and more …

We need to talk about Websockets

We need to talk about Websockets

If you're tired of slow and flaky networking, this talk will explain to you the state-of-the-art solution: Websockets. Using technologies such as Apple's freshly announced URLSession Websockets, incredible new open-source SwiftNIO, and Network.framework, we'll look at how you can open a two-way socket between your app and server to accelerate your networking, allowing you to communicate without disruptions, do so securely, and even learn some tips and tricks from Apple. The speaker is the maintainer of Starscream, one of the most popular Swift Websocket libraries.

Kristaps Grinbergs

May 19, 2020
Tweet

More Decks by Kristaps Grinbergs

Other Decks in Programming

Transcript

  1. “Innovation is taking two things that already exist and putting

    them together in a new way.” - Tom Freston
  2. - Wikipedia WebSocket is a computer communications protocol, providing full-duplex

    communication channels over a single TCP connection.
  3. Connection handshake GET Request GET /chat HTTP/1.1 Host: example.com Upgrade:

    websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: chat, superchat Origin: http://example.com Response HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Prototocol: chat
  4. Starscream initialize let url = URL(string: "http://localhost:8080")! var request =

    URLRequest(url: url) socket = WebSocket(request: request) socket.delegate = self socket.connect()
  5. Starscream receive func didReceive(event: WebSocketEvent, client: WebSocket) { } switch

    event { case .connected(let headers): isConnected = true print("websocket is connected: \(headers)") case .disconnected(let reason, let code): isConnected = false print("websocket is disconnected: \(reason) with code: \(code)") case .text(let string): print("Received text: \(string)") case .binary(let data): print("Received data: \(data.count)") case .ping(_): break case .pong(_): break case .cancelled: isConnected = false case .error(let error): isConnected = false handleError(error) }
  6. Network.framework initialize let connection = NWConnection(host: .init("echo.websocket.org"), port: .https, using:

    .tls) let queue = DispatchQueue(label: "com.network.framework") connection.start(queue: queue)
  7. Network.framework send let message = "Hello".data(using: .utf8)! connection.send(content: message, completion:

    .contentProcessed { error in if let error = error { print("can't process message \(error)") } })
  8. Network.framework receive connection.receiveMessage { data, messageContext, isComplete, error in if

    let error = error { print("Failed to receive message \(error)") } else if let data = data, isComplete { print(String(data: data, encoding: .utf8)!) } } connection.receive(minimumIncompleteLength: 2, maximumLength: 4096) { data, context, isComplete, error in if let error = error { print("error", error) } else if let data = data { print("received: ", String(data: data, encoding: .utf8)!) } if isComplete, context == nil, error == nil { print("END") } }
  9. URLSessionWebSocketTask Initialize let session = URLSession(configuration: .default) let url =

    URL(string: "wss://echo.websocket.org")! let webSocketTask = session.webSocketTask(with: url) webSocketTask.resume()
  10. URLSessionWebSocketTask Receive webSocketTask.receive { result in switch result { case

    .success(let message): switch message { case .data(let data): print("Data received: \(data)") case .string(let text): print("Text received: \(text)") } case .failure(let error): print("Error in receiving \(error)") } }
  11. Client _ = try promise.futureResult.wait() var eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)

    let port: Int = 8709 let promise = eventLoopGroup.next().makePromise(of: String.self) WebSocket.connect(to: "ws://localhost:\(port)", on: eventLoopGroup) { ws in ws.send("hello") ws.onText { ws, string in print(string) } }.cascadeFailure(to: promise)
  12. Server let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel,

    req in WebSocket.server(on: channel) { ws in } } ws.onText { ws, string in print("received") ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed()) } ws.onBinary { ws, buffer in print(buffer) } ws.onClose.whenSuccess { value in print("onClose") }
  13. var eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2) let port = Int.random(in: 8000..<9000)

    let promise = eventLoopGroup.next().makePromise(of: String.self) let server = try ServerBootstrap(group: eventLoopGroup).childChannelInitializer { channel in let webSocket = NIOWebSocketServerUpgrader( shouldUpgrade: { channel, req in return channel.eventLoop.makeSucceededFuture([:]) }, upgradePipelineHandler: upgradePipelineHandler ) return channel.pipeline.configureHTTPServerPipeline( withServerUpgrade: ( upgraders: [webSocket], completionHandler: { ctx in // complete }) ) }.bind(host: "localhost", port: port).wait() _ = try promise.futureResult.wait() try server.close(mode: .all).wait() Server
  14. Vapor WebSockets client let websocket = WebSocket.connect(to: url, on: app.eventLoopGroup)

    { ws in ws.onText { ws, text in print(text) } ws.send("Hello") }
  15. func routes(_ app: Application) throws { app.webSocket("") { request, ws

    in } } Vapor WebSockets server ws.send("You have been connected to WebSockets") ws.onText { ws, string in ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed()) } ws.onClose.whenComplete { result in switch result { case .success(): print("Closed") case .failure(let error): print("Failed to close connection \(error)") } }
  16. Cons Before iOS13 can be buggy ⛓ Long polling can

    be more reliable ⚔ Tricky to debug Closing connection