Package org.jwebsocket.tcp.engines

Source Code of org.jwebsocket.tcp.engines.TCPEngine$EngineListener

//  ---------------------------------------------------------------------------
//  jWebSocket - WebSocket TCP Engine
//  Copyright (c) 2010 Alexander Schulze, Innotrade GmbH
//  ---------------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU Lesser General Public License as published by the
//  Free Software Foundation; either version 3 of the License, or (at your
//  option) any later version.
//  This program is distributed in the hope that it will be useful, but WITHOUT
//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
//  more details.
//  You should have received a copy of the GNU Lesser General Public License along
//  with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
//  ---------------------------------------------------------------------------
package org.jwebsocket.tcp.engines;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.Map;
import javolution.util.FastMap;

import org.apache.log4j.Logger;
import org.jwebsocket.api.EngineConfiguration;
import org.jwebsocket.api.WebSocketConnector;
import org.jwebsocket.api.WebSocketEngine;
import org.jwebsocket.config.JWebSocketCommonConstants;
import org.jwebsocket.engines.BaseEngine;
import org.jwebsocket.logging.Logging;
import org.jwebsocket.tcp.connectors.TCPConnector;
import org.jwebsocket.kit.CloseReason;
import org.jwebsocket.kit.RequestHeader;
import org.jwebsocket.kit.WebSocketException;
import org.jwebsocket.kit.WebSocketHandshake;

/**
* Implementation of the jWebSocket TCP engine. The TCP engine provide a Java
* Socket implementation of the WebSocket protocol. It contains the handshake
* @author aschulze
*/
public class TCPEngine extends BaseEngine {

  private static Logger mLog = Logging.getLogger(TCPEngine.class);
  private ServerSocket mServerSocket = null;
  private int mListenerPort = JWebSocketCommonConstants.DEFAULT_PORT;
  private int mSessionTimeout = JWebSocketCommonConstants.DEFAULT_TIMEOUT;
  private boolean mIsRunning = false;
  private Thread mEngineThread = null;

  public TCPEngine(EngineConfiguration aConfiguration) {
    super(aConfiguration);
    mListenerPort = aConfiguration.getPort();
    mSessionTimeout = aConfiguration.getTimeout();
  }

  @Override
  public void startEngine()
      throws WebSocketException {
    if (mLog.isDebugEnabled()) {
      mLog.debug("Starting TCP engine '"
          + getId()
          + "' at port " + mListenerPort
          + " with default timeout "
          + (mSessionTimeout > 0 ? mSessionTimeout + "ms" : "infinite")
          + "...");
    }
    try {
      mServerSocket = new ServerSocket(mListenerPort);
      /*
      serverSocket = new ServerSocket(listenerPort); // listenerPort
      serverSocket.setReuseAddress(true);
      InetSocketAddress lISA = new InetSocketAddress(listenerPort);
      serverSocket.bind(lISA);
       */
      setSessionTimeout(mSessionTimeout);

      EngineListener listener = new EngineListener(this);
      mEngineThread = new Thread(listener);
      mEngineThread.start();

    } catch (IOException ex) {
      throw new WebSocketException(ex.getMessage());
    }

    // TODO: results in firing started event twice! make more clean!
    // super.startEngine();
    if (mLog.isInfoEnabled()) {
      mLog.info("TCP engine '"
          + getId() + "' started' at port "
          + mListenerPort + " with default timeout "
          + (mSessionTimeout > 0 ? mSessionTimeout + "ms" : "infinite")
          + "...");
    }
  }

