Package org.eclipse.wst.wsi.internal.core.monitor

Source Code of org.eclipse.wst.wsi.internal.core.monitor.SocketHandler

/*******************************************************************************
* Copyright (c) 2002-2003 IBM Corporation, Beacon Information Technology Inc. and others.
* All rights reserved.   This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   IBM      - Initial API and implementation
*   BeaconIT - Initial API and implementation
*******************************************************************************/
package org.eclipse.wst.wsi.internal.core.monitor;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;

import org.eclipse.wst.wsi.internal.core.WSIConstants;
import org.eclipse.wst.wsi.internal.core.WSIException;
import org.eclipse.wst.wsi.internal.core.log.MessageEntry;
import org.eclipse.wst.wsi.internal.core.util.Utils;

/**
* Socket Handler.
*
* @author Peter  Brittenham (peterbr@us.ibm.com)
* @version 1.0.1
*/
public class SocketHandler extends Thread
{
  protected SocketConnection socketConnection;
  protected SocketHandler pairedSocketHandler;
  protected String connectionType;
  protected int conversationID;
  protected String targetHost;
  protected int readTimeoutSeconds;

  protected Socket inSocket;
  protected Socket outSocket;
  protected InputStream inputStream = null;
  protected OutputStream outputStream = null;

  protected boolean verbose = false;

  protected boolean readTimedOut = false;

  // I18N: 2003.02.26 modified by K.Nakagome@BeaconIT
  private String mimeCharset = null;
  private String xmlEncoding = null;

  protected static final String CRLF = "\r\n";

  protected static final String HTTP_100_CONTINUE =
    "100 Continue".toUpperCase();
  protected static final String CHUNKED =
    "Transfer-Encoding: chunked".toUpperCase();
  protected static final String CHUNKED_WITH_QUOTES =
    "Transfer-Encoding: \"chunked\"".toUpperCase();
  protected static final String CONTENT_LENGTH =
    "Content-Length:".toUpperCase();

  /**
   * Create socket handler.
   * @param socketConnection   socket connection.
   * @param connectionType     connection type.
   * @param conversationID     conversation id.
   * @param targetHost         target host.
   * @param inSocket           in socket.
   * @param outSocket          out socket.
   * @param readTimeoutSeconds read timeout seconds.
   */
  public SocketHandler(
    SocketConnection socketConnection,
    String connectionType,
    int conversationID,
    String targetHost,
    Socket inSocket,
    Socket outSocket,
    int readTimeoutSeconds)
  {
    this.socketConnection = socketConnection;
    this.connectionType = connectionType;
    this.conversationID = conversationID;
    this.targetHost = targetHost;
    this.inSocket = inSocket;
    this.outSocket = outSocket;
    this.readTimeoutSeconds = readTimeoutSeconds;

    // ADD:
    verbose =
      socketConnection.getMonitor().getMonitorConfig().getVerboseOption();

    start();
  }

  /**
   * Set paired socket handler.
   * @param pairedSocketHandler paired socket handler.
   */
  public void setPairedSocketHandler(SocketHandler pairedSocketHandler)
  {
    this.pairedSocketHandler = pairedSocketHandler;
  }

