WICKET-5301 Create a module for Native WebSocket based on JSR 356
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/36ad94dd Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/36ad94dd Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/36ad94dd Branch: refs/heads/5299-ajax-strategy Commit: 36ad94ddf50121dd1f288232b3e6ce8453d919a5 Parents: d6ff34e Author: Martin Tzvetanov Grigorov <mgrigo...@apache.org> Authored: Mon Aug 5 10:05:47 2013 +0200 Committer: Martin Tzvetanov Grigorov <mgrigo...@apache.org> Committed: Mon Aug 5 10:05:47 2013 +0200 ---------------------------------------------------------------------- .../wicket-native-websocket/pom.xml | 1 + .../ws/api/AbstractWebSocketProcessor.java | 1 - .../protocol/ws/api/WebSocketBehavior.java | 4 + .../ws/api/res/js/wicket-websocket-jquery.js | 23 +- .../api/res/js/wicket-websocket-setup.js.tmpl | 2 +- .../wicket-native-websocket-javax/pom.xml | 67 +++ .../ws/javax/JavaxUpgradeHttpRequest.java | 558 +++++++++++++++++++ .../ws/javax/JavaxWebSocketConnection.java | 119 ++++ .../protocol/ws/javax/JavaxWebSocketFilter.java | 280 ++++++++++ .../ws/javax/JavaxWebSocketProcessor.java | 83 +++ .../protocol/ws/javax/WicketEndpoint.java | 67 +++ .../src/test/java/log4j.properties | 16 + .../util/licence/ApacheLicenceHeaderTest.java | 34 ++ 13 files changed, 1240 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/pom.xml ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/pom.xml b/wicket-experimental/wicket-native-websocket/pom.xml index e9e87cb..a2f8c05 100644 --- a/wicket-experimental/wicket-native-websocket/pom.xml +++ b/wicket-experimental/wicket-native-websocket/pom.xml @@ -33,5 +33,6 @@ <module>wicket-native-websocket-jetty</module> <module>wicket-native-websocket-jetty9</module> <module>wicket-native-websocket-tomcat</module> + <module>wicket-native-websocket-javax</module> </modules> </project> http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java index 16eeb5b..3ac7625 100644 --- a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java @@ -92,7 +92,6 @@ public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor public AbstractWebSocketProcessor(final HttpServletRequest request, final WebApplication application) { this.sessionId = request.getSession(true).getId(); - Session.get().bind(); String pageId = request.getParameter("pageId"); Checks.notEmpty(pageId, "Request parameter 'pageId' is required!"); http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java index ec2bbd5..f7f1e5d 100644 --- a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java @@ -114,6 +114,10 @@ public abstract class WebSocketBehavior extends Behavior Url baseUrl = component.getRequestCycle().getUrlRenderer().getBaseUrl(); CharSequence ajaxBaseUrl = Strings.escapeMarkup(baseUrl.toString()); variables.put("baseUrl", ajaxBaseUrl); + + String contextPath = component.getRequest().getContextPath(); + variables.put("contextPath", contextPath); + String webSocketSetupScript = webSocketSetupTemplate.asString(variables); response.render(OnDomReadyHeaderItem.forScript(webSocketSetupScript)); http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-jquery.js ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-jquery.js b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-jquery.js index d1d9495..7423c47 100644 --- a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-jquery.js +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-jquery.js @@ -38,18 +38,15 @@ var self = this, url, - hashIdx, - delim; - - url = document.location.toString() - .replace('http://', 'ws://') - .replace('https://', 'wss://'); - hashIdx = url.indexOf('#'); - if (hashIdx > -1) { - url = url.substring(0, hashIdx); - } - delim = url.indexOf('?') > -1 ? '&' : '?'; - url += delim + 'pageId=' + Wicket.WebSocket.pageId; + protocol; + + protocol = document.location.protocol + .replace('https:', 'wss:') + .replace('http:', 'ws:'); + + url = protocol + '//' + document.location.host + Wicket.WebSocket.contextPath + '/wicket/websocket'; + + url += '?pageId=' + Wicket.WebSocket.pageId; url += '&wicket-ajax-baseurl=' + Wicket.WebSocket.baseUrl; self.ws = new WebSocket(url); @@ -135,4 +132,4 @@ } }; -})(); \ No newline at end of file +})(); http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-setup.js.tmpl ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-setup.js.tmpl b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-setup.js.tmpl index 3d8950a..6d2f8a3 100644 --- a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-setup.js.tmpl +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/res/js/wicket-websocket-setup.js.tmpl @@ -2,7 +2,7 @@ 'use strict'; if (!Wicket.WebSocket.pageId) { - jQuery.extend(Wicket.WebSocket, { pageId: ${pageId}, baseUrl: '${baseUrl}' }); + jQuery.extend(Wicket.WebSocket, { pageId: ${pageId}, baseUrl: '${baseUrl}', contextPath: '${contextPath}' }); Wicket.WebSocket.createDefaultConnection(); } })(); http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/pom.xml ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/pom.xml b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/pom.xml new file mode 100644 index 0000000..1d05f36 --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/pom.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-native-websocket</artifactId> + <version>0.9-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>wicket-native-websocket-javax</artifactId> + <packaging>jar</packaging> + <version>0.9-SNAPSHOT</version> + <name>Wicket Native WebSocket Javax</name> + <description>Provides the common code needed for the different integrations with web container's WebSocket implementations</description> + + <dependencies> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-core</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-native-websocket-core</artifactId> + </dependency> + + <dependency> + <groupId>javax.websocket</groupId> + <artifactId>javax.websocket-api</artifactId> + <version>1.0</version> + <scope>provided</scope> + </dependency> + + </dependencies> + + <repositories> + <repository> + <id>tc8</id> + <url>https://repository.apache.org/content/repositories/orgapachetomcat-048/</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>false</enabled> + </snapshots> + </repository> + </repositories> + +</project> http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxUpgradeHttpRequest.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxUpgradeHttpRequest.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxUpgradeHttpRequest.java new file mode 100644 index 0000000..2fea729 --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxUpgradeHttpRequest.java @@ -0,0 +1,558 @@ +/* + * 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.wicket.protocol.ws.javax; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.Part; +import javax.websocket.Session; + +import org.apache.wicket.util.string.StringValue; + +/** + * An artificial HttpServletRequest with data collected from the + * available WebSocket Session and from the HandshakeRequest + */ +public class JavaxUpgradeHttpRequest implements HttpServletRequest +{ + private final HttpSession httpSession; + private final String queryString; + private final Principal userPrincipal; + private final String requestUri; + private final Map<String, String[]> parametersMap; + private final Map<String, List<String>> headers; + private final String contextPath = ""; // artificial + + public JavaxUpgradeHttpRequest(final Session session) + { + Map<String, Object> userProperties = session.getUserProperties(); + this.httpSession = (HttpSession) userProperties.get("session"); + this.headers = (Map<String, List<String>>) userProperties.get("headers"); + this.queryString = (String) userProperties.get("queryString"); + this.userPrincipal = (Principal) userProperties.get("userPrincipal"); + this.requestUri = userProperties.get("requestURI").toString(); + + this.parametersMap = new HashMap<>(); + + Map<String, List<String>> parameters = (Map<String, List<String>>) userProperties.get("parameterMap"); + for (Map.Entry<String, List<String>> entry : parameters.entrySet()) + { + String name = entry.getKey(); + List<String> value = entry.getValue(); + parametersMap.put(name, value.toArray(new String[value.size()])); + } + } + + @Override + public String getAuthType() + { + return null; + } + + @Override + public Cookie[] getCookies() + { + return new Cookie[0]; + } + + @Override + public long getDateHeader(String name) + { + String headerValue = getHeader(name); + return StringValue.valueOf(headerValue).toLong(); + } + + @Override + public String getHeader(String name) + { + String value = null; + if (headers != null) + { + List<String> headerValues = headers.get(name); + if (headerValues.isEmpty() == false) + { + value = headerValues.get(0); + } + } + return value; + } + + @Override + public Enumeration<String> getHeaders(String name) + { + Enumeration<String> values = null; + if (headers != null) + { + List<String> headerValues = headers.get(name); + if (headerValues.isEmpty() == false) + { + final Iterator<String> iterator = headerValues.iterator(); + values = new Enumeration<String>() + { + @Override + public boolean hasMoreElements() + { + return iterator.hasNext(); + } + + @Override + public String nextElement() + { + return iterator.next(); + } + }; + } + } + return values; + } + + @Override + public Enumeration<String> getHeaderNames() + { + Enumeration<String> names = null; + if (headers != null) + { + Set<String> headerNames = headers.keySet(); + if (headerNames.isEmpty() == false) + { + final Iterator<String> iterator = headerNames.iterator(); + names = new Enumeration<String>() + { + @Override + public boolean hasMoreElements() + { + return iterator.hasNext(); + } + + @Override + public String nextElement() + { + return iterator.next(); + } + }; + } + } + return names; + } + + @Override + public int getIntHeader(String name) + { + String headerValue = getHeader(name); + return StringValue.valueOf(headerValue).toInt(); + } + + @Override + public String getMethod() + { + return null; + } + + @Override + public String getPathInfo() + { + return null; + } + + @Override + public String getPathTranslated() + { + return null; + } + + @Override + public String getContextPath() + { + return contextPath; + } + + @Override + public String getQueryString() + { + return queryString; + } + + @Override + public String getRemoteUser() + { + return null; + } + + @Override + public boolean isUserInRole(String role) + { + return false; + } + + @Override + public Principal getUserPrincipal() + { + return userPrincipal; + } + + @Override + public String getRequestedSessionId() + { + return null; + } + + @Override + public String getRequestURI() + { + return requestUri; + } + + @Override + public StringBuffer getRequestURL() + { + return null; + } + + @Override + public String getServletPath() + { + return null; + } + + @Override + public HttpSession getSession(boolean create) + { + return httpSession; + } + + @Override + public HttpSession getSession() + { + return httpSession; + } + + @Override + public boolean isRequestedSessionIdValid() + { + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() + { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() + { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() + { + return false; + } + + @Override + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException + { + return false; + } + + @Override + public void login(String username, String password) throws ServletException + { + } + + @Override + public void logout() throws ServletException + { + } + + @Override + public Collection<Part> getParts() throws IOException, ServletException + { + return null; + } + + @Override + public Part getPart(String name) throws IOException, ServletException + { + return null; + } + + @Override + public Object getAttribute(String name) + { + return null; + } + + @Override + public Enumeration<String> getAttributeNames() + { + return new Enumeration<String>() + { + @Override + public boolean hasMoreElements() + { + return false; + } + + @Override + public String nextElement() + { + return null; + } + }; + } + + @Override + public String getCharacterEncoding() + { + return null; + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException + { + } + + @Override + public int getContentLength() + { + return 0; + } + + @Override + public String getContentType() + { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + return null; + } + + @Override + public String getParameter(String name) + { + String[] values = parametersMap.get(name); + return values != null ? values[0] : null; + } + + @Override + public Enumeration<String> getParameterNames() + { + final Iterator<String> iterator = parametersMap.keySet().iterator(); + return new Enumeration<String>() + { + @Override + public boolean hasMoreElements() + { + return iterator.hasNext(); + } + + @Override + public String nextElement() + { + return iterator.next(); + } + }; + } + + @Override + public String[] getParameterValues(String name) + { + return parametersMap.get(name); + } + + @Override + public Map<String, String[]> getParameterMap() + { + return parametersMap; + } + + @Override + public String getProtocol() + { + return null; + } + + @Override + public String getScheme() + { + return null; + } + + @Override + public String getServerName() + { + return null; + } + + @Override + public int getServerPort() + { + return 0; + } + + @Override + public BufferedReader getReader() throws IOException + { + return null; + } + + @Override + public String getRemoteAddr() + { + return null; + } + + @Override + public String getRemoteHost() + { + return null; + } + + @Override + public void setAttribute(String name, Object o) + { + } + + @Override + public void removeAttribute(String name) + { + } + + @Override + public Locale getLocale() + { + return null; + } + + @Override + public Enumeration<Locale> getLocales() + { + return null; + } + + @Override + public boolean isSecure() + { + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) + { + return null; + } + + @Override + public String getRealPath(String path) + { + return null; + } + + @Override + public int getRemotePort() + { + return 0; + } + + @Override + public String getLocalName() + { + return null; + } + + @Override + public String getLocalAddr() + { + return null; + } + + @Override + public int getLocalPort() + { + return 0; + } + + @Override + public ServletContext getServletContext() + { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException + { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + return null; + } + + @Override + public boolean isAsyncStarted() + { + return false; + } + + @Override + public boolean isAsyncSupported() + { + return false; + } + + @Override + public AsyncContext getAsyncContext() + { + return null; + } + + @Override + public DispatcherType getDispatcherType() + { + return null; + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketConnection.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketConnection.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketConnection.java new file mode 100644 index 0000000..553682a --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketConnection.java @@ -0,0 +1,119 @@ +/* + * 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.wicket.protocol.ws.javax; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import javax.websocket.CloseReason; +import javax.websocket.Session; + +import org.apache.wicket.protocol.ws.api.AbstractWebSocketConnection; +import org.apache.wicket.protocol.ws.api.AbstractWebSocketProcessor; +import org.apache.wicket.protocol.ws.api.IWebSocketConnection; +import org.apache.wicket.util.lang.Args; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A wrapper around Jetty9's native WebSocketConnection. + * + * @since 6.2 + */ +public class JavaxWebSocketConnection extends AbstractWebSocketConnection +{ + private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketConnection.class); + + private final Session session; + + /** + * Constructor. + * + * @param session + * the jetty websocket connection + */ + public JavaxWebSocketConnection(Session session, AbstractWebSocketProcessor webSocketProcessor) + { + super(webSocketProcessor); + this.session = Args.notNull(session, "connection"); + } + + @Override + public boolean isOpen() + { + return session.isOpen(); + } + + @Override + public void close(int code, String reason) + { + if (isOpen()) + { + try + { + session.close(new CloseReason(new CloseCode(code), reason)); + } catch (IOException iox) + { + LOG.error("An error occurred while closing WebSocket session", iox); + } + } + } + + @Override + public IWebSocketConnection sendMessage(String message) throws IOException + { + checkClosed(); + + session.getBasicRemote().sendText(message); + return this; + } + + @Override + public IWebSocketConnection sendMessage(byte[] message, int offset, int length) + throws IOException + { + checkClosed(); + + ByteBuffer buf = ByteBuffer.wrap(message, offset, length); + session.getBasicRemote().sendBinary(buf); + return this; + } + + private void checkClosed() + { + if (!isOpen()) + { + throw new IllegalStateException("The connection is closed."); + } + } + + private static class CloseCode implements CloseReason.CloseCode + { + private final int code; + + private CloseCode(int code) + { + this.code = code; + } + + @Override + public int getCode() + { + return code; + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketFilter.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketFilter.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketFilter.java new file mode 100644 index 0000000..55b202b --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketFilter.java @@ -0,0 +1,280 @@ +/* + * 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.wicket.protocol.ws.javax; + +import java.net.URI; +import java.security.Principal; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.websocket.Decoder; +import javax.websocket.DeploymentException; +import javax.websocket.Encoder; +import javax.websocket.Extension; +import javax.websocket.HandshakeResponse; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; + +import org.apache.wicket.protocol.http.WicketFilter; +import org.apache.wicket.protocol.ws.AbstractUpgradeFilter; +import org.apache.wicket.util.string.Strings; + +/** + * An upgrade filter that setups javax.websocket + */ +public class JavaxWebSocketFilter extends AbstractUpgradeFilter +{ + /** + * A fake mount path used for WebSocket endpoint. + * WicketFilter should not process this path. + * @see WicketFilter#ignorePaths + */ + private static final String WICKET_WEB_SOCKET_PATH = "/wicket/websocket"; + + /** + * A key used to store the application object in WebSocket's Endpoint user properties + */ + static final String APPLICATION_KEY = "wicket.application"; + + @Override + public void init(final boolean isServlet, final FilterConfig filterConfig) throws ServletException + { + super.init(isServlet, new JavaxWebSocketFilterConfig(filterConfig)); + + ServletContext servletContext = filterConfig.getServletContext(); + ServerContainer sc = (ServerContainer) servletContext.getAttribute(ServerContainer.class.getName()); + + try + { + ServerEndpointConfig config = new WicketServerEndpointConfig(ServerEndpointConfig.Builder.create(WicketEndpoint.class, WICKET_WEB_SOCKET_PATH).build()); + config.getUserProperties().put(APPLICATION_KEY, getApplication()); + sc.addEndpoint(config); + } + catch (DeploymentException e) { + throw new IllegalStateException(e); + } + } + + /** + * A wrapper of the passed FilterConfig in #init() that adds #WICKET_WEB_SOCKET_PATH to + * the list of ignored paths + */ + private static class JavaxWebSocketFilterConfig implements FilterConfig + { + private final FilterConfig delegate; + + private JavaxWebSocketFilterConfig(FilterConfig delegate) + { + this.delegate = delegate; + } + + @Override + public String getFilterName() + { + return delegate.getFilterName(); + } + + @Override + public ServletContext getServletContext() + { + return delegate.getServletContext(); + } + + @Override + public String getInitParameter(String s) + { + String result = delegate.getInitParameter(s); + + if (WicketFilter.IGNORE_PATHS_PARAM.equalsIgnoreCase(s)) + { + if (Strings.isEmpty(result)) + { + result = WICKET_WEB_SOCKET_PATH; + } + else + { + result = result + ',' + WICKET_WEB_SOCKET_PATH; + } + } + + return result; + } + + @Override + public Enumeration<String> getInitParameterNames() + { + return delegate.getInitParameterNames(); + } + } + + /** + * A ServerEndpointConfig that uses custom Configurator to collect + * all available information from the passed HandshakeRequest + */ + private static class WicketServerEndpointConfig implements ServerEndpointConfig + { + private final ServerEndpointConfig delegate; + private Configurator configurator; + + private WicketServerEndpointConfig(ServerEndpointConfig delegate) + { + this.delegate = delegate; + } + + @Override + public Class<?> getEndpointClass() + { + return delegate.getEndpointClass(); + } + + @Override + public String getPath() + { + return delegate.getPath(); + } + + @Override + public List<String> getSubprotocols() + { + return delegate.getSubprotocols(); + } + + @Override + public List<Extension> getExtensions() + { + return delegate.getExtensions(); + } + + @Override + public Configurator getConfigurator() + { + if (configurator == null) + { + configurator = new JavaxWebSocketConfigurator(delegate.getConfigurator()); + } + return configurator; + } + + @Override + public List<Class<? extends Encoder>> getEncoders() + { + return delegate.getEncoders(); + } + + @Override + public List<Class<? extends Decoder>> getDecoders() + { + return delegate.getDecoders(); + } + + @Override + public Map<String, Object> getUserProperties() + { + return delegate.getUserProperties(); + } + } + + /** + * A custom Configurator that collects all available information from the HandshakeRequest + */ + private static class JavaxWebSocketConfigurator extends ServerEndpointConfig.Configurator + { + private final ServerEndpointConfig.Configurator delegate; + + public JavaxWebSocketConfigurator(ServerEndpointConfig.Configurator delegate) + { + this.delegate = delegate; + } + + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) + { + delegate.modifyHandshake(sec, request, response); + + // do not store null keys/values because Tomcat 8 uses ConcurrentMap for UserProperties + + Map<String, Object> userProperties = sec.getUserProperties(); + Object httpSession = request.getHttpSession(); + if (httpSession != null) + { + userProperties.put("session", httpSession); + } + + Map<String, List<String>> headers = request.getHeaders(); + if (headers != null) + { + userProperties.put("headers", headers); + } + + + Map<String, List<String>> parameterMap = request.getParameterMap(); + if (parameterMap != null) + { + userProperties.put("parameterMap", parameterMap); + } + + + String queryString = request.getQueryString(); + if (queryString != null) + { + userProperties.put("queryString", queryString); + } + + + URI requestURI = request.getRequestURI(); + if (requestURI != null) + { + userProperties.put("requestURI", requestURI); + } + + Principal userPrincipal = request.getUserPrincipal(); + if (userPrincipal != null) + { + userProperties.put("userPrincipal", userPrincipal); + } + } + + @Override + public String getNegotiatedSubprotocol(List<String> supported, List<String> requested) + { + return delegate.getNegotiatedSubprotocol(supported, requested); + } + + @Override + public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested) + { + return delegate.getNegotiatedExtensions(installed, requested); + } + + @Override + public boolean checkOrigin(String originHeaderValue) + { + return delegate.checkOrigin(originHeaderValue); + } + + @Override + public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException + { + return super.getEndpointInstance(endpointClass); + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketProcessor.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketProcessor.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketProcessor.java new file mode 100644 index 0000000..1fa18a9 --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/JavaxWebSocketProcessor.java @@ -0,0 +1,83 @@ +/* + * 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.wicket.protocol.ws.javax; + +import java.nio.ByteBuffer; + +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +import org.apache.wicket.protocol.http.WebApplication; +import org.apache.wicket.protocol.ws.api.AbstractWebSocketProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An {@link org.apache.wicket.protocol.ws.api.IWebSocketProcessor processor} that integrates with + * Jetty 9.x {@link Session web socket} implementation. + * + * @since 6.2 + */ +public class JavaxWebSocketProcessor extends AbstractWebSocketProcessor +{ + private static final Logger LOG = LoggerFactory.getLogger(JavaxWebSocketProcessor.class); + + /** + * Constructor. + * + * @param session + * the WebSocket session + * @param application + * the current Wicket Application + */ + public JavaxWebSocketProcessor(final Session session, final WebApplication application) + { + super(new JavaxUpgradeHttpRequest(session), application); + + onConnect(new JavaxWebSocketConnection(session, this)); + + session.addMessageHandler(new StringMessageHandler()); + session.addMessageHandler(new BinaryMessageHandler()); + } + + + @Override + public void onOpen(Object containerConnection) + { + } + + private class StringMessageHandler implements MessageHandler.Whole<String> + { + @Override + public void onMessage(String message) + { + JavaxWebSocketProcessor.this.onMessage(message); + } + } + + private class BinaryMessageHandler implements MessageHandler.Whole<ByteBuffer> + { + @Override + public void onMessage(ByteBuffer message) + { + byte[] array = message.array(); + JavaxWebSocketProcessor.this.onMessage(array, 0, array.length); + } + } + + +} http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/WicketEndpoint.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/WicketEndpoint.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/WicketEndpoint.java new file mode 100644 index 0000000..57b4e0b --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/main/java/org/apache/wicket/protocol/ws/javax/WicketEndpoint.java @@ -0,0 +1,67 @@ +/* + * 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.wicket.protocol.ws.javax; + +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.Session; + +import org.apache.wicket.ThreadContext; +import org.apache.wicket.protocol.http.WebApplication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WicketEndpoint extends Endpoint +{ + private static final Logger LOG = LoggerFactory.getLogger(WicketEndpoint.class); + + private JavaxWebSocketProcessor javaxWebSocketProcessor; + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) + { + WebApplication app = (WebApplication) session.getUserProperties().get(JavaxWebSocketFilter.APPLICATION_KEY); + + try + { + ThreadContext.setApplication(app); + javaxWebSocketProcessor = new JavaxWebSocketProcessor(session, app); + } + finally + { + ThreadContext.detach(); + } + + } + + @Override + public void onClose(Session session, CloseReason closeReason) + { + super.onClose(session, closeReason); + + javaxWebSocketProcessor.onClose(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase()); + } + + @Override + public void onError(Session session, Throwable t) + { + super.onError(session, t); + + LOG.error("An error occurred in web socket connection with id : " + session.getId(), t); + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/log4j.properties ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/log4j.properties b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/log4j.properties new file mode 100644 index 0000000..976b5c8 --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/log4j.properties @@ -0,0 +1,16 @@ +log4j.debug=false + +log4j.rootLogger=INFO,Stdout + +# please keep this setting FATAL to avoid questions from users +# why there are stacktraces in the test output. You can turn it +# down if you need to when testing, but don't check it in. (eelco) + +# changing back to ERROR. Looks like in some cases the log4j.properties +# in wicket gets picked which results in not printing the exceptions +# and that can be a bit dangerous (matej) +log4j.logger.org.apache.wicket=ERROR + +log4j.appender.Stdout=org.apache.log4j.ConsoleAppender +log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n http://git-wip-us.apache.org/repos/asf/wicket/blob/36ad94dd/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/org/apache/wicket/protocol/ws/util/licence/ApacheLicenceHeaderTest.java ---------------------------------------------------------------------- diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/org/apache/wicket/protocol/ws/util/licence/ApacheLicenceHeaderTest.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/org/apache/wicket/protocol/ws/util/licence/ApacheLicenceHeaderTest.java new file mode 100644 index 0000000..cd3cbf1 --- /dev/null +++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-javax/src/test/java/org/apache/wicket/protocol/ws/util/licence/ApacheLicenceHeaderTest.java @@ -0,0 +1,34 @@ +/* + * 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.wicket.protocol.ws.util.licence; + +import org.apache.wicket.util.license.ApacheLicenseHeaderTestCase; + +/** + * Test that the license headers are in place in this project. The tests are run from + * {@link org.apache.wicket.util.license.ApacheLicenseHeaderTestCase}, but you can add project specific tests here if needed. + */ +public class ApacheLicenceHeaderTest extends ApacheLicenseHeaderTestCase +{ + /** + * Construct. + */ + public ApacheLicenceHeaderTest() + { + // addHeaders = true; + } +}