  @Override
  public void stopEngine(CloseReason aCloseReason)
      throws WebSocketException {
    if (mLog.isDebugEnabled()) {
      mLog.debug("Stopping TCP engine '" + getId() + "' at port " + mListenerPort + "...");
    }

    // resetting "isRunning" causes engine listener to terminate
    mIsRunning = false;
    long lStarted = new Date().getTime();

    try {
      // when done, close server socket
      // closing the server socket should lead to an IOExeption
      // at accept in the listener thread which terminates the listener
      if (mServerSocket != null && !mServerSocket.isClosed()) {
        mServerSocket.close();
        if (mLog.isInfoEnabled()) {
          mLog.info("TCP engine '" + getId() + "' stopped at port " + mListenerPort + " (closed=" + mServerSocket.isClosed() + ").");
        }
        mServerSocket = null;
      } else {
        mLog.warn("Stopping TCP engine '" + getId() + "': no server socket or server socket closed.");
      }
    } catch (Exception ex) {
      mLog.error(ex.getClass().getSimpleName() + " on stopping TCP engine '" + getId() + "': " + ex.getMessage());
    }

    if (mEngineThread != null) {
      try {
        // TODO: Make this timeout configurable one day
        mEngineThread.join(10000);
      } catch (Exception ex) {
        mLog.error(ex.getClass().getSimpleName() + ": " + ex.getMessage());
      }
      if (mLog.isDebugEnabled()) {
        long lDuration = new Date().getTime() - lStarted;
        if (mEngineThread.isAlive()) {
          mLog.warn("TCP engine '" + getId() + "' did not stop after " + lDuration + "ms.");
        } else {
          mLog.debug("TCP engine '" + getId() + "' stopped after " + lDuration + "ms.");
        }
      }
    }

    // inherited method stops all connectors
    lStarted = new Date().getTime();
    int lNumConns = getConnectors().size();
    super.stopEngine(aCloseReason);

    // now wait until all connectors have been closed properly
    try {
      while (getConnectors().size() > 0 && new Date().getTime() - lStarted < 10000) {
        Thread.sleep(250);
      }
    } catch (Exception ex) {
      mLog.error(ex.getClass().getSimpleName() + ": " + ex.getMessage());
    }
    if (mLog.isDebugEnabled()) {
      long lDuration = new Date().getTime() - lStarted;
      int lRemConns = getConnectors().size();
      if (lRemConns > 0) {
        mLog.warn(lRemConns + " of " + lNumConns
            + " TCP connectors '" + getId()
            + "' did not stop after " + lDuration + "ms.");
      } else {
        mLog.debug(lNumConns
            + " TCP connectors '" + getId()
            + "' stopped after " + lDuration + "ms.");
      }
    }
  }

  @Override
  public void connectorStarted(WebSocketConnector aConnector) {
    if (mLog.isDebugEnabled()) {
      mLog.debug("Detected new connector at port " + aConnector.getRemotePort() + ".");
    }
    super.connectorStarted(aConnector);
  }

  @Override
  public void connectorStopped(WebSocketConnector aConnector, CloseReason aCloseReason) {
    if (mLog.isDebugEnabled()) {
      mLog.debug("Detected stopped connector at port " + aConnector.getRemotePort() + ".");
    }
    super.connectorStopped(aConnector, aCloseReason);
  }

  private RequestHeader processHandshake(Socket aClientSocket)
      throws UnsupportedEncodingException, IOException {

    InputStream lIn = aClientSocket.getInputStream();
    OutputStream lOut = aClientSocket.getOutputStream();

    // TODO: Replace this structure by more dynamic ByteArrayOutputStream?
    byte[] lBuff = new byte[8192];
    int lRead = lIn.read(lBuff);
    if (lRead <= 0) {
      mLog.warn("Connection did not detect initial handshake.");
      return null;
    }
    byte[] lReq = new byte[lRead];
    System.arraycopy(lBuff, 0, lReq, 0, lRead);

    //if (mLog.isDebugEnabled()) {
    //    mLog.debug("Handshake Request:\n" + new String(lResp));
    //    mLog.debug("Parsing initial WebSocket handshake...");
    //}
    Map lRespMap = WebSocketHandshake.parseC2SRequest(lReq);
    // maybe the request is a flash policy-file-request
    String lFlashBridgeReq = (String) lRespMap.get("policy-file-request");
    if (lFlashBridgeReq != null) {
      mLog.warn("TCPEngine returned policy file request ('"
          + lFlashBridgeReq + "'), check for FlashBridge plug-in.");
    }
    // generate the websocket handshake
    // if policy-file-request is found answer it
    byte[] lBA = WebSocketHandshake.generateS2CResponse(lRespMap);
    if (lBA == null) {
      if (mLog.isDebugEnabled()) {
        mLog.warn("TCPEngine detected illegal handshake.");
      }
      return null;
    }
    //if (log.isDebugEnabled()) {
    //  log.debug("Handshake Response:\n" + new String(lResp));
    //  mLog.debug("Flushing initial WebSocket handshake...");
    //}
    lOut.write(lBA);
    lOut.flush();

    // if we detected a flash policy-file-request return "null"
    // (no websocket header detected)
    if (lFlashBridgeReq != null) {
      mLog.warn("TCPEngine returned policy file response ('" + new String(lBA, "US-ASCII") + "'), check for FlashBridge plug-in.");
      return null;
    }

    RequestHeader lHeader = new RequestHeader();
    Map<String, String> lArgs = new FastMap<String, String>();
    String lPath = (String) lRespMap.get("path");

    // isolate search string
    String lSearchString = "";
    if (lPath != null) {
      int lPos = lPath.indexOf(JWebSocketCommonConstants.PATHARG_SEPARATOR);
      if (lPos >= 0) {
        lSearchString = lPath.substring(lPos + 1);
        if (lSearchString.length() > 0) {
          String[] lArgsArray = lSearchString.split(JWebSocketCommonConstants.ARGARG_SEPARATOR);
          for (int i = 0; i < lArgsArray.length; i++) {
            String[] lKeyValuePair = lArgsArray[i].split(JWebSocketCommonConstants.KEYVAL_SEPARATOR, 2);
            if (lKeyValuePair.length == 2) {
              lArgs.put(lKeyValuePair[0], lKeyValuePair[1]);
              if (mLog.isDebugEnabled()) {
                mLog.debug("arg" + i + ": " + lKeyValuePair[0] + "=" + lKeyValuePair[1]);
              }
            }
          }
        }
      }
    }

    if (mLog.isDebugEnabled()) {
      mLog.debug("Handshake flushed.");
    }

    // set default sub protocol if none passed
    if (lArgs.get("prot") == null) {
      lArgs.put("prot", JWebSocketCommonConstants.SUB_PROT_DEFAULT);
    }

    lHeader.put("host", lRespMap.get("host"));
    lHeader.put("origin", lRespMap.get("origin"));
    lHeader.put("location", lRespMap.get("location"));

    lHeader.put("path", lRespMap.get("path"));
    lHeader.put("searchString", lSearchString);
    lHeader.put("args", lArgs);

    return lHeader;
  }