  /**
   * Send the data from the input socket to the output socket.
   */
  public void run()
  {
    int readLen;
    String readMsg;
    MessageContext messageContext = null;

    // Create read buffer     
    byte[] readBuffer = new byte[4096];

    try
    {
      // Get the input and output streams
      this.inputStream = this.inSocket.getInputStream();
      this.outputStream = this.outSocket.getOutputStream();

      // Process while the connection is active
      // (NOTE: there might be more than 1 message per connection)
      boolean connectionActive = true;
      while (connectionActive)
      {
        // Reset all data
        readLen = 0;
        messageContext = new MessageContext();

        // Read until message is complete
        boolean messageComplete = false;
        while (!messageComplete)
        {
          try
          {
            // DEBUG:
            debug("run", "Read data from the input stream.");

            // Read data from the input stream
            readLen = inputStream.read(readBuffer, 0, readBuffer.length);

            // Reset read timeout flag
            readTimedOut = false;

            // DEBUG:
            debug("run", "readLen: " + readLen);

            if (readLen == -1)
            {
              connectionActive = false;
              messageComplete = true;
            }

            // If data was read, then check for 100 continue
            else if (readLen > 0)
            {
              // If this is the first data that was read, then get the timestamp
              if (messageContext.timestamp == null)
                messageContext.timestamp = Utils.getTimestamp();

              if (connectionType.equals(MessageEntry.TYPE_REQUEST))
              {
                byte[] toHost =
                  new String(
                    socketConnection.redirect.getToHost()
                      + ":"
                      + socketConnection.redirect.getToPort())
                    .getBytes();

                String message = new String(readBuffer, 0, readLen);

                int index = message.indexOf(CRLF + "Host: ");
                if (index > -1)
                {
                  index += 8;
                  int secondPart = message.indexOf(CRLF, index);

                  // Write the data to the output stream and then go format it
                  write(this.outputStream, readBuffer, 0, index);
                  write(this.outputStream, toHost, 0, toHost.length);
                  write(
                    this.outputStream,
                    readBuffer,
                    secondPart,
                    readLen - secondPart);
                }
                else
                {
                  // Write the data to the output stream and then go format it
                  write(this.outputStream, readBuffer, 0, readLen);
                }
              }
              else
              {
                // Write the data to the output stream and then go format it
                write(this.outputStream, readBuffer, 0, readLen);
              }

              // DEBUG:
              if (verbose)
              {
                String bufferString = new String(readBuffer, 0, readLen);
                debug("run", "buffer as string: [" + bufferString + "]");
                if (bufferString.length() <= 50)
                  debug(
                    "run",
                    "buffer as hexstring: ["
                      + Utils.toHexString(bufferString)
                      + "]");
                else
                  debug(
                    "run",
                    "buffer as hexstring: ["
                      + Utils.toHexString(bufferString.substring(0, 50))
                      + " ...]");
              }

              // See if this part of the buffer contains the BOM
              if (messageContext.bom == 0)
              {
                messageContext.bom = getBOM(readBuffer);
              }

              // DEBUG
              debug("run", "bom: " + messageContext.bom);

              String encoding;

              try
              {
                encoding = getEncoding();
                readMsg =
                  new String(
                    readBuffer,
                    0,
                    readLen,
                    Utils.getJavaEncoding(encoding));
                setEncoding(readMsg);
                if (!encoding.equals(getEncoding()))
                {
                  encoding = getEncoding();
                  readMsg =
                    new String(
                      readBuffer,
                      0,
                      readLen,
                      Utils.getJavaEncoding(encoding));
                }
              }

              catch (UnsupportedEncodingException uee)
              {
                debug("run", "EXCEPTION (3): " + uee.toString());
                throw new RuntimeException(uee.toString());
              }

              // Set encoding in the message context
              messageContext.encoding = encoding;

              // DEBUG
              debug("run", "encoding: " + messageContext.encoding);

              // Process message
              messageContext = processMessage(readLen, readMsg, messageContext);
            }

            // If message is complete, then log it and reset buffer
            if ((isMessageComplete(messageContext))
              || ((readLen == -1) && (messageContext.messageBuffer.length() > 0)))
            {
              // Log message
              logMessage(messageContext);

              // Set message complete
              messageComplete = true;
            }
          }

          catch (InterruptedIOException ie)
          {
            // Set read timeout flag
            readTimedOut = true;

            debug("run", "InterruptedIOException: " + ie.toString());

            // If the read is not done, then shutdown
            if (pairedSocketHandler != null
              && pairedSocketHandler.isReadWaiting()
              && pairedSocketHandler.isReadTimedOut())
            {
              // DEBUG:
              debug("run", "read timed out on both sockets");

              // If there is data in the message buffer and it is complete, then log it
              if ((isMessageComplete(messageContext))
                || (messageContext.messageBuffer.length() > 0))
              {
                // Log message
                logMessage(messageContext);
              }

              // Set message complete
              connectionActive = false;
              messageComplete = true;
            }
          }

          catch (Exception e2)
          {
            // DEBUG:
            debug(
              "run",
              "EXCEPTION (2): "
                + e2.toString()
                + "\n"
                + Utils.getExceptionDetails(e2));
            //e2.printStackTrace();

            // If there is data in the message buffer and it is complete, then log it
            if ((isMessageComplete(messageContext))
              || (messageContext.messageBuffer.length() > 0))
            {
              // Log message
              logMessage(messageContext);
            }

            // Set message complete
            connectionActive = false;
            messageComplete = true;
          }
        }
      }
    }

    catch (Exception e)
    {
      // DEBUG:
      debug(
        "run",
        "EXCEPTION (1): "
          + e.getMessage()
          + "\n"
          + Utils.getExceptionDetails(e));
      //e.printStackTrace();
    }

    catch (Error err)
    {
      // DEBUG:
      debug("run", "ERROR: " + err.getMessage());
      //err.printStackTrace();
    }

    finally
    {
      shutdown();
      socketConnection.wakeUp();
    }
  }

