Historically Web Servers respond to requests sent to them using HTTP. These requests are sent over a TCP/IP socket. The response to one of these HTTP requests is sent back to the client using the same TCP/IP socket on which the request was sent. Fundamentally HTTP enables the client to send requests to the server and get a response for each and every request.

There are applications in which it is desirable to send information or messages from the server to the client whenever such information or messages are available. Such applications include various sorts of dashboards and messaging applications.

HTTP does not enable the server to push information to the client as no connection is kept open. There are capabilities in HTTP 1.1 for what is called keep alive, which keeps the socket open for subsequent requests for a period of time. But fundamentally this is really a performance optimization, as it saves the time it takes to create a new TCP/IP connection between the client and the server. Keep alive does not enable the server to push information to the client.

One solution to this problem is to poll the server. That is, at some rate continuously issue requests to the server for updates. However, polling in general is very inefficient, as you are constantly hitting the server with requests. This occurs even when there may be no updates for the client. Furthermore, the more important it is to know about the updates, the higher the polling frequency will often be, making the situation even worse, when there are no updates.

A partial solution to the problems with polling is to do what is called long polling. In long polling, the client still sends requests to which the server responds. The difference being that if there are no updates for the client, instead of sending a response immediately, the server leaves the request dangling until there is an update to send or a timeout of some kind occurs. The client upon receiving the response typically issues another long poll request immediately and the cycle continues. Long polling solves the problem of the number of requests hitting the server to get updates in a timely fashion. However, it is much more complicated to write a server that handles such requests.

WebSockets

WebSockets is a standard protocol from the IETF that enables true two way communications between a client and a Web server over TCP/IP. A WebSocket connection is between the client that opened it and a service, running in a WebServer. Typically many clients connect to a single service running in a WebServer.

This can be seen in the following diagram:

websocket-server-and-client

Every WebSocket connection starts out a standard HTTP GET request which then gets upgraded to a WebSocket connection. The URL path that is sent in the upgrade request is used to identify the WebSocket service that the client wants to connect to.

The WebSocket protocol enables sending and receiving of both binary and text, UTF-8 encoded, messages. In addition, there are control messages that can be sent, including ping, to determine if the other side is still functioning, and close, to close a connection gracefully for a variety of reasons. All of these messages and control messages can be sent by either the client or the WebSocket service at any time once the connection is opened.

Kitura-WebSocket

Kitura-WebSocket has been written to enable Kitura based servers to host WebSocket services in a pure Swift fashion. It provides a set of APIs to define a WebSocket service which can receive messages from clients attached to it. Additionally a variety of events related to these clients are also provided to the service. Kitura-WebSocket exploits capabilities of Kitura-net for performing asynchronous I/O on the connected sockets as well as managing the upgrade process used to “upgrade” a socket from a regular HTTP connection to a WebSocket.

Compatibility

Kitura-WebSocket has been tested in its role of the server side of WebSocket connections with a variety of clients. They include:

  • The NodeJS websocket package
  • A variety of browsers including:
    • Chrome
    • FireFox
    • Safari

Writing a simple echo server

To show what Kitura-WebSocket can do, here is a very simple example, an echo server. This server simply echos back any messages it receives.

First set up the directory structure by running:

mkdir EchoServer
cd EchoServer
swift package init
rm -f Sources/EchoServer.swift
mkdir Sources/EchoServer

The Package.swift file

Edit the Package.swift file and make its contents similar to the following:

import PackageDescription

let package = Package(
    name: "EchoServer",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 5),
        .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1, minor: 5),
        .Package(url: "https://github.com/IBM-Swift/Kitura-WebSocket", majorVersion: 0, minor: 5)
    ]
)

The main.swift file

In the directory Sources/EchoServer create the file main.swift and start editing it.

Let’s start by adding the ordinary stuff:

import Foundation

import Kitura
import KituraWebSocket

import HeliumLogger

// Using an implementation for a Logger
HeliumLogger.use(.info)

let router = Router()

We’ve imported the packages we’ll use in main.swift, setup the HeliumLogger to be the logger, and created the Router object.