  @Override
  /*
   * Returns {@code true} if the TCP engine is running or {@code false}
   * otherwise. The alive status represents the state of the TCP engine
   * listener thread.
   */
  public boolean isAlive() {
    return (mEngineThread != null && mEngineThread.isAlive());
  }

  private class EngineListener implements Runnable {

    private WebSocketEngine mEngine = null;

    /**
     * Creates the server socket listener for new
     * incoming socket connections.
     * @param aEngine
     */
    public EngineListener(WebSocketEngine aEngine) {
      mEngine = aEngine;
    }

    @Override
    public void run() {

      // notify server that engine has started
      engineStarted();

      mIsRunning = true;
      while (mIsRunning) {
        try {
          // accept is blocking so here is no need
          // to put any sleeps into this loop
          // if (log.isDebugEnabled()) {
          //  log.debug("Waiting for client...");
          // }
          Socket lClientSocket = mServerSocket.accept();
          boolean lTCPNoDelay = lClientSocket.getTcpNoDelay();
          lClientSocket.setTcpNoDelay(true);
          try {
            // process handshake to parse header data
            RequestHeader lHeader = processHandshake(lClientSocket);
            if (lHeader != null) {
              // set socket timeout to given amount of milliseconds
              // use tcp engine's timeout as default and
              // check system's min and max timeout ranges
              int lSessionTimeout = lHeader.getTimeout(getSessionTimeout());
              /* min and max range removed since 0.9.0.0602, see config documentation
              if (lSessionTimeout > JWebSocketServerConstants.MAX_TIMEOUT) {
              lSessionTimeout = JWebSocketServerConstants.MAX_TIMEOUT;
              } else if (lSessionTimeout < JWebSocketServerConstants.MIN_TIMEOUT) {
              lSessionTimeout = JWebSocketServerConstants.MIN_TIMEOUT;
              }
               */
              if (mLog.isDebugEnabled()) {
                mLog.debug("Client accepted on port "
                    + lClientSocket.getPort()
                    + " with timeout "
                    + (lSessionTimeout > 0 ? lSessionTimeout + "ms" : "infinite")
                    + " (TCPNoDelay was: " + lTCPNoDelay + ")...");
              }
              if (lSessionTimeout > 0) {
                lClientSocket.setSoTimeout(lSessionTimeout);
              }
              // create connector and pass header
              // log.debug("Instantiating connector...");
              WebSocketConnector lConnector = new TCPConnector(mEngine, lClientSocket);
              // log.debug("Setting header to engine...");
              lConnector.setHeader(lHeader);
              // log.debug("Adding connector to engine...");
              getConnectors().put(lConnector.getId(), lConnector);
              if (mLog.isDebugEnabled()) {
                mLog.debug("Starting connector...");
              }
              lConnector.startConnector();
            } else {
              // if header could not be parsed properly
              // immediately disconnect the client.
              lClientSocket.close();
            }
          } catch (UnsupportedEncodingException ex) {
            mLog.error("(encoding) " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
          } catch (IOException ex) {
            mLog.error("(io) " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
          } catch (Exception ex) {
            mLog.error("(other) " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
          }
        } catch (Exception ex) {
          mIsRunning = false;
          mLog.error("(accept) " + ex.getClass().getSimpleName() + ": " + ex.getMessage());
        }
      }

      // notify server that engine has stopped
      // this closes all connections
      engineStopped();
    }
  }
}
TOP

Related Classes of org.jwebsocket.tcp.engines.TCPEngine$EngineListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.