Socket Programming using Vapor and URLSessionWebSocketTask

Socket programming in Swift using Vapor WebSocket and URLSessionWebSocketTask

In this post, I’ll share how to build socket application.

Project Structure

I created a workspace and put the Client (SwiftUI) and Server (Vapor)

screenshot 2024 07 02 at 2.23.38e280afpm
screenshot 2024 07 02 at 2.26.00e280afpm

Server (Vapor)

Server code is very simple. It is only 32 lines of code.

import Vapor
func routes(_ app: Application) throws {
//Endpoint - ws://127.0.0.1:8080/echo
app.webSocket("echo") { req, ws in
// Connected WebSocket.
//Send Ping to Client
ws.sendPing()
ws.onPong { ws, data in
// Pong was received from client
print("Connection alive, Received Pong")
}
ws.onText { ws, text in
// String received by this WebSocket.
ws.send("👋 Sent Message: \(text) from Server")
}
ws.onBinary { ws, binary in
// [UInt8] received by this WebSocket.
print(binary)
ws.send("Received Data: \(binary.readableBytes)")
}
ws.onPing { ws, data in
// Ping was received.
print("Received Ping")
}
}
}

Client (SwiftUI)

I used URLSessionWebSocketTask.

Here is viewModel logic. This class is @Observale and inherent from NSObject to handle URLSessionWebSocketDelegate.

import SwiftUI
import Foundation
@Observable class ContentViewModel: NSObject {
var message: String = ""
var received: String = ""
private var session: URLSession!
private var socketTask: URLSessionWebSocketTask!
override init() {
super.init()
self.session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
self.socketTask = session.webSocketTask(
with: URL(string: "ws://127.0.0.1:8080/echo")!
)
self.socketTask.resume()
}
func ping() {
socketTask.sendPing { error in
if let error {
print("ping failed: \(error.localizedDescription)")
}
else {
print("pong received")
}
}
}
func send() async {
try? await socketTask.send(.string(message))
}
func receive() async {
while let message = try? await socketTask.receive() {
switch message {
case let .string(receivedMessage):
print("Received Message from Server: \(receivedMessage)")
received = receivedMessage
default:
break
}
}
}
func disconnect() {
socketTask.cancel(with: .goingAway, reason: nil)
}
}
extension ContentViewModel: URLSessionWebSocketDelegate {
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
print("Connected")
Task {
await receive()
}
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
print("Disconnected: \(closeCode)")
}
}

SwiftUI Code

import SwiftUI
struct ContentView: View {
@State private var viewModel = ContentViewModel()
var body: some View {
VStack {
Text(viewModel.received)
Divider()
HStack {
TextField(text: $viewModel.message) {
Text("Message")
}
Button("Send") {
Task {
await viewModel.send()
}
}.padding(4)
.background(.blue)
.foregroundColor(.white)
}
Divider()
Button("Ping") {
viewModel.ping()
}.frame(
maxWidth: .infinity,
maxHeight: 30
).background(.teal)
.foregroundColor(.white)
}
.padding()
}
}
#Preview {
ContentView()
}

Let’s run a simple socket application

Both client and server can send a ping. When received the ping message then receiver send a pong message automatically. It’s a way to check status of connections.

screenshot 2024 07 02 at 2.20.50e280afpm
screenshot 2024 07 02 at 2.20.10e280afpm

If you want to more details about WebSocket, check my post

Comments

Leave a Reply

Discover more from Shawn

Subscribe now to keep reading and get access to the full archive.

Continue reading