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/11/27 23:04:57 UTC
svn commit: r1414427 - in /tomcat/trunk: java/org/apache/tomcat/websocket/
webapps/examples/WEB-INF/classes/websocket/echo/ webapps/examples/websocket/
Author: markt
Date: Tue Nov 27 22:04:53 2012
New Revision: 1414427
URL: http://svn.apache.org/viewvc?rev=1414427&view=rev
Log:
WebSocket 1.0 implementation part 3 of many
Complete the WsServlet implementation
Now gets as far as Endpoint.onOpen() and then immediately closes
Add the new programmatic echo endpoint to the examples web app for testing
Added:
tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java (with props)
tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java (with props)
tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java (with props)
Modified:
tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
tomcat/trunk/java/org/apache/tomcat/websocket/ServerContainerImpl.java
tomcat/trunk/java/org/apache/tomcat/websocket/WsServlet.java
tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/echo/EchoEndpoint.java
tomcat/trunk/webapps/examples/websocket/echo.html
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties?rev=1414427&r1=1414426&r2=1414427&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/LocalStrings.properties Tue Nov 27 22:04:53 2012
@@ -14,6 +14,7 @@
# limitations under the License.
sci.newInstance.fail=Failed to create an Endpoint instance of type [{0}]
serverContainer.endpointDeploy=Endpoint class [{0}] deploying to path [{1}] in ServletContext [{2}]
+serverContainer.missingEndpoint=An Endpoint instance has been request for path [{0}] but no matching Endpoint class was found
serverContainer.pojoDeploy=POJO class [{0}] deploying to path [{1}] in ServletContext [{2}]
serverContainer.servletContextMismatch=Attempted to register a POJO annotated for WebSocket at path [{0}] in the ServletContext with context path [{1}] when the WebSocket ServerContainer is allocated to the ServletContext with context path [{2}]
serverContainer.servletContextMissing=No ServletContext was specified
\ No newline at end of file
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/ServerContainerImpl.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/ServerContainerImpl.java?rev=1414427&r1=1414426&r2=1414427&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/ServerContainerImpl.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/ServerContainerImpl.java Tue Nov 27 22:04:53 2012
@@ -160,4 +160,23 @@ public class ServerContainerImpl extends
sr.addMapping(mapping);
}
+
+
+ public Endpoint getEndpoint(String servletPath)
+ throws InstantiationException, IllegalAccessException {
+ Class<? extends Endpoint> clazzEndpoint = endpointMap.get(servletPath);
+ if (clazzEndpoint != null) {
+ Endpoint ep = clazzEndpoint.newInstance();
+ return ep;
+ }
+
+ Class<?> clazzPojo = pojoMap.get(servletPath);
+ if (clazzPojo != null) {
+ Endpoint ep = new WsEndpointPojo(clazzPojo, servletPath);
+ return ep;
+ }
+
+ throw new IllegalStateException(
+ sm.getString("serverContainer.missingEndpoint", servletPath));
+ }
}
Added: tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java?rev=1414427&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java Tue Nov 27 22:04:53 2012
@@ -0,0 +1,90 @@
+/*
+ * 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.tomcat.websocket;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.websocket.CloseReason;
+import javax.websocket.DefaultServerConfiguration;
+import javax.websocket.Endpoint;
+import javax.websocket.EndpointConfiguration;
+import javax.websocket.Session;
+
+public class WsEndpointPojo extends Endpoint {
+
+ private final Object pojo;
+ private final EndpointConfiguration config;
+ private final Method onOpen;
+ private final Method onClose;
+ private final Method onError;
+
+ public WsEndpointPojo(Class<?> clazzPojo, String path)
+ throws InstantiationException, IllegalAccessException {
+ this.pojo = clazzPojo.newInstance();
+ this.config = new DefaultServerConfiguration(path);
+
+ // TODO - Find these
+ this.onOpen = null;
+ this.onClose = null;
+ this.onError = null;
+ }
+
+ @Override
+ public EndpointConfiguration getEndpointConfiguration() {
+ return config;
+ }
+
+ @Override
+ public void onOpen(Session session) {
+ if (onOpen != null) {
+ try {
+ onOpen.invoke(pojo, session);
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onClose(CloseReason closeReason) {
+ if (onClose != null) {
+ try {
+ onClose.invoke(pojo, (Object[]) null);
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (onError != null) {
+ try {
+ onError.invoke(pojo, throwable);
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+}
Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/WsEndpointPojo.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java?rev=1414427&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java Tue Nov 27 22:04:53 2012
@@ -0,0 +1,38 @@
+/*
+ * 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.tomcat.websocket;
+
+import javax.servlet.http.ProtocolHandler;
+import javax.servlet.http.WebConnection;
+import javax.websocket.Endpoint;
+
+public class WsProtocolHandler implements ProtocolHandler {
+
+ private final Endpoint ep;
+
+ public WsProtocolHandler(Endpoint ep) {
+ this.ep = ep;
+ }
+
+ @Override
+ public void init(WebConnection connection) {
+
+ ep.onOpen(new WsSession());
+
+ // TODO Message handling
+ }
+}
Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/WsProtocolHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: tomcat/trunk/java/org/apache/tomcat/websocket/WsServlet.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/websocket/WsServlet.java?rev=1414427&r1=1414426&r2=1414427&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsServlet.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsServlet.java Tue Nov 27 22:04:53 2012
@@ -17,21 +17,192 @@
package org.apache.tomcat.websocket;
import java.io.IOException;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.ProtocolHandler;
+import javax.websocket.Endpoint;
+import javax.websocket.ServerEndpointConfiguration;
+import javax.xml.bind.DatatypeConverter;
+
public class WsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
+ private static final Charset ISO_8859_1;
+ static {
+ ISO_8859_1 = Charset.forName("ISO-8859-1");
+ }
+
+ private static final byte[] WS_ACCEPT =
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(ISO_8859_1);
+
+ private final Queue<MessageDigest> sha1Helpers =
+ new ConcurrentLinkedQueue<>();
+
+
+
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
- resp.setContentType("text/plain");
- resp.getWriter().print("TODO: Implement HTTP Upgrade");
+ // Information required to send the server handshake message
+ String key;
+ String subProtocol = null;
+ List<String> extensions = Collections.emptyList();
+
+ if (!headerContainsToken(req, "upgrade", "websocket")) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (!headerContainsToken(req, "connection", "upgrade")) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ if (!headerContainsToken(req, "sec-websocket-version", "13")) {
+ resp.setStatus(426);
+ resp.setHeader("Sec-WebSocket-Version", "13");
+ return;
+ }
+
+ key = req.getHeader("Sec-WebSocket-Key");
+ if (key == null) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return;
+ }
+
+ // Need an Endpoint instance to progress this further
+ ServerContainerImpl cp = ServerContainerImpl.getServerContainer();
+ Endpoint ep = null;
+ try {
+ ep = cp.getEndpoint(req.getServletPath());
+ } catch (InstantiationException | IllegalAccessException e) {
+ // This will trigger an error response
+ throw new ServletException(e);
+ }
+
+ ServerEndpointConfiguration epConfig =
+ (ServerEndpointConfiguration) ep.getEndpointConfiguration();
+
+ // Origin check
+ String origin = req.getHeader("Origin");
+ if (!epConfig.checkOrigin(origin)) {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return;
+ }
+
+ // Sub-protocols
+ List<String> subProtocols =
+ getTokensFromHeader(req, "Sec-WebSocket-Protocol");
+ if (!subProtocols.isEmpty()) {
+ subProtocol = epConfig.getNegotiatedSubprotocol(subProtocols);
+ }
+
+ // Extensions
+ List<String> requestedExtensions =
+ getTokensFromHeader(req, "Sec-WebSocket-Extensions");
+ if (!extensions.isEmpty()) {
+ extensions = epConfig.getNegotiatedExtensions(requestedExtensions);
+ }
+
+ // If we got this far, all is good. Accept the connection.
+ resp.setHeader("Upgrade", "websocket");
+ resp.setHeader("Connection", "upgrade");
+ resp.setHeader("Sec-WebSocket-Accept", getWebSocketAccept(key));
+ if (subProtocol != null) {
+ resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
+ }
+ if (!extensions.isEmpty()) {
+ StringBuilder sb = new StringBuilder();
+ Iterator<String> iter = extensions.iterator();
+ // There must be at least one
+ sb.append(iter.next());
+ while (iter.hasNext()) {
+ sb.append(',');
+ sb.append(iter.next());
+ }
+ resp.setHeader("Sec-WebSocket-Extensions", sb.toString());
+ }
+
+ ProtocolHandler wsHandler = new WsProtocolHandler(ep);
+
+ req.upgrade(wsHandler);
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private boolean headerContainsToken(HttpServletRequest req,
+ String headerName, String target) {
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ if (target.equalsIgnoreCase(token.trim())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ /*
+ * This only works for tokens. Quoted strings need more sophisticated
+ * parsing.
+ */
+ private List<String> getTokensFromHeader(HttpServletRequest req,
+ String headerName) {
+ List<String> result = new ArrayList<>();
+
+ Enumeration<String> headers = req.getHeaders(headerName);
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ String[] tokens = header.split(",");
+ for (String token : tokens) {
+ result.add(token.trim());
+ }
+ }
+ return result;
+ }
+
+
+ private String getWebSocketAccept(String key) throws ServletException {
+
+ MessageDigest sha1Helper = sha1Helpers.poll();
+ if (sha1Helper == null) {
+ try {
+ sha1Helper = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ServletException(e);
+ }
+ }
+
+ sha1Helper.reset();
+ sha1Helper.update(key.getBytes(ISO_8859_1));
+ String result = DatatypeConverter.printBase64Binary(
+ sha1Helper.digest(WS_ACCEPT));
+
+ sha1Helpers.add(sha1Helper);
+
+ return result;
}
}
Added: 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=1414427&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java Tue Nov 27 22:04:53 2012
@@ -0,0 +1,166 @@
+/*
+ * 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.tomcat.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.websocket.ClientContainer;
+import javax.websocket.CloseReason;
+import javax.websocket.Encoder;
+import javax.websocket.MessageHandler;
+import javax.websocket.RemoteEndpoint;
+import javax.websocket.Session;
+
+public class WsSession implements Session {
+
+ @Override
+ public ClientContainer getContainer() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void setEncoders(List<Encoder> encoders) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void addMessageHandler(MessageHandler listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public Set<MessageHandler> getMessageHandlers() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void removeMessageHandler(MessageHandler listener) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public String getProtocolVersion() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getNegotiatedSubprotocol() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public List<String> getNegotiatedExtensions() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean isSecure() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public long getInactiveTime() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public boolean isActive() {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public long getTimeout() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void setTimeout(long seconds) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void setMaximumMessageSize(long length) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public long getMaximumMessageSize() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public RemoteEndpoint getRemote() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void close(CloseReason closeStatus) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public URI getRequestURI() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Map<String, String[]> getRequestParameterMap() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public String getQueryString() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getPathParameters() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+}
Propchange: tomcat/trunk/java/org/apache/tomcat/websocket/WsSession.java
------------------------------------------------------------------------------
svn:eol-style = native
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=1414427&r1=1414426&r2=1414427&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 Nov 27 22:04:53 2012
@@ -25,7 +25,14 @@ import javax.websocket.Session;
public class EchoEndpoint extends Endpoint{
private static ServerEndpointConfiguration config =
- new DefaultServerConfiguration("/websocket/echoProgrammatic");
+ new DefaultServerConfiguration("/websocket/echoProgrammatic") {
+
+ @Override
+ public boolean checkOrigin(String originHeaderValue) {
+ // Accept any
+ return true;
+ }
+ };
@Override
public EndpointConfiguration getEndpointConfiguration() {
@@ -34,6 +41,8 @@ public class EchoEndpoint extends Endpoi
@Override
public void onOpen(Session session) {
+ // TODO - Review this debug hack
+ System.out.println("EchoEndpoint onOpen() called");
// TODO Auto-generated method stub
}
Modified: tomcat/trunk/webapps/examples/websocket/echo.html
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/websocket/echo.html?rev=1414427&r1=1414426&r2=1414427&view=diff
==============================================================================
--- tomcat/trunk/webapps/examples/websocket/echo.html (original)
+++ tomcat/trunk/webapps/examples/websocket/echo.html Tue Nov 27 22:04:53 2012
@@ -137,6 +137,9 @@
<!-- echo example using messages on the server side -->
<input id="radio2" type="radio" name="group1" value="/examples/websocket/echoMessage"
onclick="updateTarget(this.value);"> <label for="radio2">messages</label>
+ <!-- echo example using new programmatic API on the server side -->
+ <input id="radio3" type="radio" name="group1" value="/examples/websocket/echoProgrammatic"
+ onclick="updateTarget(this.value);"> <label for="radio2">new programmatic</label>
</div>
<div>
<input id="target" type="text" size="40" style="width: 350px"/>
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org