Next we’ll register our WebSocketService implementation, an instance of the EchoService class, with Kitura-WebSocket on the path “kitura-echo”:

WebSocket.register(service: EchoService(), onPath: "kitura-echo")

Next we’ll add code to figure out what port the server should run on. This is needed if you want to do a cloud deployment.

// Figure out what port we should listen on
let envVars = ProcessInfo.processInfo.environment
let portString = envVars["PORT"] ?? envVars["CF_INSTANCE_PORT"] ??  envVars["VCAP_APP_PORT"] ?? "8090"
let port = Int(portString) ?? 8090

Lastly we’ll create the HTTP Server instance, and start the server running:

// Add HTTP Server to listen on the appropriate port
Kitura.addHTTPServer(onPort: port, with: router)

// Start the framework - the servers added until now will start listening
Kitura.run()

The EchoService class

Now we’ll write the WebSocketService protocol implementation for our very simple echo server.

In the directory Sources/EchoServer create the file EchoService.swift and start editing it.

Let’s start by adding the ordinary stuff:

import Foundation

import KituraWebSocket
import LoggerAPI

Then we’ll add the WebSocketService implementation:


class EchoService: WebSocketService {

    public func connected(connection: WebSocketConnection) {}

    public func disconnected(connection: WebSocketConnection, reason: WebSocketCloseReasonCode) {}

    public func received(message: Data, from: WebSocketConnection) {
        from.send(message: message)
    }

    public func received(message: String, from: WebSocketConnection) {
        Log.info("Got message \(message)... sending it back")
        from.send(message: message)
    }
}

The WebSocketService implementation implements four functions:

  1. connected, which indicates that a client has connected to the server.
    • In this example, we do nothing here as we aren’t tracking the connections of the server’s clients in any fashion.
  2. disconnected, which indicates that a client has disconnected from the server.
    • In this example, we do nothing here as we aren’t tracking the connections of the server’s clients in any fashion.
  3. received, with a message parameter of type Data, which indicates that a client has sent a binary message.
    • Here we simply send the message back to the client.
  4. received, with a message parameter of type String, which indicates that a client has sent a text message.
    • Here we simply send the message back to the client.

Running the example server

To run the example server:

  1. Build the server by running from the root EchoServer directory:
    swift build
    
  2. Run the server by running from the root EchoServer directory:
    .build/debug/EchoServer
    

A client for the Echo Server

A server without a client isn’t very useful. We’ll use a web based client, to demonstrate compatibility:

  1. Open a browser and go to websocket.org’s Echo Test.
  2. Connect to your running example server by:
    1. Entering in the Location field:
      ws://localhost:port/kitura-echo
      

      Where:

      • The port is the port the EchoServer is listening on.
    2. Clicking the Connect button.
  3. Send messages by:
    1. Entering the text of the message to be sent in the Message field.
    2. Clicking the Send button.

    You can send many messages by repeating the above two steps.

When you do this, you should see something like this in your browser:

websocket.org Echo Test

You will see something like this in the terminal in which you are running the server:

.build/debug/EchoServer
[2017-01-08T11:01:28.263+02:00] [INFO] [HTTPServer.swift:85 listen(on:)] Listening on port 8090
[2017-01-08T11:02:41.790+02:00] [INFO] [EchoService.swift:17 received(message:from:)] Got message Rock it with HTML5 WebSocket... sending it back

An overview of the Kitura-WebSocket API

The Kitura-WebSocket API reflects the diagram above and provides:

  • A class, WebSocketConnection, reflecting connections of WebSocket clients.
  • A protocol, WebSocketService, implemented by WebSocket services.

In addition, there is:

  • A class, WebSocket, used to register WebSocketService implementations.
  • An enum, WebSocketCloseReasonCode, that contains the various reason codes associated with connections disconnecting.

WebSocketConnection

