You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2012/03/16 00:17:48 UTC
svn commit: r1301264 - in /tomcat/tc7.0.x/trunk: ./
java/org/apache/catalina/websocket/ java/org/apache/coyote/http11/upgrade/
test/org/apache/catalina/websocket/
webapps/examples/WEB-INF/classes/websocket/
Author: markt
Date: Thu Mar 15 23:17:47 2012
New Revision: 1301264
URL: http://svn.apache.org/viewvc?rev=1301264&view=rev
Log:
More WebSocket
Added:
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java (contents, props changed)
- copied, changed from r1293154, tomcat/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java
- copied, changed from r1292670, tomcat/trunk/java/org/apache/catalina/websocket/WsFrame.java
Removed:
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrameHeader.java
Modified:
tomcat/tc7.0.x/trunk/ (props changed)
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/MessageInbound.java
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WebSocketServlet.java
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java
tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java
tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java
tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoStream.java
Propchange: tomcat/tc7.0.x/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Mar 15 23:17:47 2012
@@ -1 +1 @@
-/tomcat/trunk:1156115-1157160,1157162-1157859,1157862-1157942,1157945-1160347,1160349-1163716,1163718-1166689,1166691-1174340,1174342-1175596,1175598-1175611,1175613-1175932,1175934-1177783,1177785-1177980,1178006-1180720,1180722-1183094,1183096-1187753,1187755,1187775,1187801,1187806,1187809,1187826-1188312,1188314-1188401,1188646-1188840,1188842-1190176,1190178-1195223,1195225-1195953,1195955,1195957-1201238,1201240-1203345,1203347-1206623,1206625-1208046,1208073,1208096,1208114,1208145,1208772,1209194-1212125,1212127-1220291,1220293,1220295-1221321,1221323-1222328,1222332-1222401,1222405-1222795,1222850-1222950,1222969-1225326,1225328-1225463,1225465,1225627,1225629-1226534,1226536-1228908,1228911-1228923,1228927-1229532,1229534-1230766,1230768-1231625,1231627-1233414,1233419-1235207,1235209-1237425,1237427,1237429-1237977,1237981,1237985,1237995,1238070,1238073,1239024-1239048,1239050-1239062,1239135,1239256,1239258-1239485,1239785-1240046,1240101,1240106,1240109,1240112
,1240114,1240116,1240118,1240121,1240329,1240474-1240850,1240857,1241087,1241160,1241408-1241822,1241908-1241909,1241912-1242110,1242371-1292130,1292334,1292338,1292345-1292347,1293155,1293831-1293832,1295998,1296284,1297014-1297015,1297017,1297158,1297177,1297202,1297209,1297213,1297717,1297722,1297729,1297768,1297778,1297818,1297828,1297979,1297987,1298121,1298140,1298590,1298592,1298628-1298629,1298794,1298983-1298984,1299020,1299034,1299819,1300154-1300155,1300569,1300948
+/tomcat/trunk:1156115-1157160,1157162-1157859,1157862-1157942,1157945-1160347,1160349-1163716,1163718-1166689,1166691-1174340,1174342-1175596,1175598-1175611,1175613-1175932,1175934-1177783,1177785-1177980,1178006-1180720,1180722-1183094,1183096-1187753,1187755,1187775,1187801,1187806,1187809,1187826-1188312,1188314-1188401,1188646-1188840,1188842-1190176,1190178-1195223,1195225-1195953,1195955,1195957-1201238,1201240-1203345,1203347-1206623,1206625-1208046,1208073,1208096,1208114,1208145,1208772,1209194-1212125,1212127-1220291,1220293,1220295-1221321,1221323-1222328,1222332-1222401,1222405-1222795,1222850-1222950,1222969-1225326,1225328-1225463,1225465,1225627,1225629-1226534,1226536-1228908,1228911-1228923,1228927-1229532,1229534-1230766,1230768-1231625,1231627-1233414,1233419-1235207,1235209-1237425,1237427,1237429-1237977,1237981,1237985,1237995,1238070,1238073,1239024-1239048,1239050-1239062,1239135,1239256,1239258-1239485,1239785-1240046,1240101,1240106,1240109,1240112
,1240114,1240116,1240118,1240121,1240329,1240474-1240850,1240857,1241087,1241160,1241408-1241822,1241908-1241909,1241912-1242110,1242371-1292130,1292134-1292458,1292464-1292670,1292672-1292776,1292780-1293392,1293831-1293832,1295998,1296284,1297014-1297015,1297017,1297158,1297177,1297202,1297209,1297213,1297717,1297722,1297729,1297768,1297778,1297818,1297828,1297979,1297987,1298121,1298140,1298590,1298592,1298628-1298629,1298794,1298983-1298984,1299020,1299034,1299819,1300154-1300155,1300569,1300948
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/LocalStrings.properties Thu Mar 15 23:17:47 2012
@@ -11,4 +11,15 @@
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
-# limitations under the License.
\ No newline at end of file
+# limitations under the License.
+
+frame.eos=The end of the stream was reached before the expected number of payload bytes could be read
+frame.invalidUtf8=A sequence of bytes was received that did not represent valid UTF-8
+frame.notMasked=The client frame was not masked but all client frames must be masked
+
+is.notContinutation=A frame with the OpCode [{0}] was recieved when a continuation frame was expected
+is.unknownOpCode=A frame with the unrecognized OpCode [{0}] was received
+
+message.bufferTooSmall=The buffer is not big enough to contain the message currently being processed
+
+outbound.closed=The WebSocket connection has been closed
\ No newline at end of file
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/MessageInbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/MessageInbound.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/MessageInbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/MessageInbound.java Thu Mar 15 23:17:47 2012
@@ -22,17 +22,30 @@ import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Base implementation of the class used to process WebSocket connections based
+ * on messages. Applications should extend this class to provide application
+ * specific functionality. Applications that wish to operate on a stream basis
+ * rather than a message basis should use {@link StreamInbound}.
+ */
public abstract class MessageInbound extends StreamInbound {
+ private static final StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
// 2MB - like maxPostSize
private int byteBufferMaxSize = 2097152;
private int charBufferMaxSize = 2097152;
- ByteBuffer bb = ByteBuffer.allocate(8192);
- CharBuffer cb = CharBuffer.allocate(8192);
+ private ByteBuffer bb = ByteBuffer.allocate(8192);
+ private CharBuffer cb = CharBuffer.allocate(8192);
+
@Override
- protected void onBinaryData(InputStream is) throws IOException {
+ protected final void onBinaryData(InputStream is) throws IOException {
int read = 0;
while (read > -1) {
bb.position(bb.position() + read);
@@ -46,8 +59,9 @@ public abstract class MessageInbound ext
bb.clear();
}
+
@Override
- protected void onTextData(Reader r) throws IOException {
+ protected final void onTextData(Reader r) throws IOException {
int read = 0;
while (read > -1) {
cb.position(cb.position() + read);
@@ -61,11 +75,11 @@ public abstract class MessageInbound ext
cb.clear();
}
+
private void resizeByteBuffer() throws IOException {
int maxSize = getByteBufferMaxSize();
if (bb.limit() >= maxSize) {
- // TODO i18n
- throw new IOException("Buffer not big enough for message");
+ throw new IOException(sm.getString("message.bufferTooSmall"));
}
long newSize = bb.limit() * 2;
@@ -80,11 +94,11 @@ public abstract class MessageInbound ext
bb = newBuffer;
}
+
private void resizeCharBuffer() throws IOException {
int maxSize = getCharBufferMaxSize();
if (cb.limit() >= maxSize) {
- // TODO i18n
- throw new IOException("Buffer not big enough for message");
+ throw new IOException(sm.getString("message.bufferTooSmall"));
}
long newSize = cb.limit() * 2;
@@ -99,24 +113,70 @@ public abstract class MessageInbound ext
cb = newBuffer;
}
- public int getByteBufferMaxSize() {
+
+ /**
+ * Obtain the current maximum size (in bytes) of the buffer used for binary
+ * messages.
+ */
+ public final int getByteBufferMaxSize() {
return byteBufferMaxSize;
}
- public void setByteBufferMaxSize(int byteBufferMaxSize) {
+
+ /**
+ * Set the maximum size (in bytes) of the buffer used for binary messages.
+ */
+ public final void setByteBufferMaxSize(int byteBufferMaxSize) {
this.byteBufferMaxSize = byteBufferMaxSize;
}
- public int getCharBufferMaxSize() {
+
+ /**
+ * Obtain the current maximum size (in characters) of the buffer used for
+ * binary messages.
+ */
+ public final int getCharBufferMaxSize() {
return charBufferMaxSize;
}
- public void setCharBufferMaxSize(int charBufferMaxSize) {
+
+ /**
+ * Set the maximum size (in characters) of the buffer used for textual
+ * messages.
+ */
+ public final void setCharBufferMaxSize(int charBufferMaxSize) {
this.charBufferMaxSize = charBufferMaxSize;
}
+
+ /**
+ * This method is called when there is a binary WebSocket message available
+ * to process. The message is presented via a ByteBuffer and may have been
+ * formed from one or more frames. The number of frames used to transmit the
+ * message is not made visible to the application.
+ *
+ * @param message The WebSocket message
+ *
+ * @throws IOException If a problem occurs processing the message. Any
+ * exception will trigger the closing of the WebSocket
+ * connection.
+ */
protected abstract void onBinaryMessage(ByteBuffer message)
throws IOException;
+
+
+ /**
+ * This method is called when there is a textual WebSocket message available
+ * to process. The message is presented via a CharBuffer and may have been
+ * formed from one or more frames. The number of frames used to transmit the
+ * message is not made visible to the application.
+ *
+ * @param message The WebSocket message
+ *
+ * @throws IOException If a problem occurs processing the message. Any
+ * exception will trigger the closing of the WebSocket
+ * connection.
+ */
protected abstract void onTextMessage(CharBuffer message)
throws IOException;
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/StreamInbound.java Thu Mar 15 23:17:47 2012
@@ -20,126 +20,130 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
-import java.nio.ByteBuffer;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.UnmappableCharacterException;
import org.apache.coyote.http11.upgrade.UpgradeInbound;
import org.apache.coyote.http11.upgrade.UpgradeOutbound;
import org.apache.coyote.http11.upgrade.UpgradeProcessor;
-import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+/**
+ * Base implementation of the class used to process WebSocket connections based
+ * on streams. Applications should extend this class to provide application
+ * specific functionality. Applications that wish to operate on a message basis
+ * rather than a stream basis should use {@link MessageInbound}.
+ */
public abstract class StreamInbound implements UpgradeInbound {
private UpgradeProcessor<?> processor = null;
private WsOutbound outbound;
@Override
- public void setUpgradeOutbound(UpgradeOutbound upgradeOutbound) {
+ public final void setUpgradeOutbound(UpgradeOutbound upgradeOutbound) {
outbound = new WsOutbound(upgradeOutbound);
}
@Override
- public void setUpgradeProcessor(UpgradeProcessor<?> processor) {
+ public final void setUpgradeProcessor(UpgradeProcessor<?> processor) {
this.processor = processor;
}
- public WsOutbound getOutbound() {
+
+ /**
+ * Obtain the outbound side of this WebSocket connection used for writing
+ * data to the client.
+ */
+ public final WsOutbound getWsOutbound() {
return outbound;
}
+
@Override
- public SocketState onData() throws IOException {
+ public final SocketState onData() throws IOException {
// Must be start the start of a frame or series of frames
- WsInputStream wsIs = new WsInputStream(processor);
- WsFrameHeader header = wsIs.getFrameHeader();
+ try {
+ WsInputStream wsIs = new WsInputStream(processor, getWsOutbound());
- // TODO User defined extensions may define values for rsv
- if (header.getRsv() > 0) {
- getOutbound().close(1002, null);
- return SocketState.CLOSED;
- }
-
- byte opCode = header.getOpCode();
-
- if (opCode == Constants.OPCODE_BINARY) {
- onBinaryData(wsIs);
- return SocketState.UPGRADED;
- } else if (opCode == Constants.OPCODE_TEXT) {
- InputStreamReader r =
- new InputStreamReader(wsIs, B2CConverter.UTF_8);
- onTextData(r);
- return SocketState.UPGRADED;
- }
+ WsFrame frame = wsIs.getFrame();
- // Must be a control frame and control frames:
- // - have a limited payload length
- // - must not be fragmented
- if (wsIs.getPayloadLength() > 125 || !wsIs.getFrameHeader().getFin()) {
- getOutbound().close(1002, null);
- return SocketState.CLOSED;
- }
+ // TODO User defined extensions may define values for rsv
+ if (frame.getRsv() > 0) {
+ getWsOutbound().close(1002, null);
+ return SocketState.CLOSED;
+ }
- if (opCode == Constants.OPCODE_CLOSE){
- doClose(wsIs);
- return SocketState.CLOSED;
- } else if (opCode == Constants.OPCODE_PING) {
- doPing(wsIs);
- return SocketState.UPGRADED;
- } else if (opCode == Constants.OPCODE_PONG) {
- doPong(wsIs);
- return SocketState.UPGRADED;
- }
+ byte opCode = frame.getOpCode();
- getOutbound().close(1002, null);
- return SocketState.CLOSED;
- }
+ if (opCode == Constants.OPCODE_BINARY) {
+ onBinaryData(wsIs);
+ return SocketState.UPGRADED;
+ } else if (opCode == Constants.OPCODE_TEXT) {
+ InputStreamReader r =
+ new InputStreamReader(wsIs, new Utf8Decoder());
+ onTextData(r);
+ return SocketState.UPGRADED;
+ }
- private void doClose(InputStream is) throws IOException {
- // Control messages have a max size of 125 bytes. Need to try and read
- // one more so we reach end of stream (less 2 for the status)
- ByteBuffer data = ByteBuffer.allocate(124);
-
- int status = is.read();
- if (status != -1) {
- status = status << 8;
- status = status + is.read();
- int read = 0;
- while (read > -1) {
- data.position(data.position() + read);
- read = is.read(data.array(), data.position(), data.remaining());
+ if (opCode == Constants.OPCODE_CLOSE){
+ getWsOutbound().close(frame);
+ return SocketState.CLOSED;
+ } else if (opCode == Constants.OPCODE_PING) {
+ getWsOutbound().pong(frame.getPayLoad());
+ return SocketState.UPGRADED;
+ } else if (opCode == Constants.OPCODE_PONG) {
+ // NO-OP
+ return SocketState.UPGRADED;
}
- } else {
- status = 0;
- }
- data.flip();
- getOutbound().close(status, data);
- }
- private void doPing(InputStream is) throws IOException {
- // Control messages have a max size of 125 bytes. Need to try and read
- // one more so we reach end of stream
- ByteBuffer data = ByteBuffer.allocate(126);
-
- int read = 0;
- while (read > -1) {
- data.position(data.position() + read);
- read = is.read(data.array(), data.position(), data.remaining());
+ // Unknown OpCode
+ getWsOutbound().close(1002, null);
+ return SocketState.CLOSED;
+ } catch (MalformedInputException mie) {
+ // Invalid UTF-8
+ getWsOutbound().close(1007, null);
+ return SocketState.CLOSED;
+ } catch (UnmappableCharacterException uce) {
+ // Invalid UTF-8
+ getWsOutbound().close(1007, null);
+ return SocketState.CLOSED;
+ } catch (IOException ioe) {
+ // Given something must have gone to reach this point, this might
+ // not work but try it anyway.
+ getWsOutbound().close(1002, null);
+ return SocketState.CLOSED;
}
-
- data.flip();
- getOutbound().pong(data);
}
- private void doPong(InputStream is) throws IOException {
- // Unsolicited pong - swallow it
- int read = 0;
- while (read > -1) {
- read = is.read();
- }
- }
+ /**
+ * This method is called when there is a binary WebSocket message available
+ * to process. The message is presented via a stream and may be formed from
+ * one or more frames. The number of frames used to transmit the message is
+ * not made visible to the application.
+ *
+ * @param is The WebSocket message
+ *
+ * @throws IOException If a problem occurs processing the message. Any
+ * exception will trigger the closing of the WebSocket
+ * connection.
+ */
protected abstract void onBinaryData(InputStream is) throws IOException;
+
+
+ /**
+ * This method is called when there is a textual WebSocket message available
+ * to process. The message is presented via a reader and may be formed from
+ * one or more frames. The number of frames used to transmit the message is
+ * not made visible to the application.
+ *
+ * @param r The WebSocket message
+ *
+ * @throws IOException If a problem occurs processing the message. Any
+ * exception will trigger the closing of the WebSocket
+ * connection.
+ */
protected abstract void onTextData(Reader r) throws IOException;
}
Copied: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java (from r1293154, tomcat/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java)
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java?p2=tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java&p1=tomcat/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java&r1=1293154&r2=1301264&rev=1301264&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java Thu Mar 15 23:17:47 2012
@@ -1,207 +1,207 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.catalina.websocket;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
-
-import org.apache.tomcat.util.buf.B2CConverter;
-
-/**
- * Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
- * code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
- * does not reject these.
- */
-public class Utf8Decoder extends CharsetDecoder {
-
- // The next table contains information about UTF-8 charset and
- // correspondence of 1st byte to the length of sequence
- // For information please visit http://www.ietf.org/rfc/rfc3629.txt
- //
- // Please note, o means 0, actually.
- // -------------------------------------------------------------------
- // 0 1 2 3 Value
- // -------------------------------------------------------------------
- // oxxxxxxx 00000000 00000000 0xxxxxxx
- // 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
- // 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
- // 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
-
- private static final int remainingBytes[] = {
- // 1owwwwww
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
- // 11oyyyyy
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- // 111ozzzz
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- // 1111ouuu
- 3, 3, 3, 3, 3, 3, 3, 3,
- // > 11110111
- -1, -1, -1, -1, -1, -1, -1, -1 };
-
- private static final int remainingNumbers[] = {
- 0, // 0 1 2 3
- 4224, // (01o00000b << 6)+(1o000000b)
- 401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
- 29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b << 6)+(1o000000b)
- };
-
- private static final int lowerEncodingLimit[] = { -1, 0x80, 0x800, 0x10000 };
-
- public Utf8Decoder() {
- super(B2CConverter.UTF_8, 1.0f, 1.0f);
- }
-
- @Override
- protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
- if (in.hasArray() && out.hasArray()) {
- return decodeHasArray(in, out);
- }
- return decodeNotHasArray(in, out);
- }
-
- private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
- int outRemaining = out.remaining();
- int pos = in.position();
- int limit = in.limit();
- try {
- while (pos < limit) {
- if (outRemaining == 0) {
- return CoderResult.OVERFLOW;
- }
-
- int jchar = in.get();
- if (jchar < 0) {
- jchar = jchar & 0x7F;
- int tail = remainingBytes[jchar];
- if (tail == -1) {
- return CoderResult.malformedForLength(1);
- }
- if (limit - pos < 1 + tail) {
- return CoderResult.UNDERFLOW;
- }
-
- int nextByte;
- for (int i = 0; i < tail; i++) {
- nextByte = in.get() & 0xFF;
- if ((nextByte & 0xC0) != 0x80) {
- return CoderResult
- .malformedForLength(1 + i);
- }
- jchar = (jchar << 6) + nextByte;
- }
- jchar -= remainingNumbers[tail];
- if (jchar < lowerEncodingLimit[tail]) {
- // Should have been encoded in a fewer octets
- return CoderResult.malformedForLength(1);
- }
- pos += tail;
- }
- if (jchar <= 0xffff) {
- out.put((char) jchar);
- outRemaining--;
- } else {
- if (outRemaining < 2) {
- return CoderResult.OVERFLOW;
- }
- out.put((char) ((jchar >> 0xA) + 0xD7C0));
- out.put((char) ((jchar & 0x3FF) + 0xDC00));
- outRemaining -= 2;
- }
- pos++;
- }
- return CoderResult.UNDERFLOW;
- } finally {
- in.position(pos);
- }
- }
-
- private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
- int outRemaining = out.remaining();
- int pos = in.position();
- int limit = in.limit();
- final byte[] bArr = in.array();
- final char[] cArr = out.array();
- final int inIndexLimit = limit + in.arrayOffset();
-
- int inIndex = pos + in.arrayOffset();
- int outIndex = out.position() + out.arrayOffset();
-
- // if someone would change the limit in process,
- // he would face consequences
- for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
- int jchar = bArr[inIndex];
- if (jchar < 0) {
- jchar = jchar & 0x7F;
- int tail = remainingBytes[jchar];
-
- if (tail == -1) {
- in.position(inIndex - in.arrayOffset());
- out.position(outIndex - out.arrayOffset());
- return CoderResult.malformedForLength(1);
- }
- if (inIndexLimit - inIndex < 1 + tail) {
- break;
- }
-
- for (int i = 0; i < tail; i++) {
- int nextByte = bArr[inIndex + i + 1] & 0xFF;
- if ((nextByte & 0xC0) != 0x80) {
- in.position(inIndex - in.arrayOffset());
- out.position(outIndex - out.arrayOffset());
- return CoderResult.malformedForLength(1 + i);
- }
- jchar = (jchar << 6) + nextByte;
- }
- jchar -= remainingNumbers[tail];
- if (jchar < lowerEncodingLimit[tail]) {
- // Should have been encoded in fewer octets
- in.position(inIndex - in.arrayOffset());
- out.position(outIndex - out.arrayOffset());
- return CoderResult.malformedForLength(1);
- }
- inIndex += tail;
- }
- // Note: This is the additional test added
- if (jchar >= 0xD800 && jchar <=0xDFFF) {
- return CoderResult.unmappableForLength(3);
- }
- if (jchar <= 0xffff) {
- cArr[outIndex++] = (char) jchar;
- outRemaining--;
- } else {
- if (outRemaining < 2) {
- return CoderResult.OVERFLOW;
- }
- cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
- cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
- outRemaining -= 2;
- }
- }
- in.position(inIndex - in.arrayOffset());
- out.position(outIndex - out.arrayOffset());
- return (outRemaining == 0 && inIndex < inIndexLimit) ?
- CoderResult.OVERFLOW :
- CoderResult.UNDERFLOW;
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.websocket;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+
+import org.apache.tomcat.util.buf.B2CConverter;
+
+/**
+ * Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
+ * code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
+ * does not reject these.
+ */
+public class Utf8Decoder extends CharsetDecoder {
+
+ // The next table contains information about UTF-8 charset and
+ // correspondence of 1st byte to the length of sequence
+ // For information please visit http://www.ietf.org/rfc/rfc3629.txt
+ //
+ // Please note, o means 0, actually.
+ // -------------------------------------------------------------------
+ // 0 1 2 3 Value
+ // -------------------------------------------------------------------
+ // oxxxxxxx 00000000 00000000 0xxxxxxx
+ // 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
+ // 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
+ // 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
+
+ private static final int remainingBytes[] = {
+ // 1owwwwww
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ // 11oyyyyy
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ // 111ozzzz
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ // 1111ouuu
+ 3, 3, 3, 3, 3, 3, 3, 3,
+ // > 11110111
+ -1, -1, -1, -1, -1, -1, -1, -1 };
+
+ private static final int remainingNumbers[] = {
+ 0, // 0 1 2 3
+ 4224, // (01o00000b << 6)+(1o000000b)
+ 401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
+ 29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b << 6)+(1o000000b)
+ };
+
+ private static final int lowerEncodingLimit[] = { -1, 0x80, 0x800, 0x10000 };
+
+ public Utf8Decoder() {
+ super(B2CConverter.UTF_8, 1.0f, 1.0f);
+ }
+
+ @Override
+ protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+ if (in.hasArray() && out.hasArray()) {
+ return decodeHasArray(in, out);
+ }
+ return decodeNotHasArray(in, out);
+ }
+
+ private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
+ int outRemaining = out.remaining();
+ int pos = in.position();
+ int limit = in.limit();
+ try {
+ while (pos < limit) {
+ if (outRemaining == 0) {
+ return CoderResult.OVERFLOW;
+ }
+
+ int jchar = in.get();
+ if (jchar < 0) {
+ jchar = jchar & 0x7F;
+ int tail = remainingBytes[jchar];
+ if (tail == -1) {
+ return CoderResult.malformedForLength(1);
+ }
+ if (limit - pos < 1 + tail) {
+ return CoderResult.UNDERFLOW;
+ }
+
+ int nextByte;
+ for (int i = 0; i < tail; i++) {
+ nextByte = in.get() & 0xFF;
+ if ((nextByte & 0xC0) != 0x80) {
+ return CoderResult
+ .malformedForLength(1 + i);
+ }
+ jchar = (jchar << 6) + nextByte;
+ }
+ jchar -= remainingNumbers[tail];
+ if (jchar < lowerEncodingLimit[tail]) {
+ // Should have been encoded in a fewer octets
+ return CoderResult.malformedForLength(1);
+ }
+ pos += tail;
+ }
+ if (jchar <= 0xffff) {
+ out.put((char) jchar);
+ outRemaining--;
+ } else {
+ if (outRemaining < 2) {
+ return CoderResult.OVERFLOW;
+ }
+ out.put((char) ((jchar >> 0xA) + 0xD7C0));
+ out.put((char) ((jchar & 0x3FF) + 0xDC00));
+ outRemaining -= 2;
+ }
+ pos++;
+ }
+ return CoderResult.UNDERFLOW;
+ } finally {
+ in.position(pos);
+ }
+ }
+
+ private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
+ int outRemaining = out.remaining();
+ int pos = in.position();
+ int limit = in.limit();
+ final byte[] bArr = in.array();
+ final char[] cArr = out.array();
+ final int inIndexLimit = limit + in.arrayOffset();
+
+ int inIndex = pos + in.arrayOffset();
+ int outIndex = out.position() + out.arrayOffset();
+
+ // if someone would change the limit in process,
+ // he would face consequences
+ for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
+ int jchar = bArr[inIndex];
+ if (jchar < 0) {
+ jchar = jchar & 0x7F;
+ int tail = remainingBytes[jchar];
+
+ if (tail == -1) {
+ in.position(inIndex - in.arrayOffset());
+ out.position(outIndex - out.arrayOffset());
+ return CoderResult.malformedForLength(1);
+ }
+ if (inIndexLimit - inIndex < 1 + tail) {
+ break;
+ }
+
+ for (int i = 0; i < tail; i++) {
+ int nextByte = bArr[inIndex + i + 1] & 0xFF;
+ if ((nextByte & 0xC0) != 0x80) {
+ in.position(inIndex - in.arrayOffset());
+ out.position(outIndex - out.arrayOffset());
+ return CoderResult.malformedForLength(1 + i);
+ }
+ jchar = (jchar << 6) + nextByte;
+ }
+ jchar -= remainingNumbers[tail];
+ if (jchar < lowerEncodingLimit[tail]) {
+ // Should have been encoded in fewer octets
+ in.position(inIndex - in.arrayOffset());
+ out.position(outIndex - out.arrayOffset());
+ return CoderResult.malformedForLength(1);
+ }
+ inIndex += tail;
+ }
+ // Note: This is the additional test added
+ if (jchar >= 0xD800 && jchar <=0xDFFF) {
+ return CoderResult.unmappableForLength(3);
+ }
+ if (jchar <= 0xffff) {
+ cArr[outIndex++] = (char) jchar;
+ outRemaining--;
+ } else {
+ if (outRemaining < 2) {
+ return CoderResult.OVERFLOW;
+ }
+ cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
+ cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
+ outRemaining -= 2;
+ }
+ }
+ in.position(inIndex - in.arrayOffset());
+ out.position(outIndex - out.arrayOffset());
+ return (outRemaining == 0 && inIndex < inIndexLimit) ?
+ CoderResult.OVERFLOW :
+ CoderResult.UNDERFLOW;
+ }
+}
Propchange: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/Utf8Decoder.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WebSocketServlet.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WebSocketServlet.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WebSocketServlet.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WebSocketServlet.java Thu Mar 15 23:17:47 2012
@@ -175,6 +175,7 @@ public abstract class WebSocketServlet e
}
}
+
/**
* Intended to be overridden by sub-classes that wish to verify the origin
* of a WebSocket request before processing it.
@@ -190,6 +191,7 @@ public abstract class WebSocketServlet e
return true;
}
+
/**
* Intended to be overridden by sub-classes that wish to select a
* sub-protocol if the client provides a list of supported protocols.
@@ -199,14 +201,17 @@ public abstract class WebSocketServlet e
* obligation to respect the declared preference
* @return <code>null</code> if no sub-protocol is selected or the name of
* the protocol which <b>must</b> be one of the protocols listed by
- * the client.
+ * the client. This default implementation always returns
+ * <code>null</code>.
*/
protected String selectSubProtocol(List<String> subProtocols) {
return null;
}
+
/**
* Create the instance that will process this inbound connection.
+ * Applications must provide a new instance for each connection.
*
* @param subProtocol The sub-protocol agreed between the client and
* server or <code>null</code> if none was agreed
Copied: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java (from r1292670, tomcat/trunk/java/org/apache/catalina/websocket/WsFrame.java)
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java?p2=tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java&p1=tomcat/trunk/java/org/apache/catalina/websocket/WsFrame.java&r1=1292670&r2=1301264&rev=1301264&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/websocket/WsFrame.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsFrame.java Thu Mar 15 23:17:47 2012
@@ -18,23 +18,41 @@ package org.apache.catalina.websocket;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CoderResult;
import org.apache.catalina.util.Conversions;
import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.tomcat.util.res.StringManager;
/**
- * Represents a WebSocket frame with the exception of the payload for
+ * Represents a complete WebSocket frame with the exception of the payload for
* non-control frames.
*/
public class WsFrame {
+ private static final StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+
private final boolean fin;
private final int rsv;
private final byte opCode;
- private int[] mask = new int[4];
+ private byte[] mask = new byte[4];
private long payloadLength;
private ByteBuffer payload;
+ /**
+ * Create the new WebSocket frame, reading data from the processor as
+ * necessary.
+ *
+ * @param processor Processor associated with the WebSocket connection on
+ * which the frame has been sent
+ *
+ * @throws IOException If a problem occurs processing the frame. Any
+ * exception will trigger the closing of the WebSocket
+ * connection.
+ */
public WsFrame(UpgradeProcessor<?> processor) throws IOException {
int b = processorRead(processor);
@@ -45,8 +63,7 @@ public class WsFrame {
b = processorRead(processor);
// Client data must be masked
if ((b & 0x80) == 0) {
- // TODO: StringManager / i18n
- throw new IOException("Client frame not masked");
+ throw new IOException(sm.getString("frame.notMasked"));
}
payloadLength = b & 0x7F;
@@ -69,14 +86,24 @@ public class WsFrame {
}
}
- for (int j = 0; j < mask.length; j++) {
- mask[j] = processorRead(processor) & 0xFF;
- }
+ processorRead(processor, mask);
if (isControl()) {
// Note: Payload limited to <= 125 bytes by test above
payload = ByteBuffer.allocate((int) payloadLength);
processorRead(processor, payload);
+
+ if (opCode == Constants.OPCODE_CLOSE && payloadLength > 2) {
+ // Check close payload - if present - is valid UTF-8
+ CharBuffer cb = CharBuffer.allocate((int) payloadLength);
+ Utf8Decoder decoder = new Utf8Decoder();
+ payload.position(2);
+ CoderResult cr = decoder.decode(payload, cb, true);
+ payload.position(0);
+ if (cr.isError()) {
+ throw new IOException(sm.getString("frame.invalidUtf8"));
+ }
+ }
} else {
payload = null;
}
@@ -98,7 +125,7 @@ public class WsFrame {
return (opCode & 0x08) > 0;
}
- public int[] getMask() {
+ public byte[] getMask() {
return mask;
}
@@ -117,8 +144,7 @@ public class WsFrame {
throws IOException {
int result = processor.read();
if (result == -1) {
- // TODO i18n
- throw new IOException("End of stream before end of frame");
+ throw new IOException(sm.getString("frame.eos"));
}
return result;
}
@@ -131,8 +157,7 @@ public class WsFrame {
while (read < bytes.length) {
last = processor.read(bytes, read, bytes.length - read);
if (last == -1) {
- // TODO i18n
- throw new IOException("End of stream before end of frame");
+ throw new IOException(sm.getString("frame.eos"));
}
read += last;
}
@@ -148,8 +173,7 @@ public class WsFrame {
while (bb.hasRemaining()) {
last = processor.read();
if (last == -1) {
- // TODO i18n
- throw new IOException("End of stream before end of frame");
+ throw new IOException(sm.getString("frame.eos"));
}
bb.put((byte) (last ^ mask[bb.position() % 4]));
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsInputStream.java Thu Mar 15 23:17:47 2012
@@ -18,68 +18,48 @@ package org.apache.catalina.websocket;
import java.io.IOException;
-import org.apache.catalina.util.Conversions;
import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.tomcat.util.res.StringManager;
+/**
+ * This class is used to read WebSocket frames from the underlying socket and
+ * makes the payload available for reading as an {@link InputStream}. It only
+ * makes the number of bytes declared in the payload length available for
+ * reading even if more bytes are available from the socket.
+ */
public class WsInputStream extends java.io.InputStream {
- private UpgradeProcessor<?> processor;
- private WsFrameHeader wsFrameHeader;
- private long payloadLength = -1;
- private int[] mask = new int[4];
+ private static final StringManager sm =
+ StringManager.getManager(Constants.Package);
+
+ private UpgradeProcessor<?> processor;
+ private WsOutbound outbound;
+ private WsFrame frame;
private long remaining;
private long readThisFragment;
- public WsInputStream(UpgradeProcessor<?> processor) throws IOException {
- this.processor = processor;
-
- processFrameHeader();
- }
+ private String error = null;
- private void processFrameHeader() throws IOException {
-
- // TODO: Per frame extension handling is not currently supported.
-
- // TODO: Handle control frames between fragments
-
- int i = processor.read();
- this.wsFrameHeader = new WsFrameHeader(i);
-
- // Client data must be masked
- i = processor.read();
- if ((i & 0x80) == 0) {
- // TODO: StringManager / i18n
- throw new IOException("Client frame not masked");
- }
-
- payloadLength = i & 0x7F;
- if (payloadLength == 126) {
- byte[] extended = new byte[2];
- processor.read(extended);
- payloadLength = Conversions.byteArrayToLong(extended);
- } else if (payloadLength == 127) {
- byte[] extended = new byte[8];
- processor.read(extended);
- payloadLength = Conversions.byteArrayToLong(extended);
- }
- remaining = payloadLength;
+ public WsInputStream(UpgradeProcessor<?> processor, WsOutbound outbound)
+ throws IOException {
+ this.processor = processor;
+ this.outbound = outbound;
+ processFrame();
+ }
- for (int j = 0; j < mask.length; j++) {
- mask[j] = processor.read() & 0xFF;
- }
- readThisFragment = 0;
+ public WsFrame getFrame() {
+ return frame;
}
- public WsFrameHeader getFrameHeader() {
- return wsFrameHeader;
- }
- public long getPayloadLength() {
- return payloadLength;
+ private void processFrame() throws IOException {
+ frame = new WsFrame(processor);
+ readThisFragment = 0;
+ remaining = frame.getPayLoadLength();
}
@@ -87,13 +67,29 @@ public class WsInputStream extends java.
@Override
public int read() throws IOException {
- while (remaining == 0 && !getFrameHeader().getFin()) {
+ if (error != null) {
+ throw new IOException(error);
+ }
+ while (remaining == 0 && !getFrame().getFin()) {
// Need more data - process next frame
- processFrameHeader();
-
- if (getFrameHeader().getOpCode() != Constants.OPCODE_CONTINUATION) {
- // TODO i18n
- throw new IOException("Not a continuation frame");
+ processFrame();
+ while (frame.isControl()) {
+ if (getFrame().getOpCode() == Constants.OPCODE_PING) {
+ outbound.pong(frame.getPayLoad());
+ } else if (getFrame().getOpCode() == Constants.OPCODE_PONG) {
+ // NO-OP. Swallow it.
+ } else if (getFrame().getOpCode() == Constants.OPCODE_CLOSE) {
+ outbound.close(frame);
+ } else{
+ throw new IOException(sm.getString("is.unknownOpCode",
+ Byte.valueOf(getFrame().getOpCode())));
+ }
+ processFrame();
+ }
+ if (getFrame().getOpCode() != Constants.OPCODE_CONTINUATION) {
+ error = sm.getString("is.notContinutation",
+ Byte.valueOf(getFrame().getOpCode()));
+ throw new IOException(error);
}
}
@@ -108,6 +104,58 @@ public class WsInputStream extends java.
if(masked == -1) {
return -1;
}
- return masked ^ mask[(int) ((readThisFragment - 1) % 4)];
+ return masked ^
+ (frame.getMask()[(int) ((readThisFragment - 1) % 4)] & 0xFF);
+ }
+
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ if (error != null) {
+ throw new IOException(error);
+ }
+ while (remaining == 0 && !getFrame().getFin()) {
+ // Need more data - process next frame
+ processFrame();
+ while (frame.isControl()) {
+ if (getFrame().getOpCode() == Constants.OPCODE_PING) {
+ outbound.pong(frame.getPayLoad());
+ } else if (getFrame().getOpCode() == Constants.OPCODE_PONG) {
+ // NO-OP. Swallow it.
+ } else if (getFrame().getOpCode() == Constants.OPCODE_CLOSE) {
+ outbound.close(frame);
+ } else{
+ throw new IOException(sm.getString("is.unknownOpCode",
+ Byte.valueOf(getFrame().getOpCode())));
+ }
+ processFrame();
+ }
+ if (getFrame().getOpCode() != Constants.OPCODE_CONTINUATION) {
+ error = sm.getString("is.notContinutation",
+ Byte.valueOf(getFrame().getOpCode()));
+ throw new IOException(error);
+ }
+ }
+
+ if (remaining == 0) {
+ return -1;
+ }
+
+ if (len > remaining) {
+ len = (int) remaining;
+ }
+ int result = processor.read(b, off, len);
+ if(result == -1) {
+ return -1;
+ }
+
+ for (int i = off; i < off + result; i++) {
+ b[i] = (byte) (b[i] ^
+ frame.getMask()[(int) ((readThisFragment + i - off) % 4)]);
+ }
+ remaining -= result;
+ readThisFragment += result;
+ return result;
}
+
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/websocket/WsOutbound.java Thu Mar 15 23:17:47 2012
@@ -19,20 +19,28 @@ package org.apache.catalina.websocket;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
import org.apache.coyote.http11.upgrade.UpgradeOutbound;
import org.apache.tomcat.util.buf.B2CConverter;
+import org.apache.tomcat.util.res.StringManager;
+/**
+ * Provides the means to write WebSocket messages to the client.
+ */
public class WsOutbound {
+ private static final StringManager sm =
+ StringManager.getManager(Constants.Package);
private static final int DEFAULT_BUFFER_SIZE = 8192;
private UpgradeOutbound upgradeOutbound;
private ByteBuffer bb;
private CharBuffer cb;
private boolean closed = false;
- protected Boolean text = null;
- protected boolean firstFrame = true;
+ private Boolean text = null;
+ private boolean firstFrame = true;
public WsOutbound(UpgradeOutbound upgradeOutbound) {
@@ -43,7 +51,23 @@ public class WsOutbound {
}
+ /**
+ * 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 {
+ if (closed) {
+ throw new IOException(sm.getString("outbound.closed"));
+ }
+
if (bb.position() == bb.capacity()) {
doFlush(false);
}
@@ -58,7 +82,22 @@ public class WsOutbound {
}
+ /**
+ * 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 b 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 {
+ if (closed) {
+ throw new IOException(sm.getString("outbound.closed"));
+ }
+
if (cb.position() == cb.capacity()) {
doFlush(false);
}
@@ -74,7 +113,20 @@ public class WsOutbound {
}
+ /**
+ * 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 {
+ if (closed) {
+ throw new IOException(sm.getString("outbound.closed"));
+ }
+
if (text != null) {
// Empty the buffer
flush();
@@ -84,7 +136,20 @@ public class WsOutbound {
}
+ /**
+ * 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 msgBb The buffer containing the payload
+ *
+ * @throws IOException If an error occurs writing to the client
+ */
public void writeTextMessage(CharBuffer msgCb) throws IOException {
+ if (closed) {
+ throw new IOException(sm.getString("outbound.closed"));
+ }
+
if (text != null) {
// Empty the buffer
flush();
@@ -94,10 +159,19 @@ public class WsOutbound {
}
+ /**
+ * 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 {
+ if (closed) {
+ throw new IOException(sm.getString("outbound.closed"));
+ }
doFlush(true);
}
+
private void doFlush(boolean finalFragment) throws IOException {
if (text == null) {
// No data
@@ -113,6 +187,64 @@ public class WsOutbound {
}
+ /**
+ * 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(1002, null);
+ }
+ } else {
+ // No status
+ close(0, null);
+ }
+ }
+
+
+ private boolean validateCloseStatus(int status) {
+
+ if (status == 1000 || status == 1001 || status == 1002 ||
+ status == 1003 || status == 1007 || status == 1008 ||
+ status == 1009 || status == 1010 || status == 1011 ||
+ (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 {
// TODO Think about threading requirements for writing. This is not
// currently thread safe and writing almost certainly needs to be.
@@ -121,20 +253,19 @@ public class WsOutbound {
}
closed = true;
- doFlush(true);
-
upgradeOutbound.write(0x88);
if (status == 0) {
upgradeOutbound.write(0);
- } else if (data == null) {
+ } else if (data == null || data.position() == data.limit()) {
upgradeOutbound.write(2);
upgradeOutbound.write(status >>> 8);
upgradeOutbound.write(status);
} else {
- upgradeOutbound.write(2 + data.limit());
+ upgradeOutbound.write(2 + data.limit() - data.position());
upgradeOutbound.write(status >>> 8);
upgradeOutbound.write(status);
- upgradeOutbound.write(data.array(), 0, data.limit());
+ upgradeOutbound.write(data.array(), data.position(),
+ data.limit() - data.position());
}
upgradeOutbound.flush();
@@ -143,22 +274,36 @@ public class WsOutbound {
upgradeOutbound = null;
}
+
+ /**
+ * 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 {
// TODO Think about threading requirements for writing. This is not
// currently thread safe and writing almost certainly needs to be.
if (closed) {
- // TODO - handle this - ISE?
+ throw new IOException(sm.getString("outbound.closed"));
}
doFlush(true);
upgradeOutbound.write(0x8A);
- upgradeOutbound.write(data.limit());
- upgradeOutbound.write(data.array(), 0, data.limit());
+ 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();
}
+
/**
* Writes the provided bytes as the payload in a new WebSocket frame.
*
@@ -170,6 +315,10 @@ public class WsOutbound {
private void doWriteBytes(ByteBuffer buffer, boolean finalFragment)
throws IOException {
+ if (closed) {
+ throw new IOException("Closed");
+ }
+
// Work out the first byte
int first = 0x00;
if (finalFragment) {
@@ -220,10 +369,17 @@ public class WsOutbound {
}
+ /*
+ * Convert the textual message to bytes and then output it.
+ */
private void doWriteText(CharBuffer buffer, boolean finalFragment)
throws IOException {
+ CharsetEncoder encoder = B2CConverter.UTF_8.newEncoder();
do {
- B2CConverter.UTF_8.newEncoder().encode(buffer, bb, true);
+ CoderResult cr = encoder.encode(buffer, bb, true);
+ if (cr.isError()) {
+ cr.throwException();
+ }
bb.flip();
if (buffer.hasRemaining()) {
doWriteBytes(bb, false);
Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeAprProcessor.java Thu Mar 15 23:17:47 2012
@@ -55,6 +55,12 @@ public class UpgradeAprProcessor extends
}
+ @Override
+ public void write(byte[]b, int off, int len) throws IOException {
+ Socket.send(socket, b, off, len);
+ }
+
+
/*
* Input methods
*/
@@ -67,7 +73,7 @@ public class UpgradeAprProcessor extends
@Override
- public int read(byte[] bytes) throws IOException {
- return Socket.recv(socket, bytes, 0, bytes.length);
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return Socket.recv(socket, bytes, off, len);
}
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeBioProcessor.java Thu Mar 15 23:17:47 2012
@@ -58,6 +58,12 @@ public class UpgradeBioProcessor extends
}
+ @Override
+ public void write(byte[]b, int off, int len) throws IOException {
+ outputStream.write(b, off, len);
+ }
+
+
/*
* Input methods
*/
@@ -68,7 +74,7 @@ public class UpgradeBioProcessor extends
@Override
- public int read(byte[] bytes) throws IOException {
- return inputStream.read(bytes);
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return inputStream.read(bytes, off, len);
}
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeNioProcessor.java Thu Mar 15 23:17:47 2012
@@ -77,7 +77,12 @@ public class UpgradeNioProcessor extends
@Override
public void write(int b) throws IOException {
- writeToSocket(new byte[] {(byte) b});
+ writeToSocket(new byte[] {(byte) b}, 0, 1);
+ }
+
+ @Override
+ public void write(byte[]b, int off, int len) throws IOException {
+ writeToSocket(b, off, len);
}
/*
@@ -91,8 +96,8 @@ public class UpgradeNioProcessor extends
}
@Override
- public int read(byte[] bytes) throws IOException {
- return readSocket(true, bytes, 0, bytes.length);
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return readSocket(true, bytes, off, len);
}
@@ -147,10 +152,11 @@ public class UpgradeNioProcessor extends
/*
* Adapted from the NioOutputBuffer
*/
- private synchronized int writeToSocket(byte[] bytes) throws IOException {
+ private synchronized int writeToSocket(byte[] bytes, int off, int len)
+ throws IOException {
nioChannel.getBufHandler().getWriteBuffer().clear();
- nioChannel.getBufHandler().getWriteBuffer().put(bytes);
+ nioChannel.getBufHandler().getWriteBuffer().put(bytes, off, len);
nioChannel.getBufHandler().getWriteBuffer().flip();
int written = 0;
Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeOutbound.java Thu Mar 15 23:17:47 2012
@@ -42,4 +42,9 @@ public class UpgradeOutbound extends Out
public void write(int b) throws IOException {
processor.write(b);
}
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ processor.write(b, off, len);
+ }
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java Thu Mar 15 23:17:47 2012
@@ -43,10 +43,11 @@ public abstract class UpgradeProcessor<S
// Output methods
public abstract void flush() throws IOException;
public abstract void write(int b) throws IOException;
+ public abstract void write(byte[] b, int off, int len) throws IOException;
// Input methods
public abstract int read() throws IOException;
- public abstract int read(byte[] bytes) throws IOException;
+ public abstract int read(byte[] bytes, int off, int len) throws IOException;
@Override
public final UpgradeInbound getUpgradeInbound() {
Modified: tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java (original)
+++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/websocket/TestWebSocket.java Thu Mar 15 23:17:47 2012
@@ -46,7 +46,7 @@ public class TestWebSocket extends Tomca
private OutputStream os;
private InputStream is;
- boolean isContinuation = false;
+ private boolean isContinuation = false;
@Test
public void testSimple() throws Exception {
Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoMessage.java Thu Mar 15 23:17:47 2012
@@ -38,12 +38,12 @@ public class EchoMessage extends WebSock
@Override
protected void onBinaryMessage(ByteBuffer message) throws IOException {
- getOutbound().writeBinaryMessage(message);
+ getWsOutbound().writeBinaryMessage(message);
}
@Override
protected void onTextMessage(CharBuffer message) throws IOException {
- getOutbound().writeTextMessage(message);
+ getWsOutbound().writeTextMessage(message);
}
}
}
Modified: tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoStream.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoStream.java?rev=1301264&r1=1301263&r2=1301264&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoStream.java (original)
+++ tomcat/tc7.0.x/trunk/webapps/examples/WEB-INF/classes/websocket/EchoStream.java Thu Mar 15 23:17:47 2012
@@ -39,7 +39,7 @@ public class EchoStream extends WebSocke
@Override
protected void onBinaryData(InputStream is) throws IOException {
// Simply echo the data to back to the client.
- WsOutbound outbound = getOutbound();
+ WsOutbound outbound = getWsOutbound();
int i = is.read();
while (i != -1) {
@@ -53,7 +53,7 @@ public class EchoStream extends WebSocke
@Override
protected void onTextData(Reader r) throws IOException {
// Simply echo the data to back to the client.
- WsOutbound outbound = getOutbound();
+ WsOutbound outbound = getWsOutbound();
int c = r.read();
while (c != -1) {
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org