/** * 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; } }
/** * 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; } }
/** * 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; } }
/** * 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(); }
/** * 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; } }
/** * 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; } }
/** * 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; } }