An Introduction to WebSockets
Learn about WebSockets using Swift and Vapor by building a question and answer client and server app. By Jari Koopman.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
An Introduction to WebSockets
30 mins
- Getting Started
- Creating a WebSocket Server
- Testing the WebSocket
- Writing WebSocket Data
- Understanding WebSocket Messages
- Setting up the UI
- Connecting to the Server
- Creating the Data Structure
- Handling the Data
- Setting up the Server Handshake
- Challenge: Extending WebSocketSendOption
- Sending the Questions
- Giving Your WebSocket Connection a Test Run
- Receiving WebSocket Data
- Storing the Data
- Connecting to the Server 2: Connect Harder
- Answering Questions
- Implementing the Answer Button
- Connecting to the Server: With a Vengeance
- Where to Go From Here?
Answering Questions
To answer questions, you’ll use Leaf to create an admin page. Leaf is Vapor’s templating language to create HTML pages.
Open the websocket-backend project and open QuestionsController.swift. Replace index
with the following:
struct QuestionsContext: Encodable {
let questions: [Question]
}
func index(req: Request) throws -> EventLoopFuture<View> {
// 1
Question.query(on: req.db).all().flatMap {
// 2
return req.view.render("questions", QuestionsContext(questions: $0))
}
}
Here’s what this code does:
- Select all
Question
objects from the database. - Returns the leaf view with the questions object set in the context to the questions pulled in step 1 above.
Before you can see the dashboard in action, you need to tell Leaf where to find your templates.
To do this, press Command-Option-R or, if you prefer not to fold your fingers into uncomfortable positions, click websocket-backend in the top-left corner, then click Edit Scheme….
This brings up the Scheme editor. Inside, navigate to the Options tab and locate the Working Directory setting.
Check Use custom working directory and make sure the working directory points to the directory containing your Package.swift. For example: /Users/lotu/Downloads/Conference_Q&A/Starter/websockets-backend.
Now, build and run the server and open http://localhost:8080 in your browser. You’ll see a basic HTML table with three columns: Question, Answered and Answer. Use the iOS app to send in a question and refresh the page.
You’ll now see a new table row with your question, which has false
in the Answered column and an Answer button in the last column.
However, when you click Answer, you get a 404 error at the moment. You’ll fix that next.
Implementing the Answer Button
To get the Answer button working properly, open QuestionsController.swift, find index(_:)
, and add the new route directly below its implementation:
func answer(req: Request) throws -> EventLoopFuture<Response> {
// 1
guard let questionId = req.parameters.get("questionId"),
let questionUid = UUID(questionId) else {
throw Abort(.badRequest)
}
// 2
return Question.find(questionUid, on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { question in
question.answered = true
// 3
return question.save(on: req.db).flatMapThrowing {
// 4
try self.wsController.send(message:
QuestionAnsweredMessage(questionId: question.requireID()),
to: .id(question.askedFrom))
// 5
return req.redirect(to: "/")
}
}
}
This code will:
- Make sure the question ID you’re trying to answer has a valid UUID.
- Try to find the question in the database.
- Set
answered
totrue
and save the question in the database. - Send an update to the client indicating you answered the question.
- Redirect to the index page to show the updated front end.
Finally, you need to register the route in boot
like this:
routes.post(":questionId", "answer", use: answer)
OK, so now it’s time to see if answering questions works as you expect.
Connecting to the Server: With a Vengeance
Yes, more “Die Hard” jokes. :] Before you give answering questions a test run, you need to make sure the iOS app is ready for it.
Once again, open the Conference Q&A Xcode project and open WebSocketController.swift. Add the following code to handleQuestionAnswer(_:)
:
// 1
let response = try decoder.decode(QuestionAnsweredMessage.self, from: data)
DispatchQueue.main.async {
// 2
guard let question = self.questions[response.questionId] else { return }
question.answered = true
self.questions[response.questionId] = question
}
Here’s what’s happening:
- You decode the full message.
- On the main queue, you update the question in the
questions
dictionary. This also updates the UI!.
Now, it’s time to make sure everything works.
Build and run the back-end server and the iOS app. From the app, enter a question and open the dashboard at http://localhost:8080. Click the Answer button and stare in awe at how the iOS app UI updates instantly!
Now as the very last step, take your right hand, move it to your left shoulder and give yourself a firm pat on the back. You just created your very own WebSocket server and client!
Where to Go From Here?
Download the final project using the Download Materials button at the top or bottom of this page.
To learn more about WebSockets, read the WebSocket Protocol RFC, the NIOWebSocket documentation or Vapor’s WebSocket documentation.
If you have any questions or comments, please join the forum discussion below!