コード例 #1
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;
    }
  }
コード例 #2
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;
    }
  }
コード例 #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
  /**
   * 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();
  }
コード例 #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
/**
 * Abstract implementation of the Store interface to support most of the functionality required by a
 * Store.
 *
 * @author Bip Thelin
 */
public abstract class StoreBase extends LifecycleBase implements Store {

  // ----------------------------------------------------- Instance Variables

  /** The descriptive information about this implementation. */
  private static final String info = "StoreBase/1.0";

  /** Name to register for this Store, used for logging. */
  private static String storeName = "StoreBase";

  /** The property change support for this component. */
  private PropertyChangeSupport support = new PropertyChangeSupport(this);

  /** The string manager for this package. */
  private static final StringManager3 sm = StringManager3.getManager(Constants16.getPackage());

  /** The Manager with which this JDBCStore is associated. */
  private Manager manager;

  // ------------------------------------------------------------- Properties

  /** Return the info for this Store. */
  @Override
  public String getInfo() {
    return (info);
  }

  /** Return the name for this Store, used for logging. */
  public String getStoreName() {
    return (storeName);
  }

  /**
   * Set the Manager with which this Store is associated.
   *
   * @param manager The newly associated Manager
   */
  @Override
  public void setManager(Manager manager) {
    Manager oldManager = this.manager;
    this.manager = manager;
    support.firePropertyChange("manager", oldManager, this.manager);
  }

  /** Return the Manager with which the Store is associated. */
  @Override
  public Manager getManager() {
    return (this.manager);
  }

  // --------------------------------------------------------- Public Methods

  /**
   * Add a property change listener to this component.
   *
   * @param listener a value of type 'PropertyChangeListener'
   */
  @Override
  public void addPropertyChangeListener(PropertyChangeListener listener) {
    support.addPropertyChangeListener(listener);
  }

  /**
   * Remove a property change listener from this component.
   *
   * @param listener The listener to remove
   */
  @Override
  public void removePropertyChangeListener(PropertyChangeListener listener) {
    support.removePropertyChangeListener(listener);
  }

  // --------------------------------------------------------- Protected
  // Methods

  /**
   * Called by our background reaper thread to check if Sessions saved in our store are subject of
   * being expired. If so expire the Session and remove it from the Store.
   */
  public void processExpires() {
    String[] keys = null;

    if (!getState().isAvailable()) {
      return;
    }

    try {
      keys = keys();
    } catch (IOException e) {
      manager.getContainer().getLogger().error("Error getting keys", e);
      return;
    }
    if (manager.getContainer().getLogger().isDebugEnabled()) {
      manager
          .getContainer()
          .getLogger()
          .debug(getStoreName() + ": processExpires check number of " + keys.length + " sessions");
    }

    long timeNow = System.currentTimeMillis();

    for (int i = 0; i < keys.length; i++) {
      try {
        StandardSession session = (StandardSession) load(keys[i]);
        if (session == null) {
          continue;
        }
        int timeIdle = (int) ((timeNow - session.getThisAccessedTime()) / 1000L);
        if (timeIdle < session.getMaxInactiveInterval()) {
          continue;
        }
        if (manager.getContainer().getLogger().isDebugEnabled()) {
          manager
              .getContainer()
              .getLogger()
              .debug(getStoreName() + ": processExpires expire store session " + keys[i]);
        }
        boolean isLoaded = false;
        if (manager instanceof PersistentManagerBase) {
          isLoaded = ((PersistentManagerBase) manager).isLoaded(keys[i]);
        } else {
          try {
            if (manager.findSession(keys[i]) != null) {
              isLoaded = true;
            }
          } catch (IOException ioe) {
            // Ignore - session will be expired
          }
        }
        if (isLoaded) {
          // recycle old backup session
          session.recycle();
        } else {
          // expire swapped out session
          session.expire();
        }
        remove(keys[i]);
      } catch (Exception e) {
        manager.getContainer().getLogger().error("Session: " + keys[i] + "; ", e);
        try {
          remove(keys[i]);
        } catch (IOException e2) {
          manager.getContainer().getLogger().error("Error removing key", e2);
        }
      }
    }
  }

  @Override
  protected void initInternal() {
    // NOOP
  }

  /**
   * Start this component and implement the requirements of {@link LifecycleBase#startInternal()}.
   *
   * @exception LifecycleException if this component detects a fatal error that prevents this
   *     component from being used
   */
  @Override
  protected synchronized void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);
  }

  /**
   * Stop this component and implement the requirements of {@link LifecycleBase#stopInternal()}.
   *
   * @exception LifecycleException if this component detects a fatal error that prevents this
   *     component from being used
   */
  @Override
  protected synchronized void stopInternal() throws LifecycleException {

    setState(LifecycleState.STOPPING);
  }

  @Override
  protected void destroyInternal() {
    // NOOP
  }

  /** Return a String rendering of this object. */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder(this.getClass().getName());
    sb.append('[');
    if (manager == null) {
      sb.append("Manager is null");
    } else {
      sb.append(manager);
    }
    sb.append(']');
    return sb.toString();
  }

  public static StringManager3 getSm() {
    return sm;
  }

  public PropertyChangeSupport getSupport() {
    return support;
  }

  public void setSupport(PropertyChangeSupport support) {
    this.support = support;
  }

  public static void setStoreName(String storeName) {
    StoreBase.storeName = storeName;
  }
}
コード例 #7
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;
  }
}