org.apache.camel.component.websocket.SignalkWebSocketServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.camel.component.websocket.SignalkWebSocketServlet.java

Source

/*
 * 
 * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
 * Web: www.42.co.nz
 * Email: robert@42.co.nz
 * Author: R T Huitema
 * 
 * This file is part of the signalk-server-java project
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.component.websocket;

import static nz.co.fortytwo.signalk.util.SignalKConstants.EXTERNAL_IP;
import static nz.co.fortytwo.signalk.util.SignalKConstants.INTERNAL_IP;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_SRC_IP;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_SRC_IP_PORT;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_TYPE;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import nz.co.fortytwo.signalk.server.CamelContextFactory;
import nz.co.fortytwo.signalk.server.RouteManager;
import nz.co.fortytwo.signalk.server.RouteManagerFactory;
import nz.co.fortytwo.signalk.server.SubscriptionManagerFactory;
import nz.co.fortytwo.signalk.util.ConfigConstants;
import nz.co.fortytwo.signalk.util.SignalKConstants;
import nz.co.fortytwo.signalk.util.Util;

import org.apache.camel.Producer;
import org.apache.camel.ProducerTemplate;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.BlockingHttpConnection;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.websocket.Extension;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketBuffers;
import org.eclipse.jetty.websocket.WebSocketFactory;
import org.eclipse.jetty.websocket.WebSocketServletConnection;
import org.eclipse.jetty.websocket.WebSocketServletConnectionD00;
import org.eclipse.jetty.websocket.WebSocketServletConnectionD06;
import org.eclipse.jetty.websocket.WebSocketServletConnectionD08;
import org.eclipse.jetty.websocket.WebSocketServletConnectionRFC6455;

public class SignalkWebSocketServlet extends WebsocketComponentServlet {

    private static final long serialVersionUID = 1L;
    private static Logger logger = LogManager.getLogger(SignalkWebSocketServlet.class);
    private WebSocketFactory _webSocketFactory;
    private ProducerTemplate producer = null;

    public SignalkWebSocketServlet(NodeSynchronization sync) {
        super(sync);
        producer = CamelContextFactory.getInstance().createProducerTemplate();
    }

    public void init() throws ServletException {
        try {
            String bs = getInitParameter("bufferSize");
            if (logger.isDebugEnabled())
                logger.debug("Upgrade ws, create factory:");
            this._webSocketFactory = new WebSocketFactory(this, (bs == null) ? 8192 : Integer.parseInt(bs)) {
                private WebSocketBuffers _buffers = new WebSocketBuffers(8192);
                private Map<WebSocketServletConnection, String> sessionMap = new HashMap<WebSocketServletConnection, String>();

                public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket,
                        String protocol) throws IOException {
                    String sessionId = request.getRequestedSessionId();
                    if (logger.isDebugEnabled())
                        logger.debug("Upgrade ws, requested sessionId:" + sessionId);
                    if (StringUtils.isBlank(sessionId)) {
                        sessionId = request.getSession().getId();
                        if (logger.isDebugEnabled())
                            logger.debug("Request.sessionId:" + sessionId);

                    }
                    if (StringUtils.isBlank(sessionId)) {
                        sessionId = ((DefaultWebsocket) websocket).getConnectionKey();
                        if (logger.isDebugEnabled())
                            logger.debug("Request.wsSessionId:" + sessionId);

                    }

                    if (!("websocket".equalsIgnoreCase(request.getHeader("Upgrade"))))
                        throw new IllegalStateException("!Upgrade:websocket");
                    if (!("HTTP/1.1".equals(request.getProtocol()))) {
                        throw new IllegalStateException("!HTTP/1.1");
                    }
                    int draft = request.getIntHeader("Sec-WebSocket-Version");
                    if (draft < 0) {
                        draft = request.getIntHeader("Sec-WebSocket-Draft");
                    }

                    int requestedVersion = draft;
                    AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
                    if (http instanceof BlockingHttpConnection)
                        throw new IllegalStateException("Websockets not supported on blocking connectors");
                    ConnectedEndPoint endp = (ConnectedEndPoint) http.getEndPoint();

                    List<String> extensions_requested = new ArrayList<>();

                    Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
                    while (e.hasMoreElements()) {
                        QuotedStringTokenizer tok = new QuotedStringTokenizer((String) e.nextElement(), ",");
                        while (tok.hasMoreTokens()) {
                            extensions_requested.add(tok.nextToken());
                        }

                    }

                    if (draft < getMinVersion())
                        draft = 2147483647;

                    WebSocketServletConnection connection;
                    switch (draft) {
                    case -1:
                    case 0:
                        connection = new WebSocketServletConnectionD00(this, websocket, endp, this._buffers,
                                http.getTimeStamp(), (int) getMaxIdleTime(), protocol);
                        break;
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                    case 6:
                        connection = new WebSocketServletConnectionD06(this, websocket, endp, this._buffers,
                                http.getTimeStamp(), (int) getMaxIdleTime(), protocol);
                        break;
                    case 7:
                    case 8:
                        List<Extension> extensions = initExtensions(extensions_requested, 5, 5, 3);
                        connection = new WebSocketServletConnectionD08(this, websocket, endp, this._buffers,
                                http.getTimeStamp(), (int) getMaxIdleTime(), protocol, extensions, draft);
                        break;
                    case 13:
                        List<Extension> extensions1 = initExtensions(extensions_requested, 5, 5, 3);
                        connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, this._buffers,
                                http.getTimeStamp(), (int) getMaxIdleTime(), protocol, extensions1, draft);
                        break;
                    case 9:
                    case 10:
                    case 11:
                    case 12:
                    default:
                        String versions = "13";
                        if (getMinVersion() <= 8)
                            versions = new StringBuilder().append(versions).append(", 8").toString();
                        if (getMinVersion() <= 6)
                            versions = new StringBuilder().append(versions).append(", 6").toString();
                        if (getMinVersion() <= 0) {
                            versions = new StringBuilder().append(versions).append(", 0").toString();
                        }
                        response.setHeader("Sec-WebSocket-Version", versions);

                        StringBuilder err = new StringBuilder();
                        err.append("Unsupported websocket client version specification ");
                        if (requestedVersion >= 0)
                            err.append("[").append(requestedVersion).append("]");
                        else {
                            err.append("<Unspecified, likely a pre-draft version of websocket>");
                        }
                        err.append(", configured minVersion [").append(getMinVersion()).append("]");
                        err.append(", reported supported versions [").append(versions).append("]");
                        // LOG.warn(err.toString(), new Object[0]);

                        throw new HttpException(400, "Unsupported websocket version specification");
                    }

                    addConnection(connection);

                    connection.getConnection().setMaxBinaryMessageSize(getMaxBinaryMessageSize());
                    connection.getConnection().setMaxTextMessageSize(getMaxTextMessageSize());

                    connection.handshake(request, response, protocol);
                    response.flushBuffer();

                    connection.fillBuffersFrom(((HttpParser) http.getParser()).getHeaderBuffer());
                    connection.fillBuffersFrom(((HttpParser) http.getParser()).getBodyBuffer());
                    String wsSession = ((DefaultWebsocket) websocket).getConnectionKey();
                    //if(logger.isDebugEnabled())logger.debug("Upgraded session " + request.getSession().getId() + " to ws " + ((DefaultWebsocket) websocket).getConnectionKey());
                    if (logger.isDebugEnabled())
                        logger.debug("Upgraded session " + sessionId + " to ws " + wsSession + " from remote ip:"
                                + request.getRemoteAddr());
                    try {
                        sessionMap.put(connection, wsSession);
                        SubscriptionManagerFactory.getInstance().add(sessionId, wsSession,
                                ConfigConstants.OUTPUT_WS, request.getLocalAddr(), request.getRemoteAddr());
                        //add default sub, or specific sub here, all instant policy
                        String subscribe = request.getParameter("subscribe");
                        if (StringUtils.isBlank(subscribe) || "self".equals(subscribe)) {
                            //subscribe to self
                            String sub = "{\"context\":\"vessels.self\",\"subscribe\":[{\"path\":\"*\", \"policy\":\"instant\"}]}";
                            sendSub(request, sub, wsSession);
                        } else if ("all".equals(subscribe)) {
                            //subscribe to all
                            String sub = "{\"context\":\"vessels.*\",\"subscribe\":[{\"path\":\"*\", \"policy\":\"instant\"}]}";
                            sendSub(request, sub, wsSession);
                        } else if ("none".equals(subscribe)) {
                            //subscribe to none - do nothing
                        }

                    } catch (Exception e1) {
                        logger.error(e1.getMessage(), e1);
                        throw new IOException(e1);
                    }
                    // LOG.debug("Websocket upgrade {} {} {} {}", new Object[] { request.getRequestURI(), Integer.valueOf(draft), protocol, connection });
                    request.setAttribute("org.eclipse.jetty.io.Connection", connection);
                    connection.getConnection().sendMessage(Util.getWelcomeMsg().toString());
                }

                private void sendSub(HttpServletRequest request, String sub, String wsSession) throws Exception {
                    Map<String, Object> headers = new HashMap<>();
                    headers.put(MSG_SRC_IP, request.getRemoteAddr());
                    headers.put(MSG_SRC_IP_PORT, request.getRemotePort());

                    if (Util.sameNetwork(request.getLocalAddr(), request.getRemoteAddr())) {
                        headers.put(MSG_TYPE, INTERNAL_IP);
                    } else {
                        headers.put(MSG_TYPE, EXTERNAL_IP);
                    }
                    headers.put(WebsocketConstants.CONNECTION_KEY, wsSession);
                    if (logger.isDebugEnabled())
                        logger.debug("Sending connection sub:" + sub);
                    producer.sendBodyAndHeaders(RouteManager.SEDA_INPUT, sub, headers);

                }

                @Override
                protected boolean removeConnection(WebSocketServletConnection connection) {
                    //unsubscribe and remove websocket session
                    String wsSession = sessionMap.get(connection);
                    if (logger.isDebugEnabled())
                        logger.debug("Ended wsSession " + wsSession);
                    try {
                        SubscriptionManagerFactory.getInstance().removeWsSession(wsSession);
                    } catch (Exception e1) {
                        logger.error(e1.getMessage(), e1);
                    }
                    return super.removeConnection(connection);
                }

            };
            this._webSocketFactory.setMaxTextMessageSize(256 * 1024);
            this._webSocketFactory.start();

            String max = getInitParameter("maxIdleTime");
            if (max != null) {
                this._webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
            }
            max = getInitParameter("maxTextMessageSize");
            if (max != null) {
                this._webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
            }
            max = getInitParameter("maxBinaryMessageSize");
            if (max != null) {
                this._webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
            }
            String min = getInitParameter("minVersion");
            if (min != null)
                this._webSocketFactory.setMinVersion(Integer.parseInt(min));
        } catch (ServletException x) {
            throw x;
        } catch (Exception x) {
            throw new ServletException(x);
        }
    }

    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if ((this._webSocketFactory.acceptWebSocket(request, response)) || (response.isCommitted()))
            return;
        super.service(request, response);
    }

    public boolean checkOrigin(HttpServletRequest request, String origin) {
        return true;
    }

    public void destroy() {
        try {
            this._webSocketFactory.stop();
        } catch (Exception x) {
            logger.warn(x.getMessage());
        }
    }

}