Пример #1
0
  /**
   * Generic function to send either a ping or a pong.
   *
   * @param data Optional message.
   * @param opcode The byte to include as the opcode.
   * @throws IOException If an error occurs writing to the client
   */
  private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        doFlush(false);

        upgradeOutbound.write(0x80 | opcode);
        if (data == null) {
          upgradeOutbound.write(0);
        } else {
          upgradeOutbound.write(data.limit() - data.position());
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }

        upgradeOutbound.flush();
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
Пример #2
0
  /**
   * Adds the data to the buffer for binary data. If a textual message is currently in progress that
   * message will be completed and a new binary message started. If the buffer for binary data is
   * full, the buffer will be flushed and a new binary continuation fragment started.
   *
   * @param b The byte (only the least significant byte is used) of data to send to the client.
   * @throws IOException If a flush is required and an error occurs writing the WebSocket frame to
   *     the client
   */
  public void writeBinaryData(int b) throws IOException {
    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (bb.position() == bb.capacity()) {
          doFlush(false);
        }
        if (text == null) {
          text = Boolean.FALSE;
        } else if (text == Boolean.TRUE) {
          // Flush the character data
          flush();
          text = Boolean.FALSE;
        }
        bb.put((byte) (b & 0xFF));
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
Пример #3
0
  /**
   * Flush any message (binary or textual) that may be buffered and then send a WebSocket text
   * message as a single frame with the provided buffer as the payload of the message.
   *
   * @param msgCb The buffer containing the payload
   * @throws IOException If an error occurs writing to the client
   */
  public void writeTextMessage(CharBuffer msgCb) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (text != null) {
          // Empty the buffer
          flush();
        }
        text = Boolean.TRUE;
        doWriteText(msgCb, true);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
Пример #4
0
  private boolean validateCloseStatus(int status) {

    if (status == Constants23.getStatusCloseNormal()
        || status == Constants23.getStatusShutdown()
        || status == Constants23.getStatusProtocolError()
        || status == Constants23.getStatusUnexpectedDataType()
        || status == Constants23.getStatusBadData()
        || status == Constants23.getStatusPolicyViolation()
        || status == Constants23.getStatusMessageTooLarge()
        || status == Constants23.getStatusRequiredExtension()
        || status == Constants23.getStatusUnexpectedCondition()
        || (status > 2999 && status < 5000)) {
      // Other 1xxx reserved / not permitted
      // 2xxx reserved
      // 3xxx framework defined
      // 4xxx application defined
      return true;
    }
    // <1000 unused
    // >4999 undefined
    return false;
  }
Пример #5
0
 /**
  * Flush any message (binary or textual) that may be buffered.
  *
  * @throws IOException If an error occurs writing to the client
  */
 public void flush() throws IOException {
   try {
     synchronized (stateLock) {
       if (closed) {
         throw new IOException(sm.getString("outbound.closed"));
       }
       doFlush(true);
     }
   } catch (IOException ioe) {
     // Any IOException is terminal. Make sure the Inbound side knows
     // that something went wrong.
     // The exception handling needs to be outside of the sync to avoid
     // possible deadlocks (e.g. BZ55524) when triggering the inbound
     // close as that will execute user code
     streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
     throw ioe;
   }
 }
Пример #6
0
  /**
   * Send a close message to the client
   *
   * @param status Must be a valid status code or zero to send no code
   * @param data Optional message. If message is defined, a valid status code must be provided.
   * @throws IOException If an error occurs writing to the client
   */
  public void close(int status, ByteBuffer data) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          return;
        }

        // Send any partial data we have
        try {
          doFlush(false);
        } finally {
          closed = true;
        }

        upgradeOutbound.write(0x88);
        if (status == 0) {
          upgradeOutbound.write(0);
        } else if (data == null || data.position() == data.limit()) {
          upgradeOutbound.write(2);
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
        } else {
          upgradeOutbound.write(2 + data.limit() - data.position());
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }
        upgradeOutbound.flush();

        bb = null;
        cb = null;
        upgradeOutbound = null;
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }
Пример #7
0
  /**
   * Respond to a client close by sending a close that echoes the status code and message.
   *
   * @param frame The close frame received from a client
   * @throws IOException If an error occurs writing to the client
   */
  protected void close(WsFrame frame) throws IOException {
    if (frame.getPayLoadLength() > 0) {
      // Must be status (2 bytes) plus optional message
      if (frame.getPayLoadLength() == 1) {
        throw new IOException();
      }
      int status = (frame.getPayLoad().get() & 0xFF) << 8;
      status += frame.getPayLoad().get() & 0xFF;

      if (validateCloseStatus(status)) {
        // Echo the status back to the client
        close(status, frame.getPayLoad());
      } else {
        // Invalid close code
        close(Constants23.getStatusProtocolError(), null);
      }
    } else {
      // No status
      close(0, null);
    }
  }
