All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.femo.ws.WebSocketHandler Maven / Gradle / Ivy

package io.femo.ws;

import io.femo.http.*;
import io.femo.http.Http;
import io.femo.http.helper.HandledCallback;
import io.femo.http.helper.HttpHelper;
import io.femo.support.jdk7.Optional;
import io.femo.ws.lib.WebSocketLibraryHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xjs.dynamic.PluggableAccessor;

import javax.xml.bind.DatatypeConverter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * Created by felix on 6/3/16.
 */
public class WebSocketHandler implements HttpHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger("HTTP-WS");

    private List webSocketEventHandlers;
    private List connections;

    private List onConnectListeners;

    private MessageDigest messageDigest;

    WebSocketHandler() {
        try {
            this.messageDigest = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            LOGGER.error("Message Digest not found. This system is not compatible", e);
        }
        this.webSocketEventHandlers = new ArrayList<>();
        this.onConnectListeners = new ArrayList<>();
        this.connections = new ArrayList<>();
    }

    public boolean handle(HttpRequest request, HttpResponse response) throws HttpHandleException {
        if (messageDigest == null) {
            return false;
        }
        if (Objects.equals(request.method(), Http.GET) &&
                request.hasHeaders(
                        Constants.WEBSOCKET.HEADERS.UPGRADE,
                        Constants.WEBSOCKET.HEADERS.KEY,
                        Constants.WEBSOCKET.HEADERS.CONNECTION,
                        Constants.WEBSOCKET.HEADERS.VERSION
                )) {
            if (request.header(Constants.WEBSOCKET.HEADERS.CONNECTION).value().contains(Constants.WEBSOCKET.HEADERS.UPGRADE)) {
                if (request.header(Constants.WEBSOCKET.HEADERS.UPGRADE).value().contains(Constants.WEBSOCKET.PROTO_NAME) &&
                        request.header(Constants.WEBSOCKET.HEADERS.VERSION).asInt() == 13) {
                    String accept = request.header(Constants.WEBSOCKET.HEADERS.KEY).value() + Constants.WEBSOCKET.GUID;
                    try {
                        response.header(Constants.WEBSOCKET.HEADERS.ACCEPT, HttpHelper.context().base64().encodeToString(messageDigest.digest(accept.getBytes("UTF-8"))))
                            .status(101)
                            .header(Constants.WEBSOCKET.HEADERS.UPGRADE, Constants.WEBSOCKET.PROTO_NAME)
                            .header(Constants.WEBSOCKET.HEADERS.CONNECTION, Constants.WEBSOCKET.HEADERS.UPGRADE)
                            .header("Content-Length", "0");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    messageDigest.reset();
                    Optional optional = PluggableAccessor.getFirst(HttpHelper.get(), Socket.class);
                    final Socket socket = optional.get();
                    LOGGER.debug("New WebSocket Client connecting from [{}]:{} using version {} [{}]",
                            socket.getInetAddress().toString(), socket.getPort(), request.header(Constants.WEBSOCKET.HEADERS.VERSION),
                            request.header(Constants.WEBSOCKET.HEADERS.PROTOCOL));
                    HttpHelper.keepOpen();
                    HttpHelper.callback(new HandledCallback() {
                        @Override
                        public void sent() {
                            try {
                                WebSocketConnection webSocketConnection = new WebSocketConnection(WebSocketHandler.this, socket);
                                webSocketConnection.start();
                                for(WebSocketEventHandler handler : onConnectListeners) {
                                    try {
                                        handler.handleMessage(Constants.WEBSOCKET.FRAME.DataType.CONNECT, null, webSocketConnection);
                                    } catch (Throwable t) {
                                        LOGGER.error("Error while notifying handler of new connection", t);
                                    }
                                }
                                connections.add(webSocketConnection);
                                webSocketConnection.ping("Hello World");
                            } catch (IOException e) {
                                LOGGER.warn("Could not establish WebSocket connection", e);
                            }
                        }
                    });
                } else {
                    LOGGER.warn("Invalid upgrade request received " + request.requestLine());
                    response.status(StatusCode.BAD_REQUEST);
                    response.entity("Invalid Upgrade Protocol selected. Only websocket supported so far...");
                }
            }
        } else {
            response.status(404);
            response.entity("The requested resource could not be found!");
        }
        return true;
    }

    public WebSocketHandler handler(WebSocketEventHandler handler) {
        this.webSocketEventHandlers.add(handler);
        return this;
    }

    public WebSocketHandler connect(WebSocketEventHandler handler) {
        this.onConnectListeners.add(handler);
        return this;
    }

    synchronized void raise(int opcode, byte[] bytes, WebSocketConnection webSocketConnection) {
        Constants.WEBSOCKET.FRAME.DataType dataType;
        if(opcode == Constants.WEBSOCKET.FRAME.OPCODES.BINARY_FRAME) {
            dataType = Constants.WEBSOCKET.FRAME.DataType.BINARY;
        } else if (opcode == Constants.WEBSOCKET.FRAME.OPCODES.TEXT_FRAME) {
            dataType = Constants.WEBSOCKET.FRAME.DataType.TEXT;
        } else if (opcode == Constants.WEBSOCKET.FRAME.OPCODES.CONNECTION_CLOSE) {
            dataType = Constants.WEBSOCKET.FRAME.DataType.CLOSE;
            connections.remove(webSocketConnection);
        } else {
            dataType = Constants.WEBSOCKET.FRAME.DataType.UNKNOWN;
        }
        for (WebSocketEventHandler w :
                this.webSocketEventHandlers) {
            w.handleMessage(dataType, bytes, webSocketConnection);
        }
    }

    public void sendToAll(String message) {
        sendToAll(Constants.WEBSOCKET.FRAME.DataType.TEXT, message.getBytes());
    }

    public synchronized void sendToAll(Constants.WEBSOCKET.FRAME.DataType dataType, byte[] data) {
        Iterator iterator = connections.iterator();
        while (iterator.hasNext()) {
            WebSocketConnection c = iterator.next();
            if(c.isClosed()) {
                iterator.remove();
            } else {
                try {
                    c.send(dataType, data);
                } catch (IOException e) {
                    LOGGER.warn("Error while sending data to connection!", e);
                }
            }
        }
    }

    public WebSocketLibraryHandler library() {
        WebSocketLibraryHandler handler = new WebSocketLibraryHandler(this);
        return handler;
    }

    public void sendToAllExcept(Constants.WEBSOCKET.FRAME.DataType dataType, byte[] data, List but) {
        for(WebSocketConnection c : connections) {
            boolean found = false;
            for(WebSocketConnection ws : but) {
                if(ws.getName().equals(c.getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                try {
                    c.send(dataType, data);
                } catch (IOException e) {
                    LOGGER.warn("Error while broadcasting data! Connection probably already closed!");
                    c.close();
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy