Leave a rating/review
WebSockets are a way to create bilateral communication between a web browser and a server. This allows you to send and receive data in a single stream.
WebSockets allowed direct communication with a web page. With URLSession, we can use WebSockets as well. This requires that you make a WebSocket connection and then you can read and write to it.
You’ll need to create a server that supports WebSockets, but thankfully, Vapor comes to the rescue.
When a server has a socket connection to a client, that’s 1-on-1 communication. In your case, the server just echoed what it received.
If another client makes a connection to the server, the client will have its own connection. What this means is: if connection A echoes a response to client A, this communication is just between them.
In order for Client B to be part of the response to client A, the response needs to be persisted and then sent to all the clients. To play around with a fully functional chat server, check out chat-example-3 over at github with the url on the slide.
if you’re curious about a good way to organize your server for this, check out the following link: https://github.com/vzsg/chat-example-3
Time to show you how to work with WebSockets. The first thing you’ll do is create a new Vapor application and get that local server instance up and running.
Open the Terminal application and change directories to the Desktop:
cd ~/Desktop
Then create a new Vapor application:
vapor new WebSocketServer
Type n if you are asked about wanting to use Fluent. If asked whether you want to use Leaf, type n as well. With the Vapor application created, change into its directory with the following:
cd WebSocketServer
And open the project in Xcode with the following:
vapor xcode -y
Open routes.swift and add the following:
func routes(_ app: Application) throws {
app.webSocket("chat") { request, ws in
ws.send("Connected")
ws.onText { ws, text in
ws.send("Text Received: \(text)")
print("received from client: \(text)")
}
ws.onClose.whenComplete { result in
switch result {
case .success():
print("Closed")
case .failure(let error):
print("Error: \(error)")
}
}
}
}
This creates a new WebSocket server along with a route called chat. What this simply WebSocket server will do is receive your message, send it back within a custom string message, as well as print the message received to the console.
That’s all there is to it! Build and run the server code. Then switch to this episode’s Starter project.
This project is the same one you’ve been working on, but it adds a new tab called Support where users will be able to message a help agent live, in real-time.
Feel free to look at the code updates to the project, but it’s more SwiftUI code to set up the UI and prepare things for your upcoming work with WebSockets.
Inside SupportView.swift add a property declaration for URLSessionWebSocketTask:
@State private var webSocketTask: URLSessionWebSocketTask!
URLSession provides different types of tasks, this is the one you want since you’re working with WebSockets.
Next, add a method to set up your task:
func setUpSocket() {
let webSocketURL = URL(string: "ws://localhost:8080/chat")!
webSocketTask = URLSession.shared.webSocketTask(with: webSocketURL)
listenForMessages()
webSocketTask.resume()
}
The URL for the socket is your localhost, for your server istance that is now running. You then call a method on the shared URLSession in order to get back a web socket task.
You call a method to listen for messages, that you’ll write next. And then you resume the task in order to get things started. Time to write the listenForMessages method:
func listenForMessages() {
webSocketTask.receive { result in
switch result {
case .failure(let error):
print("Failed to receive message: \(error)")
case .success(let message):
switch message {
case .string(let text):
messages.insert(text, at: 0)
case .data(let data):
print("Received binary message: \(data)")
@unknown default:
fatalError()
}
listenForMessages()
}
}
}
You call the receive method on the task and, within the completion handler, call a switch statement on the result. In case of failure you print out a message to the console for now, and in case of success you add another switch statement for the message itself.
If the message is a string you insert it as the first message of the messages array. If it’s data you print it out to the console, and for all other cases you call fatalError() for now.
Finally, and of importance, you once again call listenForMessages if the result was successful otherwise this method gets called once and never again, ignoring more possible information sent to your app from the socket.
Because sockets are an open stream, you need to close it when you are done. Add the folowing closetSocket method:
func closeSocket() {
webSocketTask.cancel(with: .goingAway, reason: nil)
messages = []
}
This cancels the socket with the close code, and a nil reason for closing the connection. You also set the messages array to an empty array so any future interactions begin with an empty list of messages.
Next, you need to add a method to take care of sending a user’s message from your app to the server:
func sendMessageTapped() {
let message = URLSessionWebSocketTask.Message.string(self.chatMessage)
webSocketTask.send(message) { error in
if let error = error {
print(error.localizedDescription)
}
}
}
This creates a string message and uses your socket task to send it. If an error occurs you print the error to the console.
To actually send the message, update the Send button code to call your new method as its action:
Button("Send", action: sendMessageTapped)
.padding(.trailing)
Then add the following modifiers to your VStack:
.onAppear(perform: setUpSocket)
.onDisappear(perform: closeSocket)
This will open the socket when your view appears, and close it when it disappears.
Build and run the application. Navigate to the Support tab and send a message. Check out the console for the iOS app with your message returned to you. Now look at the Vapor project’s console. Yaaay! Great work.
To demonstrate how you can also connect to your server from other clients, open Safari.
Make sure the Develop menu is enabled by going, in your Menu Bar, to Safari > Preferences > Advanced. Open a new Safari window and navigate to localhost:8080 In the Menu Bar, click Develop > Show JavaScript Console. Type the following command to create a new JavaScript WebSocket:
ws = new WebSocket("ws://localhost:8080/chat")
Then a handler to print out the response from your server:
ws.onmessage = function(e) { console.log("from server: " + e.data) }
And finally, send a message:
ws.send("Hello from Safari")
If you open the Vapor app’s Xcode project you will see that it received this message as well. Your iOS app, however, should not have received the browser’s message.
Fantastic work! Sockets are a topic that can oftentimes seem daunting when in reality there are great built-in APIs to take care of the heavy lifting for you.
Sockets are also great for when you need to have a more real-time way of communicating between your app and a server, between multiple clients, or simply when you want to keep a two-way channel of communication open as opposed to just sending or receiving data as individual items.
I’ll see you in the next episode in order to conlcude part 1 of this course.