  /**
   * Process the message.
   */
  private MessageContext processMessage(
    int readLen,
    String readMsg,
    MessageContext inMessageContext)
    throws WSIException
  {
    boolean continueRead = false;

    // Initialize message context                                      
    MessageContext messageContext = inMessageContext;

    // Get message buffer and chunked data from message context
    StringBuffer messageBuffer = messageContext.messageBuffer;
    ChunkedData chunkedData = messageContext.chunkedData;

    // If all we received was the header with 100 continue, then ignore it
    if ((readMsg.toUpperCase().indexOf(HTTP_100_CONTINUE) != -1)
      && (readLen >= 25))
    {
      // DEBUG:
      debug("processMessage", "Ignore HTTP 100 Continue.");

      // Find the end of the HTTP 100 message
      int index = Utils.getFirstCRLFCRLF(readMsg);

      // If there is only the HTTP 100 message, then just ignore it
      if (index == readMsg.length())
        continueRead = true;

      // Otherwise remove the HTTP 100 message and continue
      else
        readMsg = readMsg.substring(index);
    }

    // ADD: What if a bypassed message contains another message after it?
    if (!continueRead && bypassMessage(readMsg))
    {
      // DEBUG:
      debug(
        "processMessage",
        "Do not log message as defined in the monitor spec, but it will be sent.");

      continueRead = true;
    }

    if (!continueRead)
    {
      int index = 0;

      // If there is chunked data, then get the length
      if ((readMsg.toUpperCase().indexOf(CHUNKED) != -1)
        || (readMsg.toUpperCase().indexOf(CHUNKED_WITH_QUOTES) != -1))
      {
        // DEBUG:
        debug("processMessage", "Processing chunked data...");

        // Get the location of the first CFLF
        if ((index = readMsg.indexOf(CRLF + CRLF)) == -1)
        {
          throw new WSIException("Could not locate end of HTTP header.");
        }

        // Include the CRLF+CRLF in the index
        index += 4;

        // DEBUG:
        debug(
          "processMessage",
          "Add header before decoding chunked data: ["
            + readMsg.substring(0, index)
            + "]");

        // Add HTTP header to buffer
        messageBuffer.append(readMsg.substring(0, index));

        // If there is no more data (i.e. header only), then just indicate that there is more chunked data
        if (readMsg.length() == index)
        {
          chunkedData = new ChunkedData(this, true);

          // DEBUG:
          debug(
            "processMessage",
            "There is chunk data, but none in this part of the message.");
        }

        // Determine if the remainder of the data is complete (i.e. ends with [0][CRLF][Optional Footer][CRLF])
        else
        {
          // Create chunked data object
          chunkedData = new ChunkedData(this, readMsg.substring(index));

          if (!chunkedData.isMoreChunkedData())
          {
            chunkedData.decodeAndAddDataToBuffer(messageBuffer);
          }
        }
      }

      else if (chunkedData != null && chunkedData.isMoreChunkedData())
      {
        // DEBUG:
        debug("processMessage", "Processing MORE chunked data...");

        // Add data
        chunkedData.addData(readMsg);

        // Decode data
        if (!chunkedData.isMoreChunkedData())
        {
          chunkedData.decodeAndAddDataToBuffer(messageBuffer);
        }
      }

      // Else just append the data to the buffer
      else
      {
        // DEBUG:
        debug(
          "processMessage",
          "Add data to message entry buffer: [" + readMsg + "]");

        messageBuffer.append(readMsg);
      }
    }

    // Set updated message buffer and chunked data in message context   
    messageContext.messageBuffer = messageBuffer;
    messageContext.chunkedData = chunkedData;

    // Return message context
    return messageContext;
  }

