Category Archives: programming

Development of a Swift Vapor App using WebSockets

I have been working with WebSockets since their release. While I like the idea of a browser based full duplex communication between the client and server one of my favorite interests with WebSockets has been getting multiple browser based apps to communicate with each other in realtime.

In this article I will give an example for a realtime application where users can collaborate on a shared html5 canvas using a jQuery plugin I wrote a few years back. I will be using Swift on the server side with the web framework Vapor.

For more information about the plugin that I developed you can review an article that I wrote for it here.

Requirements

You will of course need to have Swift 3.x and Vapor installed on your local machine and if you haven’t already done this there is a great step by step tutorial you can follow here.

Don’t stop here If you think Swift is only for Apple OS’s because it can actually be run on multiple OS’s and the above instructions will given you an example of setting it up to run on Ubuntu. I wrote this article demonstrating how to setup a Virtual Ubuntu machine for Vapor.

Finally the rest of this post assumes you already have a basic understanding of HTML, Javascript, Swift and Vapor. For those wanting to learn more about Vapor there are some great how to videos and articles to get started here.

Setting up Client

I don’t plan to cover the details of laying out the page but for a quick demonstration of what everything will look you can go here.

Talking with Server

To make communications between the clients simple I will be using JSON objects.

The following code will be used to store and manage the WebSocket communications that are sent and received:

function dooScribConnection(host) {
    var scrib = this;
    scrib.ws = new WebSocket('wss://' + host);
            
    scrib.ws.onopen = function() {
        var connRequest = JSON.stringify({'command': 'connect', 'username': id})
        scrib.ws.send(connRequest)
    };
            
    scrib.ws.onmessage = function(event) {
        var msg = JSON.parse(event.data);
                
        if (msg.command == "click") {
            prevPoint[msg.username] = msg;
        } else if (msg.command == "paint") {
            surface.drawLine(prevPoint[msg.username].X, prevPoint[msg.username].Y, msg.X, msg.Y, msg.color, msg.pen);
            prevPoint[msg.username] = msg;
        } else if (msg.command == "clear") {
            surface.clearSurface();
        }
    };
            
    scrib.send = function(event) {
        scrib.ws.send(event);
    };
};

The connection to the server is established as soon as the WebSocket object is created. The onOpen function is called once that connection is established. By making a call to send information to the server here it will allow the server to store instance information about each of its connections. To make sure that each user is uniquely identified the id var is established using the following:

var id = Math.round($.now()*Math.random());

For messaging to the server I added the send function which will send the JSON objects that are passed as a parameter to it.

Whenever a message is received the onmessage function is called and from there you can switch on the command type (added to the JSON message) and take the appropriate action. Currently the following commands (JSON objects) are of interest for handling inside the browser:

  • click – this is used to establish the starting point for when the user has started drawing.
  • paint – this is used to draw a line from the previous point to the current one.
  • clear – a command that allows users to clear the drawing canvas.

Setting up Canvas (DooScrib plugin)

The following code is for setting up the jquery plugin as soon as the page has been loaded and is ready:

$(document).ready(function(){
    var height = $('#raw() { #surface }').height();
    var width = $('#raw() { #surface }').width();

    dooscrib = new dooScribConnection(window.location.host + "/dooscrib");
    surface = new $('#raw() { #surface }').dooScribPlugin({
        width:width,
        height:400,
        cssClass:'pad',
        penSize:4,
                                                                  
        onMove:function(e) {
            var msg = JSON.stringify({'command': 'mousemove', 'username': id, 'X': e.X, 'Y': e.Y });
            dooscrib.send(msg);
        },
                                                                  
        onClick:function(e) {
            var msg = JSON.stringify({'command': 'click', 'username': id, 'X': e.X, 'Y': e.Y});
            dooscrib.send(msg);
        },
                                                                  
        onPaint:function(e) {
            var msg = JSON.stringify({'command': 'paint', 'username': id, 'X': e.X, 'Y': e.Y,'pen': surface.penSize(),'color':surface.lineColor()});
            dooscrib.send(msg);
        },
                                                                  
        onRelease:function(e) {
            var msg = JSON.stringify({'command': 'release', 'username': id, 'X': e.X, 'Y': e.Y});
            dooscrib.send(msg);
        }
    });
});

The drawing for the current surface is handled by the plugin which then messages everything that it is being done. With each message there is JSON message created which is then sent to the server which will redirect to all the other users.

Setting up Server

For the server implementation I created the following Controller object which handles all of the WebSockets communications as well as routes for the different web pages:

final class ScribController {
    var dooscribs : [String: WebSocket]
    var droplet : Droplet

