An Introduction to WebSockets

Learn about WebSockets using Swift and Vapor by building a question and answer client and server app. By Jari Koopman.

4 (22) · 6 Reviews

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

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:

  1. Select all Question objects from the database.
  2. 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.

Custom working directory

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:

  1. Make sure the question ID you’re trying to answer has a valid UUID.
  2. Try to find the question in the database.
  3. Set answered to true and save the question in the database.
  4. Send an update to the client indicating you answered the question.
  5. 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:

  1. You decode the full message.
  2. 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!