  /**
   * Shutdown input socket and close input stream.
   * @param inSocket in socket.
   * @param inputStream input stream.
   */
  protected void stopInput(Socket inSocket, InputStream inputStream)
  {
    try
    {
      // If there is a input socket, then shutdown the input
      if (inSocket != null)
      {
        inSocket.shutdownInput();
      }

      // If there is an input stream then close it
      if (inputStream != null)
      {
        inputStream.close();
      }
    }
    catch (Exception e)
    {
      // Ignore since we are stopping everything
    }

    inputStream = null;
  }

  /**
   * Shutdown output socket and close output stream.
   * @param outSocket out socket.
   * @param outputStream output stream.
   */
  protected void stopOutput(Socket outSocket, OutputStream outputStream)
  {
    try
    {
      // If there is an output stream, then flush it
      if (outputStream != null)
      {
        outputStream.flush();
      }

      // If there is a input socket, then shutdown the input
      if (outSocket != null)
      {
        outSocket.shutdownOutput();
      }

      // If there is an output stream then close it
      if (outputStream != null)
      {
        outputStream.close();
      }
    }

    catch (Exception e)
    {
      // Ignore since we are stopping everything
    }

    outputStream = null;
  }

  /**
   * Shutdown handler.
   */
  public void shutdown()
  {
    // Stop both the input and output
    stopInput(this.inSocket, this.inputStream);
    stopOutput(this.outSocket, this.outputStream);
  }

  /**
   * Display debug messages.
   */
  void debug(String method, String message)
  {
    debug("SocketHandler", method, message);
  }

  /**
   * Display debug messages.
   */
  void debug(String className, String method, String message)
  {
    if (verbose)
      print(className, method, message);
  }

  /**
   * Display messages.
   */
  void print(String className, String method, String message)
  {
    System.out.println(
      "["
        + Thread.currentThread().getName()
        + "] ["
        + className
        + "."
        + method
        + "] ["
        + this.connectionType
        + "] "
        + message);
  }

  /**
   * Write data.
   */
  private void write(
    OutputStream outputStream,
    byte[] buffer,
    int start,
    int length)
    throws IOException
  {
    if (outputStream == null)
    {
      // DEBUG:
      debug("write", "Could not write buffer because output stream is null.");
    }
    else
    {
      // DEBUG:
      debug("write", "buffer: [" + new String(buffer, start, length) + "]");

      outputStream.write(buffer, start, length);
    }
  }