    init(drop: Droplet) {
        dooscribs = [:]
        droplet = drop

        droplet.get("", handler: scribRequest)
        droplet.get("about", handler: aboutRequest)
        droplet.socket("dooscrib", handler: socketHandler )
    }
    
    func aboutRequest(request: Request) throws -> ResponseRepresentable {
        return try droplet.view.make("about")
    }

    func scribRequest(request: Request) throws -> ResponseRepresentable {
        return try droplet.view.make("socket")
    }
    
    func socketHandler(request: Request, socket: WebSocket) throws {
        var scribUser: String? = nil
        
        // create an active ping to keep connection open
        try background {
            while socket.state == .open {
                try? socket.ping()
                drop.console.wait(seconds: 5)
            }
        }
        
        socket.onText = { socket, message in
            let json = try JSON(bytes: Array(message.utf8))
            
            guard let msgType = json.object?["command"]?.string, let user = json.object?["username"]?.string else {
                return
            }
            
            if msgType.equals(any: "connect") {
                scribUser = user
                self.dooscribs[user] = socket
                
                let response = try JSON(node: [
                    "command":"connected",
                    "username": user
                    ])
                
                // send a connect response to everyone including self
                for (_, connection) in self.dooscribs {
                    try connection.send(response)
                }
            } else if (msgType.equals(any: "clear")) {
                for (_, connection) in self.dooscribs {
                    try connection.send(json)
                }
            } else {
                // send message to everyone (minus self)
                for (scrib, connection) in self.dooscribs {
                    if (!scrib.equals(any: user)) {
                        try connection.send(json)
                    }
                }
            }
        }
        
        socket.onClose = { ws, _, _, _ in
            guard let user = scribUser else {
                return
            }
            
            let disconn = try JSON(node: [
                "command": "disconnect",
                "username": user
                ])
            
            // tell everyone (minus self) about disconnect
            for (remote, connection) in self.dooscribs {
                if (!remote.equals(any: user)) {
                    try connection.send(disconn)
                }
            }
            
            self.dooscribs.removeValue(forKey: user)
        }
    }
}

I won’t be covering the get route handlers that are established in the controller with any great detail. From the code you however you can see that they are sending back the pages the user requested.

Sending JSON Messages

Much to my surprise the WebSocket class does not have a function for sending JSON packets so I created the following extension to handle that:

extension WebSocket {
    func send(_ json: JSON) throws {
        let data = try json.makeBytes()
        
        try send(data.string)
    }
}

Handling Messages

Messages are handled via the onText EventHandler which is called with a WebSocket and String passed in via closure. The string data is parsed into a JSON object so that command and user id that should be sent with each message.

For the connect message the user id and accompanying WebSocket are saved in the dooscribs dictionary and then a connected message is created and, much like the clear message, it gets to sent to all of the connections.

The other messages that are received get forwarded to all of the current connections with an exception for the one where the message.

Handling Connections and Users

Each connection will be stored in the dooscribs dictionary which is a pairing of the unique id that was generated by the browser and the WebSocket for their connection.

Whenever a connection is closed (user leaves page, closes browser) the onClose handler is called informing of the event. For cleanup purposes send a disconnect message to all of the clients that are stored in the dooScribs dictionary, minus the connection that just closed, as well as remove that user from the dictionary.

Issues – Random Disconnection

Something I noticed in my development was that clients were disconnecting for what seemed at the time as unknown reasons. After some research I found that “quiet” connections will automatically get disconnected. To handle this I created the background handler so that it would ping the socket every 5 seconds to keep channels open.

Conclusion

Hopefully this gives you some ideas for processing realtime data in your applications. In the future I plan to expand the dooScrib application so that it can handle different rooms as well text based messaging. The code for the dooScrib plugin as well a Node.js Implementation and this Vapor implementation can be founder here.

For now enjoy and as always Happy Coding!!!

Resolutions and Challenges

Well an old year has gone and new one is on its way.

Achievements of 2016

Each year, like everyone else, I make some resolutions and do my best to achieve each of them during the year. I can say that last years achievement I am most proud of was giving up soda and any other carbonated drinks. After a lifetime of drinking carbonated drinks this was harder to achieve than I expected it would be.

I can now say though that while eating at restaurants I really enjoy drinking water now and when I see my savings on the check it makes me feel a bit better.

At the same time I wanted to make sure that I didn’t replace carbonated drinks with high sugar drinks. Again a success and I found a new taste for a Green Tea.  Success!!

On the technical front last year was quiet and I decided to focus my time on getting more comfortable with my current knowledge set and just enjoy what I had.

