2 Part Project

Diagram of Architecture

Setting Up a Home Router on a Linux Machine

given a linux machine we can almost always turn it into a WiFi router, that is if it has a built in WiFi adaptor or an extension with the available drivers. A Raspberry Pi is a perfect example, it is a mini computer with a built in WiFi adaptor and it only cost $35! No need to buy an external WiFi adaptor on amazon and no need to find and install the right driver for it!

once we obtain the raspberry pi mini computer, we need to setup 2 things:

  • host access point server - manages SSID, password, encryption, etc…
  • dhcp server - manages pool of available private IP address

Setup Process

refer to Raspberry Pi - Router

Setting Up a Chat Application

For real time text exchange this chat application uses WebSockets as a bi-directional network protocol built on top of TCP and HTTP. Other technologies are used together in order to make this happen. The language of choice is Java, as well as the 3 web languages: HTML, JavaScript, and CSS.

Java Code

package com.marcuschiu.springboot.websockets.chat;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class WebsocketsApplication {
 
   public static void main(String[] args) {
      SpringApplication.run(WebsocketsApplication.class, args);
   }
 
}
package com.marcuschiu.springboot.websockets.chat.model;
 
public class ChatMessage {
    private MessageType type;
    private String content;
    private String sender;
 
    public enum MessageType {
        CHAT,
        JOIN,
        LEAVE
    }
 
    public MessageType getType() {
        return type;
    }
 
    public void setType(MessageType type) {
        this.type = type;
    }
 
    public String getContent() {
        return content;
    }
 
    public void setContent(String content) {
        this.content = content;
    }
 
    public String getSender() {
        return sender;
    }
 
    public void setSender(String sender) {
        this.sender = sender;
    }
}
package com.marcuschiu.springboot.websockets.chat.controller;
 
import com.marcuschiu.springboot.websockets.chat.model.ChatMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
 
@Component
public class WebSocketEventListener {
 
    private static final Logger logger = LoggerFactory.getLogger(WebSocketEventListener.class);
 
    @Autowired
    private SimpMessageSendingOperations messagingTemplate;
 
    @EventListener
    public void handleWebSocketConnectListener(SessionConnectedEvent event) {
        logger.info("Received a new web socket connection");
    }
 
    /**
     * extract the user's name from the websocket session and broadcast a user leave event
     * to all the connected clients
     * @param event
     */
    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
 
        String username = (String) headerAccessor.getSessionAttributes().get("username");
        if(username != null) {
            logger.info("User Disconnected : " + username);
 
            ChatMessage chatMessage = new ChatMessage();
            chatMessage.setType(ChatMessage.MessageType.LEAVE);
            chatMessage.setSender(username);
 
            messagingTemplate.convertAndSend("/topic/public", chatMessage);
        }
    }
}
package com.marcuschiu.springboot.websockets.chat.controller;
 
import com.marcuschiu.springboot.websockets.chat.model.ChatMessage;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
 
/**
 * from config/WebSocketConfig.configureMessageBroker(...) all the messages sent from clients with a destination
 * starting with /app will be routed to these message handling methods annotated with @MessageMapping
 */
@Controller
public class ChatController {
 
    @MessageMapping("/chat.sendMessage")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
        return chatMessage;
    }
 
    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage,
                               SimpMessageHeaderAccessor headerAccessor) {
        // Add username in web socket session
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        return chatMessage;
    }
 
}
package com.marcuschiu.springboot.websockets.chat.config;
 
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;
 