  /**
   * Check if message is complete.
   *
   * @param messageContext
   */
  private boolean isMessageComplete(MessageContext messageContext)
    throws WSIException
  {
    int index, index2, contentLen;
    boolean messageComplete = false;

    boolean moreChunkedData = messageContext.chunkedData.isMoreChunkedData();

    String message = messageContext.messageBuffer.toString();

    // Find the first CRLF + CRLF which marks the end of the HTTP header
    String httpHeader;
    index = Utils.getFirstCRLFCRLF(message);
    if (index == -1)
      httpHeader = message;
    else
      httpHeader = message.substring(0, index);

    // If chunked data, then complete only if there is no more data
    if (((httpHeader.toUpperCase().indexOf(CHUNKED) != -1)
      || (httpHeader.toUpperCase().indexOf(CHUNKED_WITH_QUOTES) != -1))
      && (!moreChunkedData))
    {
      debug(
        "isMessageComplete",
        "HTTP header indicates chunked data and there is no more chunked data");
      messageComplete = true;
    }

    // Check for content length
    else if ((index = httpHeader.toUpperCase().indexOf(CONTENT_LENGTH)) == -1)
    {
      debug("isMessageComplete", "HTTP header does not contain content length");

      // Should not have complete POST header without content length
      if (httpHeader.startsWith("POST"))
      {
        if (httpHeader.endsWith(CRLF + CRLF))
        {
          throw new WSIException("Could not locate content-length in HTTP POST header.");
        }

        messageComplete = false;
      }

      // This could be a GET, so see if the the complete header has been received
      else if (
        httpHeader.startsWith("GET")
          && (message.length() == httpHeader.length()
            && message.endsWith(CRLF + CRLF)))
      {
        messageComplete = true;
      }

      else
      {
        messageComplete = false;
      }
    }

    // If there is content length, then see if the entire message has been received
    else if ((index = httpHeader.toUpperCase().indexOf(CONTENT_LENGTH)) != -1)
    {
      // Find end of content length value
      index2 = httpHeader.indexOf(CRLF, index);

      debug("isMessageComplete", "CRLF: " + Utils.toHexString(CRLF));
      debug(
        "isMessageComplete",
        "httpHeader/index: " + Utils.toHexString(httpHeader.substring(index)));

      // Get content length
      contentLen =
        Integer
          .decode(
            httpHeader.substring(index + CONTENT_LENGTH.length() + 1, index2))
          .intValue();

      // DEBUG:
      debug("isMessageComplete", "contentLen: " + contentLen);

      // Find the first CRLF + CRLF which marks the end of the HTTP header
      index = Utils.getFirstCRLFCRLF(message);

      // DEBUG:
      debug(
        "isMessageComplete",
        "actual received message length: " + (message.length() - (index)));

      // If content length is equal to actual message content length, then message is complete
      if (contentLen == message.length() - index)
      {
        messageComplete = true;

        // DEBUG:
        debug(
          "isMessageComplete",
          "contentLen = actual received message length.");
      }
    }

    // Message is not complete
    else
    {
      messageComplete = false;
    }

    // DEBUG:
    debug("isMessageComplete", "messageComplete: " + messageComplete);

    return messageComplete;
  }

  /**
   * Log message.
   *
   * @param messageBuffer
   */
  private void logMessage(MessageContext messageContext) throws WSIException
  {
    // Determine sender and receiver host/port
    String senderHostAndPort, receiverHostAndPort;

    // Request
    if (connectionType.equals(MessageEntry.TYPE_REQUEST))
    {
      senderHostAndPort =
        inSocket.getInetAddress().getHostAddress() + ":" + inSocket.getPort();
      receiverHostAndPort = targetHost + ":" + outSocket.getPort();
    }

    // Response     
    else
    {
      senderHostAndPort = targetHost + ":" + inSocket.getPort();
      receiverHostAndPort =
        outSocket.getInetAddress().getHostAddress() + ":" + outSocket.getPort();
    }

    // Create message entry
    this.socketConnection.logMessage(
      conversationID,
      this.connectionType,
      messageContext.timestamp,
      senderHostAndPort,
      receiverHostAndPort,
      messageContext.messageBuffer,
      messageContext.bom,
      messageContext.encoding);
  }

  /**
   * Check for HTTP messages that should not be logged.
   */
  private boolean bypassMessage(String message)
  {
    boolean bypass = false;
    if ((message.startsWith("CONNECT"))
      || (message.startsWith("TRACE"))
      || (message.startsWith("DELETE"))
      || (message.startsWith("OPTIONS"))
      || (message.startsWith("HEAD")))
    {
      bypass = true;
    }

    return bypass;
  }

