Difference between revisions of "User:Csmith1991/Vibe.d Documentation/websocket"

From D Wiki
Jump to: navigation, search
m (Vibe.d Websocket's Tutorial)
(Vibe.d Websocket's Tutorial -- added code)
Line 1: Line 1:
 
==Vibe.d Websocket's Tutorial==
 
==Vibe.d Websocket's Tutorial==
 +
 +
This tutorial is extended from [https://github.com/rejectedsoftware/vibe.d/tree/master/examples/websocket here]. Instead of a simple counter, this provides a functioning web chat application.
 +
 +
===Folder Structure===
 +
    .
 +
    ├── public/
 +
    |  ├── index.html   
 +
    |  └── scripts/
 +
    |      └── websocket.js
 +
    ├── source/
 +
    |  └── app.d
 +
 +
==== index.html ====
 +
<syntaxhighlight lang="html4strict">
 +
<!DOCTYPE html>
 +
<html>
 +
    <head>
 +
        <title>WebSockets Client</title>
 +
        <style>
 +
            #chatLog {
 +
                height: 400px;
 +
                padding-bottom: 20px;
 +
                overflow-y: scroll;
 +
            }
 +
        </style>
 +
    </head>
 +
    <body>
 +
        <h1>WebSockets Client</h1>
 +
        <div id="chatLog"></div><!-- this is where we'll insert chat into -->
 +
 +
        <div id="login">
 +
            <!-- {which: 13} is a hack to make the button function the same as onkeypress -->
 +
            <input id="name" type="text" placeholder="Your Name" onkeypress="startConnection(event);" /> <button id="connect" onclick="startConnection({which: 13});">Connect</button>
 +
        </div>
 +
        <div id="chat" style="display:none;">
 +
            <input id="text" type="text" placeholder="press enter to submit" onkeypress="chat(event);" /> <button id="send" onclick="chat({which: 13});">Send</button><br />
 +
            <button id="disconnect" onclick="endConnection();">Disconnect</button>
 +
        </div>
 +
        <script src="/scripts/websocket.js"></script>
 +
    </body>
 +
</html>
 +
</syntaxhighlight>
 +
 +
==== websocket.js ====
 +
 +
<syntaxhighlight lang="JavaScript">
 +
// Keep socket variable at global level
 +
var socket = {};
 +
 +
function connect(name) {
 +
    try {
 +
 +
        // Test if websocket doesn't exist, and if not, create a new connection
 +
        if (! ('readyState' in socket)) {
 +
            socket = new WebSocket(getBaseURL() + '/ws');
 +
        }
 +
 +
        socket.onopen = function() {
 +
            try {
 +
                socket.send(name); // Tell the server who you are, handle validation server side!
 +
 +
                // Swap the chat and login divs around
 +
                document.getElementById('name').value = '';
 +
                document.getElementById('login').style.display = 'none';
 +
                document.getElementById('chat').style.display = 'block';
 +
                document.getElementById('text').focus();
 +
            } catch (exception) {
 +
                alert(exception);
 +
            }
 +
        }
 +
        socket.onmessage = function(msg) {
 +
            var msgVal = JSON.parse(msg.data); // We're anticipating messages formatted as "{'name':'Csmith1991', 'text':'example'}"
 +
            var chatLog = document.getElementById('chatLog');
 +
            chatLog.innerHTML += '<p>' + msgVal.name + ': ' + msgVal.text + '</p>'; // Add to the chatLog
 +
            chatLog.scrollTop = chatLog.scrollHeight; // Scroll chatLog to bottom
 +
 +
        }
 +
        socket.onclose = function() {
 +
            socket = {}; // Remove socket connection
 +
 +
            // Swap the chat and login divs around. Note we don't do this until the connection has closed.
 +
            document.getElementById('chat').style.display = 'none';
 +
            document.getElementById('login').style.display = 'block';
 +
            document.getElementById('name').focus();
 +
        }
 +
    } catch (exception) {
 +
        alert(exception);
 +
    }
 +
}
 +
 +
function startConnection(event) {
 +
    // 13 = enter button
 +
    if (event.which === 13 || event.keyCode === 13 ) {
 +
        // Connect to WebSocket server
 +
        connect(document.getElementById('name').value);
 +
    }
 +
}
 +
 +
function chat(event) {
 +
    if (event.which === 13 || event.keyCode === 13 ) {
 +
        var myObj = document.getElementById('text');
 +
        socket.send(myObj.value);
 +
        myObj.value = '';
 +
        myObj.focus();
 +
    }
 +
}
 +
 +
function endConnection() {
 +
    // We want to close the connection on the server, so we create a message that the server listens for to close on
 +
    socket.send('/close');
 +
}
 +
 +
function getBaseURL() {
 +
    // Get the WebSocket server address e.g. ws://127.0.0.1:8080
 +
 +
    var href = window.location.href.substring(7); // strip "http://"
 +
    var idx = href.indexOf('/');
 +
    return 'ws://' + href.substring(0, idx);
 +
}
 +
</syntaxhighlight>
 +
 +
==== app.d ====
 +
 
<syntaxhighlight lang="D">
 
<syntaxhighlight lang="D">
// This is a note-to-self on syntax highlighting in the wiki.
+
module webSocketExample;
 +
 
 
import vibe.d;
 
import vibe.d;
 +
import vibe.utils.array;
 +
 +
private WebSocket[] sockets;
 +
 +
shared static this()
 +
{
 +
    /// Use /ws to identify websocket requests, serve files out of the public folder otherwise
 +
    auto router = new URLRouter;
 +
    router.get("/", staticRedirect("/index.html"));
 +
    router.get("/ws", handleWebSockets(&handleWebSocketConnection));
 +
    router.get("*", serveStaticFiles("public/"));
 +
 +
    auto settings = new HTTPServerSettings;
 +
    settings.port = 8080;
 +
    settings.bindAddresses = ["::1", "127.0.0.1"];
 +
    listenHTTP(settings, router);
 +
}
 +
 +
void handleWebSocketConnection(scope WebSocket socket)
 +
{
 +
    // Add socket to sockets list
 +
    sockets ~= socket;
 +
 +
    // Get username
 +
    socket.waitForData(1.seconds);
 +
    string name = socket.receiveText;
 +
 +
    // Server-side validation of results
 +
    if (name !is null)
 +
    {
 +
        logInfo("%s connected @ %s.", name, socket.request.peer);
 +
        sendTextToOtherClients(null, "System", name ~ " connected to the chat.");
 +
    }
 +
    else
 +
    {
 +
        // Kick person out
 +
        socket.send("{\"name\":\"System\", \"text\":\"Invalid name.\"}");
 +
       
 +
        socket.close;
 +
        sockets.removeFromArray!WebSocket(socket);
 +
        logInfo("%s disconnected.", name);
 +
        return;
 +
    }
 +
 +
    // message loop
 +
    while (socket.waitForData)
 +
    {
 +
        if (!socket.connected) break;
 +
        // we got something
 +
        auto text = socket.receiveText;
 +
       
 +
        // Close if recieve "/close"
 +
        if (text == "/close") break;
 +
 +
        logInfo("Received: \"%s\" from %s.", text, name);
 +
        // Relay text to everyone else
 +
        sendTextToOtherClients(socket, name, text);
 +
    }
 +
 +
    // Remove socket from sockets list and close socket
 +
    socket.close;
 +
    sockets.removeFromArray!WebSocket(socket);
 +
    logInfo("%s disconnected.", name);
 +
   
 +
    sendTextToOtherClients(null, "System", name ~ " disconnected to the chat.");
 +
}
 +
 +
void sendTextToOtherClients(scope WebSocket src_socket, string name, string text)
 +
{
 +
    foreach (socket; sockets)
 +
    {
 +
        // Don't send it to people who won't get it.
 +
        if (!socket.connected) continue;
 +
       
 +
        logInfo("Sending: \"%s\" to %s.", text, socket.request.peer);
 +
        // JSON encoding for simplicity
 +
        socket.send("{\"name\":\"" ~ name ~ "\", \"text\":\"" ~ text ~ "\"}");
 +
    }
 +
}
 +
 
</syntaxhighlight>
 
</syntaxhighlight>

Revision as of 02:21, 12 May 2015

Vibe.d Websocket's Tutorial

This tutorial is extended from here. Instead of a simple counter, this provides a functioning web chat application.

Folder Structure

   .
   ├── public/
   |   ├── index.html    
   |   └── scripts/
   |       └── websocket.js
   ├── source/
   |   └── app.d

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>WebSockets Client</title>
        <style>
            #chatLog {
                height: 400px;
                padding-bottom: 20px;
                overflow-y: scroll;
            }
        </style>
    </head>
    <body>
        <h1>WebSockets Client</h1>
        <div id="chatLog"></div><!-- this is where we'll insert chat into -->

        <div id="login">
            <!-- {which: 13} is a hack to make the button function the same as onkeypress -->
            <input id="name" type="text" placeholder="Your Name" onkeypress="startConnection(event);" /> <button id="connect" onclick="startConnection({which: 13});">Connect</button>
        </div>
        <div id="chat" style="display:none;">
            <input id="text" type="text" placeholder="press enter to submit" onkeypress="chat(event);" /> <button id="send" onclick="chat({which: 13});">Send</button><br />
            <button id="disconnect" onclick="endConnection();">Disconnect</button>
        </div>
        <script src="/scripts/websocket.js"></script>
    </body>
</html>

websocket.js

// Keep socket variable at global level
var socket = {};

function connect(name) {
    try {

        // Test if websocket doesn't exist, and if not, create a new connection
        if (! ('readyState' in socket)) {
            socket = new WebSocket(getBaseURL() + '/ws');
        }

        socket.onopen = function() {
            try {
                socket.send(name); // Tell the server who you are, handle validation server side!

                // Swap the chat and login divs around
                document.getElementById('name').value = '';
                document.getElementById('login').style.display = 'none';
                document.getElementById('chat').style.display = 'block';
                document.getElementById('text').focus();
            } catch (exception) {
                alert(exception);
            }
        }
        socket.onmessage = function(msg) {
            var msgVal = JSON.parse(msg.data); // We're anticipating messages formatted as "{'name':'Csmith1991', 'text':'example'}"
            var chatLog = document.getElementById('chatLog');
            chatLog.innerHTML += '<p>' + msgVal.name + ': ' + msgVal.text + '</p>'; // Add to the chatLog
            chatLog.scrollTop = chatLog.scrollHeight; // Scroll chatLog to bottom

        }
        socket.onclose = function() {
            socket = {}; // Remove socket connection

            // Swap the chat and login divs around. Note we don't do this until the connection has closed.
            document.getElementById('chat').style.display = 'none';
            document.getElementById('login').style.display = 'block';
            document.getElementById('name').focus();
        }
    } catch (exception) {
        alert(exception);
    }
}

function startConnection(event) {
    // 13 = enter button
    if (event.which === 13 || event.keyCode === 13 ) {
        // Connect to WebSocket server
        connect(document.getElementById('name').value);
    }
}

function chat(event) {
    if (event.which === 13 || event.keyCode === 13 ) {
        var myObj = document.getElementById('text');
        socket.send(myObj.value);
        myObj.value = '';
        myObj.focus();
    }
}

function endConnection() {
    // We want to close the connection on the server, so we create a message that the server listens for to close on
    socket.send('/close');
}

function getBaseURL() {
    // Get the WebSocket server address e.g. ws://127.0.0.1:8080

    var href = window.location.href.substring(7); // strip "http://"
    var idx = href.indexOf('/');
    return 'ws://' + href.substring(0, idx);
}

app.d

module webSocketExample;

import vibe.d;
import vibe.utils.array;

private WebSocket[] sockets;

shared static this()
{
    /// Use /ws to identify websocket requests, serve files out of the public folder otherwise
    auto router = new URLRouter;
    router.get("/", staticRedirect("/index.html"));
    router.get("/ws", handleWebSockets(&handleWebSocketConnection));
    router.get("*", serveStaticFiles("public/"));

    auto settings = new HTTPServerSettings;
    settings.port = 8080;
    settings.bindAddresses = ["::1", "127.0.0.1"];
    listenHTTP(settings, router);
}

void handleWebSocketConnection(scope WebSocket socket)
{
    // Add socket to sockets list
    sockets ~= socket;

    // Get username
    socket.waitForData(1.seconds);
    string name = socket.receiveText;

    // Server-side validation of results
    if (name !is null)
    {
        logInfo("%s connected @ %s.", name, socket.request.peer);
        sendTextToOtherClients(null, "System", name ~ " connected to the chat.");
    }
    else
    {
        // Kick person out
        socket.send("{\"name\":\"System\", \"text\":\"Invalid name.\"}");
        
        socket.close;
        sockets.removeFromArray!WebSocket(socket);
        logInfo("%s disconnected.", name);
        return;
    }

    // message loop
    while (socket.waitForData)
    {
        if (!socket.connected) break;
        // we got something
        auto text = socket.receiveText;
        
        // Close if recieve "/close"
        if (text == "/close") break;

        logInfo("Received: \"%s\" from %s.", text, name);
        // Relay text to everyone else
        sendTextToOtherClients(socket, name, text);
    }

    // Remove socket from sockets list and close socket
    socket.close;
    sockets.removeFromArray!WebSocket(socket);
    logInfo("%s disconnected.", name);
    
    sendTextToOtherClients(null, "System", name ~ " disconnected to the chat.");
}

void sendTextToOtherClients(scope WebSocket src_socket, string name, string text)
{
    foreach (socket; sockets)
    {
        // Don't send it to people who won't get it.
        if (!socket.connected) continue;
        
        logInfo("Sending: \"%s\" to %s.", text, socket.request.peer);
        // JSON encoding for simplicity
        socket.send("{\"name\":\"" ~ name ~ "\", \"text\":\"" ~ text ~ "\"}");
    }
}