Пример #8
0
 /**
  * Send a ping message to the client
  *
  * @param data Optional message.
  * @throws IOException If an error occurs writing to the client
  */
 public void ping(ByteBuffer data) throws IOException {
   sendControlMessage(data, Constants23.getOpcodePing());
 }
Пример #9
0
/**
 * Provides the means to write WebSocket messages to the client. All methods that write to the
 * client (or update a buffer that is later written to the client) are synchronized to prevent
 * multiple threads trying to write to the client at the same time.
 *
 * @deprecated Replaced by the JSR356 WebSocket 1.1 implementation and will be removed in Tomcat
 *     8.0.x.
 */
@Deprecated
public class WsOutbound {

  private static final StringManager3 sm = StringManager3.getManager(Constants23.getPackage());
  private static final int DEFAULT_BUFFER_SIZE = 8192;

  /**
   * This state lock is used rather than synchronized methods to allow error handling to be managed
   * outside of the synchronization else deadlocks may occur such as
   * https://issues.apache.org/bugzilla/show_bug.cgi?id=55524
   */
  private final Object stateLock = new Object();

  private UpgradeOutbound upgradeOutbound;
  private StreamInbound streamInbound;
  private ByteBuffer bb;
  private CharBuffer cb;
  private boolean closed = false;
  private Boolean text = null;
  private boolean firstFrame = true;

  public WsOutbound(UpgradeOutbound upgradeOutbound, StreamInbound streamInbound) {
    this(upgradeOutbound, streamInbound, DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE);
  }

  public WsOutbound(
      UpgradeOutbound upgradeOutbound,
      StreamInbound streamInbound,
      int byteBufferSize,
      int charBufferSize) {
    this.upgradeOutbound = upgradeOutbound;
    this.streamInbound = streamInbound;
    this.bb = ByteBuffer.allocate(byteBufferSize);
    this.cb = CharBuffer.allocate(charBufferSize);
  }

