Author: markt Date: Tue Dec 11 21:59:06 2012 New Revision: 1420446 URL: http://svn.apache.org/viewvc?rev=1420446&view=rev Log: WebSocket 1.0 implementation part 11 of many Fix s/ping/pong/ in the API. pings are automatically responded to with pongs. Apps send ping and receive pongs. (Sending unsolicited pong is also possible.) Handle incoming messages (at the frame, still need to map WebSocketMessage annotations) Add some debug to the examples to check messages are received
Added: tomcat/trunk/java/javax/websocket/PongMessage.java - copied, changed from r1420331, tomcat/trunk/java/javax/websocket/PingMessage.java tomcat/trunk/java/org/apache/tomcat/websocket/WsPongMessage.java - copied, changed from r1420331, tomcat/trunk/java/org/apache/tomcat/websocket/WsPingMessage.java Removed: tomcat/trunk/java/javax/websocket/PingMessage.java tomcat/trunk/java/org/apache/tomcat/websocket/WsPingMessage.java Modified: tomcat/trunk/java/org/apache/tomcat/websocket/PojoMethodMapping.java tomcat/trunk/java/org/apache/tomcat/websocket/WsFrame.java tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java Copied: tomcat/trunk/java/javax/websocket/PongMessage.java (from r1420331, tomcat/trunk/java/javax/websocket/PingMessage.java) URL: http://svn.apache.org/viewvc/tomcat/trunk/java/javax/websocket/PongMessage.java?p2=tomcat/trunk/java/javax/websocket/PongMessage.java&p1=tomcat/trunk/java/javax/websocket/PingMessage.java&r1=1420331&r2=1420446&rev=1420446&view=diff ============================================================================== --- tomcat/trunk/java/javax/websocket/PingMessage.java (original) +++ tomcat/trunk/java/javax/websocket/PongMessage.java Tue Dec 11 21:59:06 2012 @@ -1,23 +1,23 @@ -/* - * 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 javax.websocket; - -import java.nio.ByteBuffer; - -public interface PingMessage { - ByteBuffer getApplicationData(); -} +/* + * 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 javax.websocket; + +import java.nio.ByteBuffer; + +public interface PongMessage { + ByteBuffer getApplicationData(); +} Modified: tomcat/trunk/java/org/apache/tomcat/websocket/PojoMethodMapping.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/PojoMethodMapping.java?rev=1420446&r1=1420445&r2=1420446&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/PojoMethodMapping.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/PojoMethodMapping.java Tue Dec 11 21:59:06 2012 @@ -23,6 +23,7 @@ import java.util.Map; import javax.websocket.Session; import javax.websocket.WebSocketClose; import javax.websocket.WebSocketError; +import javax.websocket.WebSocketMessage; import javax.websocket.WebSocketOpen; import javax.websocket.WebSocketPathParam; @@ -57,6 +58,8 @@ public class PojoMethodMapping { } else if (error == null && method.getAnnotation(WebSocketError.class) != null) { error = method; + } else if (method.getAnnotation(WebSocketMessage.class) != null) { + // TODO } } this.onOpen = open; Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsFrame.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsFrame.java?rev=1420446&r1=1420445&r2=1420446&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsFrame.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsFrame.java Tue Dec 11 21:59:06 2012 @@ -22,6 +22,8 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import javax.servlet.ServletInputStream; +import javax.websocket.MessageHandler; +import javax.websocket.PongMessage; import org.apache.tomcat.util.res.StringManager; @@ -40,12 +42,17 @@ public class WsFrame { private int pos = 0; private State state = State.NEW_FRAME; + private int headerLength = 0; + private boolean continutationExpected = false; + private boolean textMessage = false; + private long payloadSent = 0; + + private long payloadLength = 0; private boolean fin; private int rsv; private byte opCode; private byte[] mask = new byte[4]; - private long payloadLength = -1; - private int headerLength = -1; + int maskIndex = 0; public WsFrame(ServletInputStream sis, WsSession wsSession) { @@ -107,6 +114,27 @@ public class WsFrame { rsv = (b & 0x70) >>> 4; opCode = (byte) (b & 0x0F); + if (!isControl()) { + if (continutationExpected) { + if (opCode != Constants.OPCODE_CONTINUATION) { + // TODO i18n + throw new IllegalStateException(); + } + } else { + if (opCode == Constants.OPCODE_BINARY) { + textMessage = false; + } else if (opCode == Constants.OPCODE_TEXT) { + textMessage = true; + } else { + // TODO i18n + throw new UnsupportedOperationException(); + } + } + + continutationExpected = !fin; + } + + b = inputBuffer[1]; // Client data must be masked if ((b & 0x80) == 0) { @@ -172,63 +200,129 @@ public class WsFrame { if (opCode == Constants.OPCODE_CLOSE) { wsSession.close(); } else if (opCode == Constants.OPCODE_PING) { - wsSession.getPingMessageHandler().onMessage( - new WsPingMessage(getPayload())); + wsSession.getRemote().sendPong(getPayloadBinary()); } else if (opCode == Constants.OPCODE_PONG) { - // TODO - // Validate the PONG? + MessageHandler.Basic<PongMessage> mhPong = + wsSession.getPongMessageHandler(); + if (mhPong != null) { + mhPong.onMessage(new WsPongMessage(getPayloadBinary())); + } } else { // TODO i18n throw new UnsupportedOperationException(); } return true; } - if (isPayloadComplete()) { - // TODO Check if partial messages supported - if (inputBuffer.length - pos > 0) { + if (!isPayloadComplete()) { + if (usePartial()) { + sendPayload(false); return false; + } else { + if (inputBuffer.length - pos > 0) { + return false; + } + throw new UnsupportedOperationException(); } - throw new UnsupportedOperationException(); } else { - // Unmask the data - for (int i = 0; i < payloadLength; i++) { - inputBuffer[headerLength + i] = (byte) - ((inputBuffer[headerLength + i] ^ mask[i % 4]) & 0xFF); - } - // TODO Handle incoming data properly - System.out.println(new String(inputBuffer, headerLength, - (int) payloadLength, Charset.forName("UTF-8"))); + sendPayload(true); } state = State.NEW_FRAME; - pos = 0; + payloadLength = 0; + payloadSent = 0; + maskIndex = 0; return true; } + @SuppressWarnings("unchecked") + private void sendPayload(boolean last) { + if (textMessage) { + String payload = getPayloadText(); + MessageHandler mh = wsSession.getTextMessageHandler(); + if (mh != null) { + if (mh instanceof MessageHandler.Async<?>) { + ((MessageHandler.Async<String>) mh).onMessage(payload, + last); + } else { + ((MessageHandler.Basic<String>) mh).onMessage(payload); + } + } + } else { + ByteBuffer payload = getPayloadBinary(); + MessageHandler mh = wsSession.getBinaryMessageHandler(); + if (mh != null) { + if (mh instanceof MessageHandler.Async<?>) { + ((MessageHandler.Async<ByteBuffer>) mh).onMessage(payload, + last); + } else { + ((MessageHandler.Basic<ByteBuffer>) mh).onMessage(payload); + } + } + } + } + private boolean isControl() { return (opCode & 0x08) > 0; } private boolean isPayloadComplete() { - return pos < (headerLength + payloadLength); + return (payloadSent + pos - headerLength) >= payloadLength; + } + + private boolean usePartial() { + if (opCode == Constants.OPCODE_BINARY) { + MessageHandler mh = wsSession.getBinaryMessageHandler(); + if (mh != null) { + return mh instanceof MessageHandler.Async<?>; + } + return false; + } else if (opCode == Constants.OPCODE_TEXT) { + MessageHandler mh = wsSession.getTextMessageHandler(); + if (mh != null) { + return mh instanceof MessageHandler.Async<?>; + } + return false; + } else { + // All other OpCodes require the full payload to be present + return false; + } } - private ByteBuffer getPayload() { - ByteBuffer result; + private ByteBuffer getPayloadBinary() { + int end; if (isPayloadComplete()) { - result = ByteBuffer.allocate((int) payloadLength); - System.arraycopy(inputBuffer, headerLength, result.array(), 0, - (int) payloadLength); + end = (int) (payloadLength - payloadSent) + headerLength; } else { - // TODO Handle partial payloads - result = null; + end = pos; } + ByteBuffer result = ByteBuffer.allocate(end - headerLength); + + for (int i = headerLength; i < end; i++) { + result.put(i - headerLength, + (byte) ((inputBuffer[i] ^ mask[maskIndex]) & 0xFF)); + maskIndex++; + if (maskIndex == 4) { + maskIndex = 0; + } + } + + // May have read past end of current frame into next + + pos = 0; + headerLength = 0; + return result; } + private String getPayloadText() { + ByteBuffer bb = getPayloadBinary(); + + return new String(bb.array(), Charset.forName("UTF-8")); + } + protected static long byteArrayToLong(byte[] b, int start, int len) throws IOException { Copied: tomcat/trunk/java/org/apache/tomcat/websocket/WsPongMessage.java (from r1420331, tomcat/trunk/java/org/apache/tomcat/websocket/WsPingMessage.java) URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsPongMessage.java?p2=tomcat/trunk/java/org/apache/tomcat/websocket/WsPongMessage.java&p1=tomcat/trunk/java/org/apache/tomcat/websocket/WsPingMessage.java&r1=1420331&r2=1420446&rev=1420446&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsPingMessage.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsPongMessage.java Tue Dec 11 21:59:06 2012 @@ -18,13 +18,13 @@ package org.apache.tomcat.websocket; import java.nio.ByteBuffer; -import javax.websocket.PingMessage; +import javax.websocket.PongMessage; -public class WsPingMessage implements PingMessage { +public class WsPongMessage implements PongMessage { private final ByteBuffer applicationData; - public WsPingMessage(ByteBuffer applicationData) { + public WsPongMessage(ByteBuffer applicationData) { this.applicationData = applicationData; } Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java?rev=1420446&r1=1420445&r2=1420446&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java (original) +++ tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java Tue Dec 11 21:59:06 2012 @@ -30,7 +30,7 @@ import javax.websocket.CloseReason; import javax.websocket.CloseReason.CloseCodes; import javax.websocket.Endpoint; import javax.websocket.MessageHandler; -import javax.websocket.PingMessage; +import javax.websocket.PongMessage; import javax.websocket.RemoteEndpoint; import javax.websocket.Session; @@ -38,7 +38,7 @@ public class WsSession implements Sessio private MessageHandler textMessageHandler = null; private MessageHandler binaryMessageHandler = null; - private MessageHandler.Basic<PingMessage> pingMessageHandler = + private MessageHandler.Basic<PongMessage> pongMessageHandler = new DefaultPingMessageHandler(this); private final Endpoint localEndpoint; @@ -62,12 +62,24 @@ public class WsSession implements Sessio throw new IllegalArgumentException(); } if (types[0].getClass().equals(String.class)) { + if (textMessageHandler != null) { + // TODO i18n + throw new IllegalStateException(); + } textMessageHandler = listener; } else if (types[0].getClass().equals(ByteBuffer.class)){ + if (binaryMessageHandler != null) { + // TODO i18n + throw new IllegalStateException(); + } binaryMessageHandler = listener; - } else if (types[0].getClass().equals(PingMessage.class)){ + } else if (types[0].getClass().equals(PongMessage.class)){ + if (pongMessageHandler != null) { + // TODO i18n + throw new IllegalStateException(); + } if (listener instanceof MessageHandler.Basic<?>) { - pingMessageHandler = (MessageHandler.Basic<PingMessage>) listener; + pongMessageHandler = (MessageHandler.Basic<PongMessage>) listener; } else { // TODO i18n throw new IllegalArgumentException(); @@ -87,8 +99,8 @@ public class WsSession implements Sessio if (textMessageHandler != null) { result.add(textMessageHandler); } - if (pingMessageHandler != null) { - result.add(pingMessageHandler); + if (pongMessageHandler != null) { + result.add(pongMessageHandler); } return result; } @@ -102,8 +114,8 @@ public class WsSession implements Sessio textMessageHandler = null; } else if (listener.equals(binaryMessageHandler)) { binaryMessageHandler = null; - } else if (listener.equals(pingMessageHandler)) { - pingMessageHandler = null; + } else if (listener.equals(pongMessageHandler)) { + pongMessageHandler = null; } // TODO Ignore? ISE? } @@ -224,13 +236,13 @@ public class WsSession implements Sessio return binaryMessageHandler; } - public MessageHandler.Basic<PingMessage> getPingMessageHandler() { - return pingMessageHandler; + public MessageHandler.Basic<PongMessage> getPongMessageHandler() { + return pongMessageHandler; } private static class DefaultPingMessageHandler - implements MessageHandler.Basic<PingMessage>{ + implements MessageHandler.Basic<PongMessage>{ private final WsSession wsSession; @@ -239,7 +251,7 @@ public class WsSession implements Sessio } @Override - public void onMessage(PingMessage message) { + public void onMessage(PongMessage message) { RemoteEndpoint remoteEndpoint = wsSession.getRemote(); if (remoteEndpoint != null) { remoteEndpoint.sendPong(message.getApplicationData()); Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java?rev=1420446&r1=1420445&r2=1420446&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoAnnotation.java Tue Dec 11 21:59:06 2012 @@ -17,6 +17,7 @@ package websocket.echo; import javax.websocket.WebSocketEndpoint; +import javax.websocket.WebSocketMessage; import javax.websocket.WebSocketOpen; import javax.websocket.WebSocketPathParam; @@ -27,4 +28,10 @@ public class EchoAnnotation { public void printOpen(@WebSocketPathParam("test") String test) { System.out.println("EchoAnnotation.printOpen() with [" + test + "]"); } + + @WebSocketMessage + public String printMessage(String msg) { + System.out.println("EchoAnnotation.printMessage() with [" + msg + "]"); + return msg; + } } Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java?rev=1420446&r1=1420445&r2=1420446&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java Tue Dec 11 21:59:06 2012 @@ -42,7 +42,10 @@ public class EchoEndpoint extends Endpoi @Override public void onMessage(String message) { try { - remoteEndpoint.sendString(message); + System.out.println(message); + if (remoteEndpoint != null) { + remoteEndpoint.sendString(message); + } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org