Resolution for 2017

For my personal achievements I want to stop going out to eat as much as we normally do and instead eat at home more often. When I discussed this with my wife it caused some initial frustrations but I explained to her with the savings it will help achieve my other resolution for the next year. I would like for our family to take either a European vacation or something that allows us to travel to the east coast.

On the technical front I have the following planned:

  • Rewrite dooscrib.com with a Swift and Vapor backend.
  • Complete the first phase of smart mirror project.
  • Update iOS apps with more features.
  • Start going to more programmer meetings again.
  • Do a presentation on Vapor and Server Side Swift.

As I complete or work on some of my resolutions for the new year I will write about them and share my experiences.

Until then Happy Coding!!!

 

Setting up Ubuntu – Getting Ready for Server Side Swift

So this is going to be a quick step by step tutorial on the steps that I took setting up Ubuntu inside of VirtualBox on my Macbook Pro. Before we get started make sure you have VirtualBox Installed.

You can get a copy here.

While you are installing VirtualBox you will want to download a version of Ubuntu that you can use in your configuration. I wanted a graphical desktop so I downloaded Ubuntu Desktop which you can get here.

As just a personal preference I store my ISO images in a folder that is easily accessible from the desktop. Your choice on where you put it just make sure it’s somewhere easy for you to get to.

Setup

With VirtualBox installed and your ISO image for Ubuntu downloaded let’s get started. Go ahead and start VirtualBox.

Create a new Virtual Machine by clicking on the New button.

Screen Shot 2016-08-25 at 10.22.01 PM

Name Virtual Machine

Next you will want to setup and name your virtual machine. For this example I took the name directly from the name of the ISO image downloaded ubuntu-16.04.1-desktop and use that as the name. Once I set my name the Type (Linux) and Version (Ubuntu (64 bit version)) were automatically updated.

Screen Shot 2016-08-25 at 10.32.36 PM

Memory Configuration

Your ability to configure memory for your virtual machine will be limited by your machines physical configurations. I am fortune enough to have a lot of memory installed on my MacBook Pro.

On the Ubuntu configuration page it recommends 2 GB of system memory so to play it safe I doubled that number to 4 GB.

Screen Shot 2016-08-25 at 10.38.11 PM

Setting up Disk

I already know that I want to do some development on this image so I made the decision to give it more disk space for future development.

In my configuration I am going to create a 100 GB Fixed size VirtualBox Disk Drive.

Screen Shot 2016-08-25 at 10.45.17 PM

Screen Shot 2016-08-25 at 10.46.09 PM

Screen Shot 2016-08-25 at 10.46.59 PM

Screen Shot 2016-08-25 at 10.48.03 PM

Start your Virtual Machine

With the VirtualBox all setup and ready to run it’s now time to get Ubuntu running. Select the image that you just created and the click the Start button.

Screen Shot 2016-08-25 at 10.22.01 PM

It’s first question will be to prompt you for a optical disk image to use for installing Ubuntu. You will want to browse to wherever you downloaded Ubuntu and select it.

Screen Shot 2016-08-25 at 11.00.08 PM

Some message and error prompts that may come up that you can either clear or click the OK button on to get rid of.

Screen Shot 2016-08-25 at 11.01.06 PM

Screen Shot 2016-08-25 at 11.02.56 PM

Install Ubuntu

These next few screens are steps that I took for my configuration. Review them during your installation and make any adjustments that are appropriate for your preferences.

On the first screen you will of course want to click on Install Ubuntu.

Screen Shot 2016-08-25 at 11.05.37 PM

Screen Shot 2016-08-25 at 11.07.11 PM

Screen Shot 2016-08-25 at 11.08.01 PM

Click Continue

Screen Shot 2016-08-25 at 11.08.53 PM

Screen Shot 2016-08-25 at 11.09.59 PM

Screen Shot 2016-08-25 at 11.10.58 PM

Screen Shot 2016-08-25 at 11.12.35 PM

When you are all finished with the installation it will prompt you for restarting your machine. As just a reminder it is referring to the Virtual Machine that was created earlier.

Click the Restart Now button.

Screen Shot 2016-08-25 at 11.15.15 PM

When it prompts you to remove the install media just click the Ok button to proceed. All that is left now is to login and enjoy.

Screen Shot 2016-08-25 at 11.19.26 PM

Done

With Ubuntu running we are now ready to start on the next step of installing Swift 3 and getting ready to start using Vapor. That will come in a future post and for now check out Ubuntu and get comfortable with it.

Screen Shot 2016-08-25 at 11.23.33 PM

Keep following and as always Happy Coding.