  /**
   * Adds the data to the buffer for binary data. If a textual message is currently in progress that
   * message will be completed and a new binary message started. If the buffer for binary data is
   * full, the buffer will be flushed and a new binary continuation fragment started.
   *
   * @param b The byte (only the least significant byte is used) of data to send to the client.
   * @throws IOException If a flush is required and an error occurs writing the WebSocket frame to
   *     the client
   */
  public void writeBinaryData(int b) throws IOException {
    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (bb.position() == bb.capacity()) {
          doFlush(false);
        }
        if (text == null) {
          text = Boolean.FALSE;
        } else if (text == Boolean.TRUE) {
          // Flush the character data
          flush();
          text = Boolean.FALSE;
        }
        bb.put((byte) (b & 0xFF));
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Adds the data to the buffer for textual data. If a binary message is currently in progress that
   * message will be completed and a new textual message started. If the buffer for textual data is
   * full, the buffer will be flushed and a new textual continuation fragment started.
   *
   * @param c The character to send to the client.
   * @throws IOException If a flush is required and an error occurs writing the WebSocket frame to
   *     the client
   */
  public void writeTextData(char c) throws IOException {
    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (cb.position() == cb.capacity()) {
          doFlush(false);
        }

        if (text == null) {
          text = Boolean.TRUE;
        } else if (text == Boolean.FALSE) {
          // Flush the binary data
          flush();
          text = Boolean.TRUE;
        }
        cb.append(c);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Flush any message (binary or textual) that may be buffered and then send a WebSocket binary
   * message as a single frame with the provided buffer as the payload of the message.
   *
   * @param msgBb The buffer containing the payload
   * @throws IOException If an error occurs writing to the client
   */
  public void writeBinaryMessage(ByteBuffer msgBb) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (text != null) {
          // Empty the buffer
          flush();
        }
        text = Boolean.FALSE;
        doWriteBytes(msgBb, true);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Flush any message (binary or textual) that may be buffered and then send a WebSocket text
   * message as a single frame with the provided buffer as the payload of the message.
   *
   * @param msgCb The buffer containing the payload
   * @throws IOException If an error occurs writing to the client
   */
  public void writeTextMessage(CharBuffer msgCb) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        if (text != null) {
          // Empty the buffer
          flush();
        }
        text = Boolean.TRUE;
        doWriteText(msgCb, true);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Flush any message (binary or textual) that may be buffered.
   *
   * @throws IOException If an error occurs writing to the client
   */
  public void flush() throws IOException {
    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }
        doFlush(true);
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  private void doFlush(boolean finalFragment) throws IOException {
    if (text == null) {
      // No data
      return;
    }
    if (text.booleanValue()) {
      cb.flip();
      doWriteText(cb, finalFragment);
    } else {
      bb.flip();
      doWriteBytes(bb, finalFragment);
    }
  }

  /**
   * Respond to a client close by sending a close that echoes the status code and message.
   *
   * @param frame The close frame received from a client
   * @throws IOException If an error occurs writing to the client
   */
  protected void close(WsFrame frame) throws IOException {
    if (frame.getPayLoadLength() > 0) {
      // Must be status (2 bytes) plus optional message
      if (frame.getPayLoadLength() == 1) {
        throw new IOException();
      }
      int status = (frame.getPayLoad().get() & 0xFF) << 8;
      status += frame.getPayLoad().get() & 0xFF;

      if (validateCloseStatus(status)) {
        // Echo the status back to the client
        close(status, frame.getPayLoad());
      } else {
        // Invalid close code
        close(Constants23.getStatusProtocolError(), null);
      }
    } else {
      // No status
      close(0, null);
    }
  }

  private boolean validateCloseStatus(int status) {

    if (status == Constants23.getStatusCloseNormal()
        || status == Constants23.getStatusShutdown()
        || status == Constants23.getStatusProtocolError()
        || status == Constants23.getStatusUnexpectedDataType()
        || status == Constants23.getStatusBadData()
        || status == Constants23.getStatusPolicyViolation()
        || status == Constants23.getStatusMessageTooLarge()
        || status == Constants23.getStatusRequiredExtension()
        || status == Constants23.getStatusUnexpectedCondition()
        || (status > 2999 && status < 5000)) {
      // Other 1xxx reserved / not permitted
      // 2xxx reserved
      // 3xxx framework defined
      // 4xxx application defined
      return true;
    }
    // <1000 unused
    // >4999 undefined
    return false;
  }

  /**
   * Send a close message to the client
   *
   * @param status Must be a valid status code or zero to send no code
   * @param data Optional message. If message is defined, a valid status code must be provided.
   * @throws IOException If an error occurs writing to the client
   */
  public void close(int status, ByteBuffer data) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          return;
        }

        // Send any partial data we have
        try {
          doFlush(false);
        } finally {
          closed = true;
        }

        upgradeOutbound.write(0x88);
        if (status == 0) {
          upgradeOutbound.write(0);
        } else if (data == null || data.position() == data.limit()) {
          upgradeOutbound.write(2);
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
        } else {
          upgradeOutbound.write(2 + data.limit() - data.position());
          upgradeOutbound.write(status >>> 8);
          upgradeOutbound.write(status);
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }
        upgradeOutbound.flush();

        bb = null;
        cb = null;
        upgradeOutbound = null;
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Send a pong message to the client
   *
   * @param data Optional message.
   * @throws IOException If an error occurs writing to the client
   */
  public void pong(ByteBuffer data) throws IOException {
    sendControlMessage(data, Constants23.getOpcodePong());
  }

  /**
   * Send a ping message to the client
   *
   * @param data Optional message.
   * @throws IOException If an error occurs writing to the client
   */
  public void ping(ByteBuffer data) throws IOException {
    sendControlMessage(data, Constants23.getOpcodePing());
  }

  /**
   * Generic function to send either a ping or a pong.
   *
   * @param data Optional message.
   * @param opcode The byte to include as the opcode.
   * @throws IOException If an error occurs writing to the client
   */
  private void sendControlMessage(ByteBuffer data, byte opcode) throws IOException {

    try {
      synchronized (stateLock) {
        if (closed) {
          throw new IOException(sm.getString("outbound.closed"));
        }

        doFlush(false);

        upgradeOutbound.write(0x80 | opcode);
        if (data == null) {
          upgradeOutbound.write(0);
        } else {
          upgradeOutbound.write(data.limit() - data.position());
          upgradeOutbound.write(data.array(), data.position(), data.limit() - data.position());
        }

        upgradeOutbound.flush();
      }
    } catch (IOException ioe) {
      // Any IOException is terminal. Make sure the Inbound side knows
      // that something went wrong.
      // The exception handling needs to be outside of the sync to avoid
      // possible deadlocks (e.g. BZ55524) when triggering the inbound
      // close as that will execute user code
      streamInbound.doOnClose(Constants23.getStatusClosedUnexpectedly());
      throw ioe;
    }
  }

  /**
   * Writes the provided bytes as the payload in a new WebSocket frame.
   *
   * @param buffer The bytes to include in the payload.
   * @param finalFragment Do these bytes represent the final fragment of a WebSocket message?
   * @throws IOException
   */
  private void doWriteBytes(ByteBuffer buffer, boolean finalFragment) throws IOException {

    if (closed) {
      throw new IOException(sm.getString("outbound.closed"));
    }

    // Work out the first byte
    int first = 0x00;
    if (finalFragment) {
      first = first + 0x80;
    }
    if (firstFrame) {
      if (text.booleanValue()) {
        first = first + 0x1;
      } else {
        first = first + 0x2;
      }
    }
    // Continuation frame is OpCode 0
    upgradeOutbound.write(first);

    if (buffer.limit() < 126) {
      upgradeOutbound.write(buffer.limit());
    } else if (buffer.limit() < 65536) {
      upgradeOutbound.write(126);
      upgradeOutbound.write(buffer.limit() >>> 8);
      upgradeOutbound.write(buffer.limit() & 0xFF);
    } else {
      // Will never be more than 2^31-1
      upgradeOutbound.write(127);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(0);
      upgradeOutbound.write(buffer.limit() >>> 24);
      upgradeOutbound.write(buffer.limit() >>> 16);
      upgradeOutbound.write(buffer.limit() >>> 8);
      upgradeOutbound.write(buffer.limit() & 0xFF);
    }

    // Write the content
    upgradeOutbound.write(buffer.array(), buffer.arrayOffset(), buffer.limit());
    upgradeOutbound.flush();

    // Reset
    if (finalFragment) {
      text = null;
      firstFrame = true;
    } else {
      firstFrame = false;
    }
    bb.clear();
  }

  /*
   * Convert the textual message to bytes and then output it.
   */
  private void doWriteText(CharBuffer buffer, boolean finalFragment) throws IOException {
    CharsetEncoder encoder = B2CConverter.getUtf8().newEncoder();
    do {
      CoderResult cr = encoder.encode(buffer, bb, true);
      if (cr.isError()) {
        cr.throwException();
      }
      bb.flip();
      if (buffer.hasRemaining()) {
        doWriteBytes(bb, false);
      } else {
        doWriteBytes(bb, finalFragment);
      }
    } while (buffer.hasRemaining());

    // Reset - bb will be cleared in doWriteBytes()
    cb.clear();
  }

  public UpgradeOutbound getUpgradeOutbound() {
    return upgradeOutbound;
  }

  public void setUpgradeOutbound(UpgradeOutbound upgradeOutbound) {
    this.upgradeOutbound = upgradeOutbound;
  }

  public StreamInbound getStreamInbound() {
    return streamInbound;
  }

  public void setStreamInbound(StreamInbound streamInbound) {
    this.streamInbound = streamInbound;
  }

  public ByteBuffer getBb() {
    return bb;
  }

  public void setBb(ByteBuffer bb) {
    this.bb = bb;
  }

  public CharBuffer getCb() {
    return cb;
  }

  public void setCb(CharBuffer cb) {
    this.cb = cb;
  }

  public boolean isClosed() {
    return closed;
  }

  public void setClosed(boolean closed) {
    this.closed = closed;
  }

  public Boolean getText() {
    return text;
  }

  public void setText(Boolean text) {
    this.text = text;
  }

  public boolean isFirstFrame() {
    return firstFrame;
  }

  public void setFirstFrame(boolean firstFrame) {
    this.firstFrame = firstFrame;
  }

  public static StringManager3 getSm() {
    return sm;
  }

  public static int getDefaultBufferSize() {
    return DEFAULT_BUFFER_SIZE;
  }

  public Object getStateLock() {
    return stateLock;
  }
}