The WebSocketConnection class provides a set of functions for:

  • Sending binary messages to the client: WebSocketConnection.send(message: Data)
  • Sending text messages to the client: WebSocketConnection.send(message: String)
  • Pinging the client: WebSocketConnection.ping(withMessage: String?=nil)
  • Closing the connection gracefully: WebSocketConnection.close(reason: WebSocketCloseReasonCode?=nil, description: String?=nil)
    • The reason parameter enables you to optionally send one of the standard connection close reason codes.
    • The description parameter enables you add a textual description as to why the the socket was closed.
  • Closing the connection forcefully: WebSocketConnection.drop(reason: WebSocketCloseReasonCode?=nil, description: String?=nil)
    • The reason parameter enables you to optionally send one of the standard connection close reason codes.
    • The description parameter enables you add a textual description as to why the the socket was closed.

In addition, a globally unique identifier is provided WebSocketConnection.id.

WebSocketService

The WebSocketService protocol is implemented by WebSocket services to enable Kitura-WebSocket to send the service events when:

  • Clients connect to the service: WebSocketService.connected(connection: WebSocketConnection)
  • Clients disconnect from the service: WebSocketService.disconnected(connection: WebSocketConnection, reason: WebSocketCloseReasonCode)
    • The reason parameter indicates why the connection was disconnected, using one of the standard connection close reason codes.
  • A client sent a binary message: WebSocketService.received(message: Data, from: WebSocketConnection)
  • A client sent a text message: WebSocketService.received(message: String, from: WebSocketConnection)

It should be noted that:

  • Each one of these events includes the WebSocketConnection object representing the connection of the client that connected, disconnected, or sent a message.
  • There is a single instance of each class that implements the WebSocketService protocol. All clients connecting to a particular WebSocket service will be connected to the same WebSocketService implementation instance. Any function of that instance may be invoked on many threads at the same time.
  • In general, messages sent via the WebSocket protocol can be broken up into frames. The Kitura-WebSocket stack assembles messages from the various frames sent before presenting the messages to the WebSocketService.
  • Pings do not get to the WebSocketService. Instead the Kitura-WebSocket stack sends automatically the appropriate Pong response to received Pings.

WebSocket

The WebSocket class has a static function used to register WebSocket services, instances of classes that have implemented the WebSocketService protocol, on their path identifier. That function is WebSocket.register(service: WebSocketService, onPath path: String).

Next steps

Now you should be able to extend your Kitura based servers with the ability to carry out fully asynchronous bi-directional I/O with a variety of clients, using Kitura-WebSocket.

You can look at Kitura-Chat-Server, a sample application that uses Kitura-WebSocket to create a chat server with a Web based User Interface.

You can read the follow-on blog article Writing a WebSocket based chat server using Kitura

You now have an additional communications tool in your toolbox, in addition to Kitura’s HTTP support. With it I hope your Kitura based servers will be better than they were before.

4 Comments on "Working with WebSockets in a Kitura based server"

  1. First of all, thank you for providing WebSocket support with Kitura, a much needed feature.

    However, I find the design of the API rather strange.
    Why don’t you provide an API similar to expressjs or Vapor.
    Why don’t you reuse router to register the web service?

    • Shmuel Kallner February 21, 2017

      George. Your welcome.

      As for why I didn’t use the Router, is mostly because the Router is about processing HTTP requests, which have a very synchronous Request/Response pattern.
      WebSockets on the other hand is all about asynchronous bi-directional I/O. Messages can come to the server and be sent by the server at any time, with no correlation between messages. Unlike NodeJS where the upgrade process is handled in websocket, here the upgrade is handled by KituraNet with the registration of protocols that can be upgraded to. The registration adds a factory class that provides the piece of code that interprets the bytes as they come in. In the case of WebSockets that code takes the incoming bytes breaks them up into frames and eventually into messages. It is to this factory class the WebSocketService classes are registered. It simply hooks new incoming WebSocketConnections to the appropriate WebSocketService. Once a connection is hooked up the appropriate service the messages flow directly without any need of routing.

      The goal is to be able to handle other protocols in a similar way. I would assume that for each upgraded protocol, the APIs provided might be different.

  2. Hi , I really appreciate the IBM efforts for Swift on server side. Thank you very much.

Join The Discussion

Your email address will not be published. Required fields are marked *