/**
 * @EnableWebSocketMessageBroker - enables our WebSocket server
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
 
    /**
     * registers a websocket endpoint that the clients will use to connect to our websocket server
     * STOMP (Simple Text Oriented Messaging Protocol):
     * - is a messaging protocol that defines the format and rules for data exchange
     * - STOMP is used to define things like:
     *   - how to send a message only to users who are subscribed to a particular topic
     *   - how to send a message to a particular user
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
 
        // .withSockJS() - SockJS is used to enable fallback options for browsers that don't support websocket
        registry.addEndpoint("/ws").withSockJS();
    }
 
    /**
     * configures a message broker that will be used to route messages from one client to another
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
 
        // all the messages sent from clients with a destination starting with '/app'
        // will be routed to these message handling methods annotated with @MessageMapping
        registry.setApplicationDestinationPrefixes("/app");
 
        // enabled a simple in-memory message broker.
        // But you're free to use any other full-featured message broker like:
        // - RabbitMQ or ActiveMQ
        registry.enableSimpleBroker("/topic");
    }
}

Web Code: HTML, JavaScript, CSS

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
    <title>WebSocket Chat Application</title>
    <link rel="stylesheet" href="/css/main.css" />
</head>
<body>
<noscript>
    <h2>Sorry! Your browser doesn't support Javascript</h2>
</noscript>
 
<div id="username-page">
    <div class="username-page-container">
        <h1 class="title">Type your username</h1>
        <form id="usernameForm" name="usernameForm">
            <div class="form-group">
                <input type="text" id="name" placeholder="Username" autocomplete="off" class="form-control" />
            </div>
            <div class="form-group">
                <button type="submit" class="accent username-submit">Start Chatting</button>
            </div>
        </form>
    </div>
</div>
 
<div id="chat-page" class="hidden">
    <div class="chat-container">
        <div class="chat-header">
            <h2>WebSocket Chat Demo</h2>
        </div>
        <div class="connecting">
            Connecting...
        </div>
        <ul id="messageArea">
 
        </ul>
        <form id="messageForm" name="messageForm">
            <div class="form-group">
                <div class="input-group clearfix">
                    <input type="text" id="message" placeholder="Type a message..." autocomplete="off" class="form-control"/>
                    <button type="submit" class="primary">Send</button>
                </div>
            </div>
        </form>
    </div>
</div>
 
<!-- includes sockjs & stomp javascript libraries -->
 
<!-- SockJS is a WebSocket client that tries to use native WebSockets and provides intelligent
     fallback options for older browsers that don't support WebSocket -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
 
<!-- STOMP JS is the stomp client for javascript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
 
<script src="/js/main.js"></script>
</body>
</html>
'use strict';
 
var usernamePage = document.querySelector('#username-page');
var chatPage = document.querySelector('#chat-page');
var usernameForm = document.querySelector('#usernameForm');
var messageForm = document.querySelector('#messageForm');
var messageInput = document.querySelector('#message');
var messageArea = document.querySelector('#messageArea');
var connectingElement = document.querySelector('.connecting');
 
var stompClient = null;
var username = null;
 
var colors = [
    '#2196F3', '#32c787', '#00BCD4', '#ff5652',
    '#ffc107', '#ff85af', '#FF9800', '#39bbb0'
];
 
function connect(event) {
    username = document.querySelector('#name').value.trim();
 
    if(username) {
        usernamePage.classList.add('hidden');
        chatPage.classList.remove('hidden');
 
        var socket = new SockJS('/ws');
        stompClient = Stomp.over(socket);
 
        stompClient.connect({}, onConnected, onError);
    }
    event.preventDefault();
}
 
function onConnected() {
    // Subscribe to the Public Topic
    stompClient.subscribe('/topic/public', onMessageReceived);
 
    // Tell your username to the server
    stompClient.send("/app/chat.addUser",
        {},
        JSON.stringify({sender: username, type: 'JOIN'})
    )
 
    connectingElement.classList.add('hidden');
}
 
function onError(error) {
    connectingElement.textContent = 'Could not connect to WebSocket server. Please refresh this page to try again!';
    connectingElement.style.color = 'red';
}
 
function sendMessage(event) {
    var messageContent = messageInput.value.trim();
    if(messageContent && stompClient) {
        var chatMessage = {
            sender: username,
            content: messageInput.value,
            type: 'CHAT'
        };
        stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
        messageInput.value = '';
    }
    event.preventDefault();
}
 
function onMessageReceived(payload) {
    var message = JSON.parse(payload.body);
 
    var messageElement = document.createElement('li');
 
    if(message.type === 'JOIN') {
        messageElement.classList.add('event-message');
        message.content = message.sender + ' joined!';
    } else if (message.type === 'LEAVE') {
        messageElement.classList.add('event-message');
        message.content = message.sender + ' left!';
    } else {
        messageElement.classList.add('chat-message');
 
        var avatarElement = document.createElement('i');
        var avatarText = document.createTextNode(message.sender[0]);
        avatarElement.appendChild(avatarText);
        avatarElement.style['background-color'] = getAvatarColor(message.sender);
 
        messageElement.appendChild(avatarElement);
 
        var usernameElement = document.createElement('span');
        var usernameText = document.createTextNode(message.sender);
        usernameElement.appendChild(usernameText);
        messageElement.appendChild(usernameElement);
    }
 
    var textElement = document.createElement('p');
    var messageText = document.createTextNode(message.content);
    textElement.appendChild(messageText);
 
    messageElement.appendChild(textElement);
 
    messageArea.appendChild(messageElement);
    messageArea.scrollTop = messageArea.scrollHeight;
}
 
function getAvatarColor(messageSender) {
    var hash = 0;
    for (var i = 0; i < messageSender.length; i++) {
        hash = 31 * hash + messageSender.charCodeAt(i);
    }
    var index = Math.abs(hash % colors.length);
    return colors[index];
}
 
usernameForm.addEventListener('submit', connect, true)
messageForm.addEventListener('submit', sendMessage, true)
* {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}
 
html,body {
    height: 100%;
    overflow: hidden;
}
 
body {
    margin: 0;
    padding: 0;
    font-weight: 400;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 1rem;
    line-height: 1.58;
    color: #333;
    background-color: #f4f4f4;
    height: 100%;
}
 
body:before {
    height: 50%;
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    background: #128ff2;
    content: "";
    z-index: 0;
}
 
.clearfix:after {
    display: block;
    content: "";
    clear: both;
}
 
.hidden {
    display: none;
}
 
.form-control {
    width: 100%;
    min-height: 38px;
    font-size: 15px;
    border: 1px solid #c8c8c8;
}
 
.form-group {
    margin-bottom: 15px;
}
 
input {
    padding-left: 10px;
    outline: none;
}
 
h1, h2, h3, h4, h5, h6 {
    margin-top: 20px;
    margin-bottom: 20px;
}
 
h1 {
    font-size: 1.7em;
}
 
a {
    color: #128ff2;
}
 
button {
    box-shadow: none;
    border: 1px solid transparent;
    font-size: 14px;
    outline: none;
    line-height: 100%;
    white-space: nowrap;
    vertical-align: middle;
    padding: 0.6rem 1rem;
    border-radius: 2px;
    transition: all 0.2s ease-in-out;
    cursor: pointer;
    min-height: 38px;
}
 
button.default {
    background-color: #e8e8e8;
    color: #333;
    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
}
 
button.primary {
    background-color: #128ff2;
    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
    color: #fff;
}
 
button.accent {
    background-color: #ff4743;
    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
    color: #fff;
}
 
#username-page {
    text-align: center;
}
 
.username-page-container {
    background: #fff;
    box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
    border-radius: 2px;
    width: 100%;
    max-width: 500px;
    display: inline-block;
    margin-top: 42px;
    vertical-align: middle;
    position: relative;
    padding: 35px 55px 35px;
    min-height: 250px;
    position: absolute;
    top: 50%;
    left: 0;
    right: 0;
    margin: 0 auto;
    margin-top: -160px;
}
 
.username-page-container .username-submit {
    margin-top: 10px;
}
 
#chat-page {
    position: relative;
    height: 100%;
}
 
.chat-container {
    max-width: 700px;
    margin-left: auto;
    margin-right: auto;
    background-color: #fff;
    box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
    margin-top: 30px;
    height: calc(100% - 60px);
    max-height: 600px;
    position: relative;
}
 
#chat-page ul {
    list-style-type: none;
    background-color: #FFF;
    margin: 0;
    overflow: auto;
    overflow-y: scroll;
    padding: 0 20px 0px 20px;
    height: calc(100% - 150px);
}
 
#chat-page #messageForm {
    padding: 20px;
}
 
#chat-page ul li {
    line-height: 1.5rem;
    padding: 10px 20px;
    margin: 0;
    border-bottom: 1px solid #f4f4f4;
}
 
#chat-page ul li p {
    margin: 0;
}
 
#chat-page .event-message {
    width: 100%;
    text-align: center;
    clear: both;
}
 
#chat-page .event-message p {
    color: #777;
    font-size: 14px;
    word-wrap: break-word;
}
 
#chat-page .chat-message {
    padding-left: 68px;
    position: relative;
}
 
#chat-page .chat-message i {
    position: absolute;
    width: 42px;
    height: 42px;
    overflow: hidden;
    left: 10px;
    display: inline-block;
    vertical-align: middle;
    font-size: 18px;
    line-height: 42px;
    color: #fff;
    text-align: center;
    border-radius: 50%;
    font-style: normal;
    text-transform: uppercase;
}
 
#chat-page .chat-message span {
    color: #333;
    font-weight: 600;
}
 
#chat-page .chat-message p {
    color: #43464b;
}
 
#messageForm .input-group input {
    float: left;
    width: calc(100% - 85px);
}
 
#messageForm .input-group button {
    float: left;
    width: 80px;
    height: 38px;
    margin-left: 5px;
}
 
.chat-header {
    text-align: center;
    padding: 15px;
    border-bottom: 1px solid #ececec;
}
 
.chat-header h2 {
    margin: 0;
    font-weight: 500;
}
 
.connecting {
    padding-top: 5px;
    text-align: center;
    color: #777;
    position: absolute;
    top: 65px;
    width: 100%;
}
 
@media screen and (max-width: 730px) {
 
    .chat-container {
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
}
 
@media screen and (max-width: 480px) {
    .chat-container {
        height: calc(100% - 30px);
    }
 
    .username-page-container {
        width: auto;
        margin-left: 15px;
        margin-right: 15px;
        padding: 25px;
    }
 
    #chat-page ul {
        height: calc(100% - 120px);
    }
 
    #messageForm .input-group button {
        width: 65px;
    }
 
    #messageForm .input-group input {
        width: calc(100% - 70px);
    }
 
    .chat-header {
        padding: 10px;
    }
 
    .connecting {
        top: 60px;
    }
 
    .chat-header h2 {
        font-size: 1.1em;
    }
}

Example Use

here are 2 browsers opened side-by-side