  // I18N: 2003.02.26 modified by K.Nakagome@BeaconIT
  /**
   * Set Encoding Parameters
   * @param messageFragment String of a HTTP message fragment.
   * @author K.Nakagome@BeaconIT Japan SIG
   */
  private void setEncoding(String messageFragment)
  {
    if (mimeCharset == null || mimeCharset.length() == 0)
    {
      mimeCharset = Utils.getHTTPCharset(messageFragment);
    }
    if (xmlEncoding == null || xmlEncoding.length() == 0)
    {
      xmlEncoding = Utils.getXMLEncoding(messageFragment);
    }
    return;
  }

  // I18N: 2003.02.26 modified by K.Nakagome@BeaconIT
  /**
   * Get Encoding Parameter
   * @return Character encoding of HTTP message.
   * @author K.Nakagome@BeaconIT Japan SIG
   */
  private String getEncoding()
  {
    String encoding = WSIConstants.DEFAULT_XML_ENCODING;
    if (mimeCharset != null && mimeCharset.length() > 0)
    {
      encoding = mimeCharset;
    }
    if (xmlEncoding != null && xmlEncoding.length() > 0)
    {
      encoding = xmlEncoding;
    }
    return encoding;
  }

  /**
   * Get the Byte Order Mark from the message (if there is one).
   */
  private int getBOM(byte[] message)
  {
    int bom = 0;

    byte FF = (byte) 0xFF;
    byte FE = (byte) 0xFE;
    byte EF = (byte) 0xEF;
    byte BB = (byte) 0xBB;
    byte BF = (byte) 0xBF;

    // Search through the byte array for CRLF+CRLF.  This will mark the end of the header.
    int i = Utils.getFirstCRLFCRLF(message, 0);
    if (i != -1)
    {
      // DEBUG:
      debug(
        "getBOM",
        "message[i]: "
          + message[i]
          + ", message[i+1]: "
          + message[i+1]);

      // Check for UTF-8 BOM
      if (((i + 2) < message.length)
        && message[i] == EF
        && message[i+1] == BB
        && message[i+2] == BF)
      {
        bom = 0xEFBBBF;
      }
      // Check for UTF-16 big-endian BOM
      else if (
        ((i+1) < message.length)
          && message[i] == FE
          && message[i + 1] == FF)
      {
        bom = 0xFEFF;
      }
      // Check for UTF-16 little-endian BOM
      else if (
        ((i+1) < message.length)
          && message[i] == FF
          && message[i+1] == FE)
      {
        bom = 0xFFFE;
      }
      // ADD: Do we need to check for other BOMs
    }
    return bom;
  }

  /**
   * Determine if the read is still waiting for data.
   */
  boolean isReadWaiting()
  {
    boolean readWaiting = false;

    try
    {
      // DEBUG:
      debug(
        "isReadWaiting",
        "inSocket.getInputStream().available(): "
          + inSocket.getInputStream().available());

      if (inSocket.getInputStream().available() == 0)
      {
        readWaiting = true;
      }
    }

    catch (IOException ioe)
    {
    }

    return readWaiting;
  }

  /**
   * Get read timed out flag.
   */
  boolean isReadTimedOut()
  {
    return this.readTimedOut;
  }

  /**
   * Message context contains information about the message that is being processed.
   */
  class MessageContext
  {
    StringBuffer messageBuffer;
    ChunkedData chunkedData;
    String timestamp;
    int bom;
    String encoding;

    /**
     * Create new message context.
     */
    MessageContext()
    {
      messageBuffer = new StringBuffer();
      chunkedData = new ChunkedData();
      timestamp = null;
      bom = 0;
      encoding = WSIConstants.DEFAULT_XML_ENCODING;
    }
  }
}
TOP

Related Classes of org.eclipse.wst.wsi.internal.core.monitor.SocketHandler

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.