You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2012/06/19 15:29:15 UTC
[1/2] git commit: WICKET-4611 Provide native integration with web
containers' websocket support
Updated Branches:
refs/heads/master 571f555da -> b3687306e
WICKET-4611 Provide native integration with web containers' websocket support
Initial push for the native websocket support for Jetty 7.5+ and Tomcat 7.0.27+.
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/b3687306
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/b3687306
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/b3687306
Branch: refs/heads/master
Commit: b3687306e1f392c85983942f6b37c81a160257bd
Parents: 571f555
Author: Martin Tzvetanov Grigorov <mg...@apache.org>
Authored: Tue Jun 19 16:16:47 2012 +0300
Committer: Martin Tzvetanov Grigorov <mg...@apache.org>
Committed: Tue Jun 19 16:16:47 2012 +0300
----------------------------------------------------------------------
wicket-experimental/pom.xml | 1 +
.../wicket-native-websocket/pom.xml | 46 ++
.../wicket-native-websocket-core/pom.xml | 47 ++
.../wicket/ajax/IWebSocketRequestHandler.java | 45 ++
.../wicket/ajax/WebSocketRequestHandler.java | 244 +++++++++
.../protocol/http/AbstractUpgradeFilter.java | 258 +++++++++
.../ws/api/AbstractWebSocketProcessor.java | 253 +++++++++
.../wicket/protocol/ws/api/HttpSessionCopy.java | 145 +++++
.../ws/api/IWebSocketConnectionRegistry.java | 64 +++
.../protocol/ws/api/IWebSocketProcessor.java | 62 +++
.../wicket/protocol/ws/api/ServletRequestCopy.java | 414 +++++++++++++++
.../ws/api/SimpleWebSocketConnectionRegistry.java | 115 ++++
.../wicket/protocol/ws/api/WebSocketBehavior.java | 113 ++++
.../protocol/ws/api/WebSocketConnection.java | 67 +++
.../wicket/protocol/ws/api/WebSocketRequest.java | 108 ++++
.../wicket/protocol/ws/api/WebSocketResponse.java | 136 +++++
.../WicketWebSocketJQueryResourceReference.java | 66 +++
.../ws/api/event/WebSocketBinaryPayload.java | 43 ++
.../ws/api/event/WebSocketClosedPayload.java | 43 ++
.../ws/api/event/WebSocketConnectedPayload.java | 43 ++
.../protocol/ws/api/event/WebSocketPayload.java | 42 ++
.../ws/api/event/WebSocketTextPayload.java | 46 ++
.../protocol/ws/api/message/BinaryMessage.java | 61 +++
.../protocol/ws/api/message/ClosedMessage.java | 61 +++
.../protocol/ws/api/message/ConnectedMessage.java | 61 +++
.../protocol/ws/api/message/IWebSocketMessage.java | 26 +
.../protocol/ws/api/message/TextMessage.java | 37 ++
.../ws/api/res/js/wicket-websocket-jquery.js | 105 ++++
.../ws/util/tester/TestWebSocketConnection.java | 89 +++
.../ws/util/tester/TestWebSocketProcessor.java | 108 ++++
.../protocol/ws/util/tester/WebSocketTester.java | 115 ++++
.../protocol/ws/util/tester/WebSocketTestPage.java | 78 +++
.../ws/util/tester/WebSocketTesterTest.java | 103 ++++
.../wicket-native-websocket-jetty/pom.xml | 29 +
.../protocol/http/Jetty7WebSocketFilter.java | 91 ++++
.../ws/jetty/JettyWebSocketConnection.java | 79 +++
.../protocol/ws/jetty/JettyWebSocketProcessor.java | 67 +++
.../wicket-native-websocket-tomcat/pom.xml | 43 ++
.../protocol/http/Tomcat7WebSocketFilter.java | 89 +++
.../ws/tomcat7/TomcatWebSocketConnection.java | 98 ++++
.../ws/tomcat7/TomcatWebSocketProcessor.java | 83 +++
41 files changed, 3824 insertions(+), 0 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/pom.xml
----------------------------------------------------------------------
diff --git a/wicket-experimental/pom.xml b/wicket-experimental/pom.xml
index 4658bc8..ba1809c 100644
--- a/wicket-experimental/pom.xml
+++ b/wicket-experimental/pom.xml
@@ -29,5 +29,6 @@
<description>Wicket-Experimental contains experimental Wicket modules that may or may not be supported in the future.</description>
<modules>
<module>wicket-atmosphere</module>
+ <module>wicket-native-websocket</module>
</modules>
</project>
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/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
new file mode 100644
index 0000000..3a9d408
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/pom.xml
@@ -0,0 +1,46 @@
+<?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-experimental</artifactId>
+ <version>6.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>wicket-native-websocket</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Apache Wicket Native WebSocket Parent</name>
+ <description>Wicket Native WebSocket provides native integration for WebSocket support with Servlet web containers.</description>
+ <modules>
+ <module>wicket-native-websocket-core</module>
+ <module>wicket-native-websocket-jetty</module>
+ <module>wicket-native-websocket-tomcat</module>
+ </modules>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.wicket</groupId>
+ <artifactId>wicket-native-websocket-core</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <type>jar</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+</project>
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/pom.xml
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/pom.xml b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/pom.xml
new file mode 100644
index 0000000..7be630d
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/pom.xml
@@ -0,0 +1,47 @@
+<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.1-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>wicket-native-websocket-core</artifactId>
+ <packaging>jar</packaging>
+ <version>0.1-SNAPSHOT</version>
+ <name>Apache Wicket Native WebSocket Core</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>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+
+ </dependencies>
+
+ <!--<build>-->
+ <!--<plugins>-->
+ <!--<plugin>-->
+ <!--<inherited>true</inherited>-->
+ <!--<groupId>org.apache.maven.plugins</groupId>-->
+ <!--<artifactId>maven-compiler-plugin</artifactId>-->
+ <!--<version>2.5.1</version>-->
+ <!--<configuration>-->
+ <!--<source>1.6</source>-->
+ <!--<target>1.6</target>-->
+ <!--<optimize>true</optimize>-->
+ <!--<debug>true</debug>-->
+ <!--</configuration>-->
+ <!--</plugin>-->
+ <!--</plugins>-->
+ <!--</build>-->
+</project>
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/IWebSocketRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/IWebSocketRequestHandler.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/IWebSocketRequestHandler.java
new file mode 100644
index 0000000..d177161
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/IWebSocketRequestHandler.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ajax;
+
+/**
+ * An interface for outbound communication with web socket clients
+ *
+ * @since 6.0
+ */
+public interface IWebSocketRequestHandler
+{
+ /**
+ * Pushes a text message to the client.
+ *
+ * @param message
+ * the text message to push to the client if the web socket connection is open
+ */
+ void push(String message);
+
+ /**
+ * Pushes a binary message to the client.
+ *
+ * @param message
+ * the binary message to push to the client if the web socket connection is open
+ * @param offset
+ * the offset to start to read from the message
+ * @param length
+ * how many bytes to read from the message
+ */
+ void push(byte[] message, int offset, int length);
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/WebSocketRequestHandler.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/WebSocketRequestHandler.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/WebSocketRequestHandler.java
new file mode 100644
index 0000000..8fdc4e2
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/ajax/WebSocketRequestHandler.java
@@ -0,0 +1,244 @@
+package org.apache.wicket.ajax;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
+import org.apache.wicket.core.request.handler.logger.PageLogData;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.protocol.ws.api.WebSocketConnection;
+import org.apache.wicket.request.ILogData;
+import org.apache.wicket.request.IRequestCycle;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.visit.IVisit;
+import org.apache.wicket.util.visit.IVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An extension of AjaxRequestTarget that also supports pushing data from the server to the
+ * client.
+ *
+ * @since 6.0
+ */
+public class WebSocketRequestHandler implements AjaxRequestTarget, IWebSocketRequestHandler
+{
+ private static final Logger LOG = LoggerFactory.getLogger(WebSocketRequestHandler.class);
+
+ private final Page page;
+
+ private final WebSocketConnection connection;
+
+ private final AbstractAjaxResponse ajaxResponse;
+
+ private PageLogData logData;
+
+ public WebSocketRequestHandler(final Component component, final WebSocketConnection connection)
+ {
+ this.page = Args.notNull(component, "component").getPage();
+ this.connection = Args.notNull(connection, "connection");
+ this.ajaxResponse = new XmlAjaxResponse(page)
+ {
+ @Override
+ protected void fireOnAfterRespondListeners(Response response)
+ {
+ }
+
+ @Override
+ protected void fireOnBeforeRespondListeners()
+ {
+ }
+ };
+ }
+
+ @Override
+ public void push(String message)
+ {
+ if (connection.isOpen())
+ {
+ try
+ {
+ connection.sendMessage(message);
+ } catch (IOException iox)
+ {
+ LOG.error("An error occurred while pushing text message.", iox);
+ }
+ }
+ }
+
+ @Override
+ public void push(byte[] message, int offset, int length)
+ {
+ if (connection.isOpen())
+ {
+ try
+ {
+ connection.sendMessage(message, offset, length);
+ } catch (IOException iox)
+ {
+ LOG.error("An error occurred while pushing binary message.", iox);
+ }
+ }
+ }
+
+ @Override
+ public void add(Component component, String markupId)
+ {
+ ajaxResponse.add(component, markupId);
+ }
+
+ @Override
+ public void add(Component... components)
+ {
+ for (final Component component : components)
+ {
+ Args.notNull(component, "component");
+
+ if (component.getOutputMarkupId() == false)
+ {
+ throw new IllegalArgumentException(
+ "cannot update component that does not have setOutputMarkupId property set to true. Component: " +
+ component.toString());
+ }
+ add(component, component.getMarkupId());
+ }
+ }
+
+ @Override
+ public final void addChildren(MarkupContainer parent, Class<?> childCriteria)
+ {
+ Args.notNull(parent, "parent");
+ Args.notNull(childCriteria, "childCriteria");
+
+ parent.visitChildren(childCriteria, new IVisitor<Component, Void>()
+ {
+ @Override
+ public void component(final Component component, final IVisit<Void> visit)
+ {
+ add(component);
+ visit.dontGoDeeper();
+ }
+ });
+ }
+
+ @Override
+ public void addListener(IListener listener)
+ {
+ }
+
+ @Override
+ public void appendJavaScript(CharSequence javascript)
+ {
+ ajaxResponse.appendJavaScript(javascript);
+ }
+
+ @Override
+ public void prependJavaScript(CharSequence javascript)
+ {
+ ajaxResponse.prependJavaScript(javascript);
+ }
+
+ @Override
+ public void registerRespondListener(ITargetRespondListener listener)
+ {
+ }
+
+ @Override
+ public Collection<? extends Component> getComponents()
+ {
+ return ajaxResponse.getComponents();
+ }
+
+ @Override
+ public final void focusComponent(Component component)
+ {
+ if (component != null && component.getOutputMarkupId() == false)
+ {
+ throw new IllegalArgumentException(
+ "cannot update component that does not have setOutputMarkupId property set to true. Component: " +
+ component.toString());
+ }
+ final String id = component != null ? ("'" + component.getMarkupId() + "'") : "null";
+ appendJavaScript("Wicket.Focus.setFocusOnId(" + id + ");");
+ }
+
+ @Override
+ public IHeaderResponse getHeaderResponse()
+ {
+ return ajaxResponse.getHeaderResponse();
+ }
+
+ @Override
+ public String getLastFocusedElementId()
+ {
+ WebRequest request = (WebRequest) page.getRequest();
+ String id = request.getHeader("Wicket-FocusedElementId");
+ return Strings.isEmpty(id) ? null : id;
+ }
+
+ @Override
+ public Page getPage()
+ {
+ return page;
+ }
+
+ @Override
+ public Integer getPageId()
+ {
+ return page.getPageId();
+ }
+
+ @Override
+ public boolean isPageInstanceCreated()
+ {
+ return true;
+ }
+
+ @Override
+ public Integer getRenderCount()
+ {
+ return page.getRenderCount();
+ }
+
+ @Override
+ public ILogData getLogData()
+ {
+ return logData;
+ }
+
+ @Override
+ public Class<? extends IRequestablePage> getPageClass()
+ {
+ return page.getPageClass();
+ }
+
+ @Override
+ public PageParameters getPageParameters()
+ {
+ return page.getPageParameters();
+ }
+
+ @Override
+ public void respond(IRequestCycle requestCycle)
+ {
+ ajaxResponse.writeTo(requestCycle.getResponse(), "UTF-8");
+ }
+
+ @Override
+ public void detach(IRequestCycle requestCycle)
+ {
+ if (logData == null)
+ {
+ logData = new PageLogData(page);
+ }
+
+ ajaxResponse.detach(requestCycle);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/http/AbstractUpgradeFilter.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/http/AbstractUpgradeFilter.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/http/AbstractUpgradeFilter.java
new file mode 100644
index 0000000..fa76ecb
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/http/AbstractUpgradeFilter.java
@@ -0,0 +1,258 @@
+/*
+ * 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.http;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.request.http.WebResponse;
+
+/**
+ * An extension of WicketFilter that is used to check whether
+ * the processed HttpServletRequest needs to upgrade its protocol
+ * from HTTP to something else
+ *
+ * @since 6.0
+ */
+public class AbstractUpgradeFilter extends WicketFilter
+{
+ /**
+ * This is Wicket's main method to execute a request
+ *
+ * @param request
+ * the http request
+ * @param response
+ * the http response
+ * @param chain
+ * the filter chain
+ * @return false, if the request could not be processed
+ * @throws IOException
+ * @throws ServletException
+ */
+ // TODO improve WicketFilter#processRequest() to provide a hook instead of copy/pasting its whole code
+ @Override
+ boolean processRequest(ServletRequest request, final ServletResponse response,
+ final FilterChain chain) throws IOException, ServletException
+ {
+ final ThreadContext previousThreadContext = ThreadContext.detach();
+
+ // Assume we are able to handle the request
+ boolean res = true;
+
+ final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
+ final ClassLoader newClassLoader = getClassLoader();
+
+ try
+ {
+ if (previousClassLoader != newClassLoader)
+ {
+ Thread.currentThread().setContextClassLoader(newClassLoader);
+ }
+
+ HttpServletRequest httpServletRequest = (HttpServletRequest)request;
+ HttpServletResponse httpServletResponse = (HttpServletResponse)response;
+
+ // Make sure getFilterPath() gets called before checkIfRedirectRequired()
+ String filterPath = getFilterPath(httpServletRequest);
+
+ if (filterPath == null)
+ {
+ throw new IllegalStateException("filter path was not configured");
+ }
+
+ WebApplication application = getApplication();
+ // No redirect; process the request
+ ThreadContext.setApplication(application);
+
+ WebRequest webRequest = application.createWebRequest(httpServletRequest, filterPath);
+ WebResponse webResponse = application.createWebResponse(webRequest,
+ httpServletResponse);
+
+ RequestCycle requestCycle = application.createRequestCycle(webRequest, webResponse);
+ ThreadContext.setRequestCycle(requestCycle);
+
+ if (acceptWebSocket(httpServletRequest, httpServletResponse, application) || httpServletResponse.isCommitted())
+ {
+ res = true;
+ }
+ else if (!requestCycle.processRequestAndDetach() && !httpServletResponse.isCommitted())
+ {
+ if (chain != null)
+ {
+ chain.doFilter(request, response);
+ }
+ res = false;
+ }
+ else
+ {
+ webResponse.flush();
+ }
+
+ }
+ finally
+ {
+ ThreadContext.restore(previousThreadContext);
+
+ if (newClassLoader != previousClassLoader)
+ {
+ Thread.currentThread().setContextClassLoader(previousClassLoader);
+ }
+
+ if (response.isCommitted())
+ {
+ response.flushBuffer();
+ }
+ }
+ return res;
+ }
+
+ protected boolean acceptWebSocket(HttpServletRequest req, HttpServletResponse resp, Application application)
+ throws ServletException, IOException
+ {
+ // 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 false;
+ }
+
+ if (!headerContainsToken(req, "connection", "upgrade")) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return false;
+ }
+
+ if (!headerContainsToken(req, "sec-websocket-version", "13")) {
+ resp.setStatus(426);
+ resp.setHeader("Sec-WebSocket-Version", "13");
+ return false;
+ }
+
+ key = req.getHeader("Sec-WebSocket-Key");
+ if (key == null) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
+ return false;
+ }
+
+ String origin = req.getHeader("Origin");
+ if (!verifyOrigin(origin)) {
+ resp.sendError(HttpServletResponse.SC_FORBIDDEN);
+ return false;
+ }
+
+ List<String> subProtocols = getTokensFromHeader(req,
+ "Sec-WebSocket-Protocol-Client");
+ if (!subProtocols.isEmpty()) {
+ subProtocol = selectSubProtocol(subProtocols);
+
+ }
+
+ resp.setHeader("upgrade", "websocket");
+ resp.setHeader("connection", "upgrade");
+
+ if (subProtocol != null) {
+ resp.setHeader("Sec-WebSocket-Protocol", subProtocol);
+ }
+
+ return true;
+ }
+
+ /*
+ * 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.
+ */
+ protected List<String> getTokensFromHeader(HttpServletRequest req,
+ String headerName) {
+ List<String> result = new ArrayList<String>();
+
+ 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;
+ }
+
+ /**
+ * Intended to be overridden by sub-classes that wish to verify the origin
+ * of a WebSocket request before processing it.
+ *
+ * @param origin The value of the origin header from the request which
+ * may be <code>null</code>
+ *
+ * @return <code>true</code> to accept the request. <code>false</code> to
+ * reject it. This default implementation always returns
+ * <code>true</code>.
+ */
+ protected boolean verifyOrigin(String origin) {
+ 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.
+ *
+ * @param subProtocols The list of sub-protocols supported by the client
+ * in client preference order. The server is under no
+ * 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. This default implementation always returns
+ * <code>null</code>.
+ */
+ protected String selectSubProtocol(List<String> subProtocols) {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/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
new file mode 100644
index 0000000..94dceca
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/AbstractWebSocketProcessor.java
@@ -0,0 +1,253 @@
+/*
+ * 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.api;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Page;
+import org.apache.wicket.Session;
+import org.apache.wicket.ThreadContext;
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.page.IPageManager;
+import org.apache.wicket.protocol.ws.api.event.WebSocketBinaryPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketClosedPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketConnectedPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketTextPayload;
+import org.apache.wicket.protocol.ws.api.message.BinaryMessage;
+import org.apache.wicket.protocol.ws.api.message.ClosedMessage;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketMessage;
+import org.apache.wicket.protocol.ws.api.message.TextMessage;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.cycle.RequestCycleContext;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.session.ISessionStore;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Checks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The base implementation of IWebSocketProcessor. Provides the common logic
+ * for registering a web socket connection and broadcasting its events.
+ *
+ * @since 6.0
+ */
+public abstract class AbstractWebSocketProcessor implements IWebSocketProcessor
+{
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractWebSocketProcessor.class);
+
+ private final WebRequest webRequest;
+ private final int pageId;
+ private final Application application;
+ private final String sessionId;
+ private final IWebSocketConnectionRegistry connectionRegistry;
+
+ /**
+ * Constructor.
+ *
+ * @param request
+ * the http request that was used to create the TomcatWebSocketProcessor
+ * @param application
+ * the current Wicket Application
+ */
+ public AbstractWebSocketProcessor(final HttpServletRequest request, final Application application)
+ {
+ this.sessionId = request.getSession(true).getId();
+ Session.get().bind();
+
+ String pageId = request.getParameter("pageId");
+ Checks.notEmpty(pageId, "Request parameter 'pageId' is required!");
+ this.pageId = Integer.parseInt(pageId, 10);
+
+ this.webRequest = new WebSocketRequest(new ServletRequestCopy(request));
+
+ this.application = Args.notNull(application, "application");
+ this.connectionRegistry = new SimpleWebSocketConnectionRegistry();
+ }
+
+ @Override
+ public void onMessage(final String message)
+ {
+ broadcastMessage(new TextMessage(message));
+ }
+
+ @Override
+ public void onMessage(byte[] data, int offset, int length)
+ {
+ BinaryMessage binaryMessage = new BinaryMessage(data, offset, length);
+ broadcastMessage(binaryMessage);
+ }
+
+ /**
+ * A helper that registers the opened connection in the application-level
+ * registry.
+ *
+ * @param connection
+ * the web socket connection to use to communicate with the client
+ * @see #onOpen(Object)
+ */
+ protected final void onConnect(final WebSocketConnection connection)
+ {
+ connectionRegistry.setConnection(getApplication(), getSessionId(), pageId, connection);
+ broadcastMessage(new ConnectedMessage(getApplication(), getSessionId(), pageId));
+ }
+
+ @Override
+ public void onClose(int closeCode, String message)
+ {
+ broadcastMessage(new ClosedMessage(getApplication(), getSessionId(), pageId));
+ connectionRegistry.removeConnection(application, sessionId, pageId);
+ }
+
+ /**
+ * Exports the Wicket thread locals and broadcasts the received message from the client to all
+ * interested components and behaviors in the page with id {@code #pageId}
+ * <p>
+ * Note: ConnectedMessage and ClosedMessage messages are notification-only. I.e. whatever the
+ * components/behaviors write in the WebSocketRequestHandler will be ignored because the protocol
+ * doesn't expect response from the user.
+ * </p>
+ *
+ * @param message
+ * the message to broadcast
+ */
+ protected final void broadcastMessage(final IWebSocketMessage message)
+ {
+ WebSocketConnection connection = connectionRegistry.getConnection(application, sessionId, pageId);
+
+ if (connection != null && connection.isOpen())
+ {
+ Application oldApplication = ThreadContext.getApplication();
+ Session oldSession = ThreadContext.getSession();
+ RequestCycle oldRequestCycle = ThreadContext.getRequestCycle();
+
+ WebSocketResponse webResponse = new WebSocketResponse(connection);
+ try
+ {
+ RequestCycle requestCycle;
+ if (oldRequestCycle == null)
+ {
+ RequestCycleContext context = new RequestCycleContext(webRequest, webResponse,
+ application.getRootRequestMapper(), application.getExceptionMapperProvider().get());
+
+ requestCycle = application.getRequestCycleProvider().get(context);
+ ThreadContext.setRequestCycle(requestCycle);
+ }
+ else
+ {
+ requestCycle = oldRequestCycle;
+ }
+
+ ThreadContext.setApplication(application);
+
+ Session session;
+ if (oldSession == null)
+ {
+ ISessionStore sessionStore = application.getSessionStore();
+ session = sessionStore.lookup(webRequest);
+ ThreadContext.setSession(session);
+ }
+ else
+ {
+ session = oldSession;
+ }
+
+ IPageManager pageManager = session.getPageManager();
+ try
+ {
+ Page page = (Page) pageManager.getPage(pageId);
+ WebSocketRequestHandler target = new WebSocketRequestHandler(page, connection);
+
+ WebSocketPayload payload = createEventPayload(message, target);
+
+ page.send(application, Broadcast.BREADTH, payload);
+
+ if (!(message instanceof ConnectedMessage || message instanceof ClosedMessage))
+ {
+ target.respond(requestCycle);
+ }
+ }
+ finally
+ {
+ pageManager.commitRequest();
+ }
+ }
+ catch (Exception x)
+ {
+ LOG.error("An error occurred during processing of a WebSocket message", x);
+ }
+ finally
+ {
+ try
+ {
+ webResponse.close();
+ }
+ finally
+ {
+ ThreadContext.setApplication(oldApplication);
+ ThreadContext.setRequestCycle(oldRequestCycle);
+ ThreadContext.setSession(oldSession);
+ }
+ }
+ }
+ else
+ {
+ LOG.debug("Either there is no connection({}) or it is closed.", connection);
+ }
+ }
+
+ protected final Application getApplication()
+ {
+ return application;
+ }
+
+ protected final String getSessionId()
+ {
+ return sessionId;
+ }
+
+ private WebSocketPayload createEventPayload(IWebSocketMessage message, WebSocketRequestHandler handler)
+ {
+ final WebSocketPayload payload;
+ if (message instanceof TextMessage)
+ {
+ payload = new WebSocketTextPayload((TextMessage) message, handler);
+ }
+ else if (message instanceof BinaryMessage)
+ {
+ payload = new WebSocketBinaryPayload((BinaryMessage) message, handler);
+ }
+ else if (message instanceof ConnectedMessage)
+ {
+ payload = new WebSocketConnectedPayload((ConnectedMessage) message, handler);
+ }
+ else if (message instanceof ClosedMessage)
+ {
+ payload = new WebSocketClosedPayload((ClosedMessage) message, handler);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Unsupported message type: " + message.getClass().getName());
+ }
+ return payload;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/HttpSessionCopy.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/HttpSessionCopy.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/HttpSessionCopy.java
new file mode 100644
index 0000000..2d5fed1
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/HttpSessionCopy.java
@@ -0,0 +1,145 @@
+/*
+ * 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.api;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+
+/**
+ * A copy of the HttpSession used at the WebSocket connection creation time
+ *
+ * @since 6.0
+ */
+public class HttpSessionCopy implements HttpSession
+{
+ private final long creationTime;
+ private final ConcurrentHashMap<String, Object> attributes;
+ private final String sessionId;
+ private final ServletContext servletContext;
+ private int maxInactiveInterval;
+
+ public HttpSessionCopy(final HttpSession originalSession) {
+ this.sessionId = originalSession.getId();
+ this.servletContext = originalSession.getServletContext();
+ this.creationTime = originalSession.getCreationTime();
+
+ this.attributes = new ConcurrentHashMap<String, Object>();
+ Enumeration<String> attributeNames = originalSession.getAttributeNames();
+ while (attributeNames.hasMoreElements())
+ {
+ String attributeName = attributeNames.nextElement();
+ Object attributeValue = originalSession.getAttribute(attributeName);
+ attributes.put(attributeName, attributeValue);
+ }
+
+ }
+
+ public void destroy(){
+ attributes.clear();
+ }
+
+ @Override
+ public long getCreationTime() {
+ return creationTime;
+ }
+
+ @Override
+ public String getId() {
+ return sessionId;
+ }
+
+ // TODO: Not supported for now. Must update on every WebSocket Message
+ @Override
+ public long getLastAccessedTime() {
+ return 0;
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public void setMaxInactiveInterval(int interval) {
+ this.maxInactiveInterval = interval;
+ }
+
+ @Override
+ public int getMaxInactiveInterval() {
+ return maxInactiveInterval;
+ }
+
+ @Override
+ public HttpSessionContext getSessionContext() {
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ @Override
+ public Object getValue(String name) {
+ return attributes.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames() {
+ return attributes.keys();
+ }
+
+ @Override
+ public String[] getValueNames() {
+ return (String[]) Collections.list(attributes.keys()).toArray();
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ attributes.put(name, value);
+ }
+
+ @Override
+ public void putValue(String name, Object value) {
+ attributes.put(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ attributes.remove(name);
+ }
+
+ @Override
+ public void removeValue(String name) {
+ attributes.remove(name);
+ }
+
+ // TODO: Not supported for now.
+ @Override
+ public void invalidate() {
+ }
+
+ @Override
+ public boolean isNew() {
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketConnectionRegistry.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketConnectionRegistry.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketConnectionRegistry.java
new file mode 100644
index 0000000..b5df5d2
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketConnectionRegistry.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api;
+
+import org.apache.wicket.Application;
+
+/**
+ * Tracks all currently connected WebSocket clients
+ *
+ * @since 6.0
+ */
+public interface IWebSocketConnectionRegistry
+{
+ /**
+ * @param application
+ * the web application to look in
+ * @param sessionId
+ * the web socket client session id
+ * @param pageId
+ * the web socket client page id
+ * @return the web socket connection used by a client from the specified coordinates
+ */
+ WebSocketConnection getConnection(Application application, String sessionId, Integer pageId);
+
+ /**
+ * Adds a new connection into the registry at the specified coordinates (application+session+page)
+ *
+ * @param application
+ * the web application to look in
+ * @param sessionId
+ * the web socket client session id
+ * @param pageId
+ * the web socket client page id
+ * @param connection
+ * the web socket connection to add
+ */
+ void setConnection(Application application, String sessionId, Integer pageId, WebSocketConnection connection);
+
+ /**
+ * Removes a web socket connection from the registry at the specified coordinates (application+session+page)
+ *
+ * @param application
+ * the web application to look in
+ * @param sessionId
+ * the web socket client session id
+ * @param pageId
+ * the web socket client page id
+ */
+ void removeConnection(Application application, String sessionId, Integer pageId);
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketProcessor.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketProcessor.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketProcessor.java
new file mode 100644
index 0000000..9e06a5e
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/IWebSocketProcessor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.api;
+
+/**
+ * Processes web socket messages.
+ *
+ * @since 6.0
+ */
+public interface IWebSocketProcessor
+{
+ /**
+ * Called when a text message arrives from the client
+ *
+ * @param message
+ * the text message from the client
+ */
+ void onMessage(final String message);
+
+ /**
+ * Called when a binary message arrives from the client
+ *
+ * @param data
+ * the binary message from the client
+ * @param offset
+ * the offset to read from
+ * @param length
+ * how much data to read
+ */
+ void onMessage(byte[] data, int offset, int length);
+
+ /**
+ * A client successfully has made a web socket connection.
+ *
+ * @param containerConnection
+ * the web socket connection to use to communicate with the client
+ */
+ void onOpen(Object containerConnection);
+
+ /**
+ * A notification after the close of the web socket connection.
+ * The connection could be closed by either the client or the server
+ *
+ * @param closeCode
+ * @param message
+ */
+ void onClose(int closeCode, String message);
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/ServletRequestCopy.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/ServletRequestCopy.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/ServletRequestCopy.java
new file mode 100644
index 0000000..02bad1a
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/ServletRequestCopy.java
@@ -0,0 +1,414 @@
+/*
+ * 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.api;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * A copy of the http servlet request used to create the WebSocket.
+ * Copy its details because they are discarded at the end of the request (even if we
+ * use AsyncContext from Servlet 3 spec). This copy is used later for following requests
+ * through the WebSocket connection to construct WebSocketRequest.
+ *
+ * @since 6.0
+ */
+public class ServletRequestCopy implements HttpServletRequest
+{
+ private final String contextPath;
+ private final String servletPath;
+ private final String pathInfo;
+ private final String requestUri;
+ private final HttpSessionCopy httpSession;
+ private final StringBuffer requestURL;
+ private final Map<String, Object> attributes = new HashMap<String, Object>();
+ private final Map<String, String> headers = new HashMap<String, String>();
+ private final Map<String, String[]> parameters = new HashMap<String, String[]>();
+ private final String method;
+ private final String serverName;
+ private final int serverPort;
+
+ public ServletRequestCopy(HttpServletRequest request) {
+ this.servletPath = request.getServletPath();
+ this.contextPath = request.getContextPath();
+ this.pathInfo = request.getPathInfo();
+ this.requestUri = request.getRequestURI();
+ this.requestURL = request.getRequestURL();
+ this.method = request.getMethod();
+ this.serverName = request.getServerName();
+ this.serverPort = request.getServerPort();
+
+ HttpSession session = request.getSession(true);
+ httpSession = new HttpSessionCopy(session);
+
+ Enumeration<String> e = request.getHeaderNames();
+ String s;
+ while (e.hasMoreElements()) {
+ s = e.nextElement();
+ headers.put(s, request.getHeader(s));
+ }
+
+ e = request.getAttributeNames();
+ while (e.hasMoreElements()) {
+ s = e.nextElement();
+ attributes.put(s, request.getAttribute(s));
+ }
+
+ e = request.getParameterNames();
+ while (e.hasMoreElements()) {
+ s = e.nextElement();
+ parameters.put(s, request.getParameterValues(s));
+ }
+ }
+
+ public void destroy() {
+ attributes.clear();
+ headers.clear();
+ parameters.clear();
+ httpSession.destroy();
+ }
+
+ @Override
+ public String getServerName() {
+ return serverName;
+ }
+
+ @Override
+ public int getServerPort() {
+ return serverPort;
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException
+ {
+ return null;
+ }
+
+ @Override
+ public String getRemoteAddr()
+ {
+ return null;
+ }
+
+ @Override
+ public String getRemoteHost()
+ {
+ return null;
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ return httpSession;
+ }
+
+ @Override
+ public String getMethod() {
+ return method;
+ }
+
+ @Override
+ public String getAuthType()
+ {
+ return null;
+ }
+
+ @Override
+ public Cookie[] getCookies()
+ {
+ return new Cookie[0];
+ }
+
+ @Override
+ public long getDateHeader(String name)
+ {
+ return 0;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(final String name) {
+ return new Enumeration<String>() {
+
+ boolean hasNext = true;
+
+ @Override
+ public boolean hasMoreElements() {
+ return hasNext && headers.get(name) != null;
+ }
+
+ @Override
+ public String nextElement() {
+ hasNext = false;
+ return headers.get(name);
+ }
+ };
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames() {
+ return Collections.enumeration(parameters.keySet());
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return parameters.get(name) != null ? parameters.get(name)[0] : null;
+ }
+
+ @Override
+ public String[] getParameterValues(final String name) {
+ return parameters.get(name);
+ }
+
+ @Override
+ public Map getParameterMap()
+ {
+ return null;
+ }
+
+ @Override
+ public String getProtocol()
+ {
+ return null;
+ }
+
+ @Override
+ public String getScheme()
+ {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ return Collections.enumeration(headers.keySet());
+ }
+
+ @Override
+ public int getIntHeader(String name)
+ {
+ return 0;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(attributes.keySet());
+ }
+
+ @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 void setAttribute(String name, Object o) {
+ attributes.put(name, o);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ attributes.remove(name);
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return null;
+ }
+
+ @Override
+ public Enumeration 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 String getContextPath() {
+ return contextPath;
+ }
+
+ @Override
+ public String getQueryString()
+ {
+ return null;
+ }
+
+ @Override
+ public String getRemoteUser()
+ {
+ return null;
+ }
+
+ @Override
+ public boolean isUserInRole(String role)
+ {
+ return false;
+ }
+
+ @Override
+ public Principal getUserPrincipal()
+ {
+ return null;
+ }
+
+ @Override
+ public String getRequestedSessionId()
+ {
+ return null;
+ }
+
+ @Override
+ public String getServletPath() {
+ return servletPath;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return pathInfo;
+ }
+
+ @Override
+ public String getPathTranslated()
+ {
+ return null;
+ }
+
+ @Override
+ public String getRequestURI() {
+ return requestUri;
+ }
+
+ @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 StringBuffer getRequestURL() {
+ return requestURL;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/SimpleWebSocketConnectionRegistry.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/SimpleWebSocketConnectionRegistry.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/SimpleWebSocketConnectionRegistry.java
new file mode 100644
index 0000000..5a43886
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/SimpleWebSocketConnectionRegistry.java
@@ -0,0 +1,115 @@
+/*
+ * 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.api;
+
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.util.lang.Args;
+import org.apache.wicket.util.lang.Generics;
+
+/**
+ * A registry that keeps all currently opened web socket connections in
+ * maps in Application's meta data.
+ *
+ * TODO remove the synchronizations below and use ConcurrentMap#putIfAbsent()
+ *
+ * @since 6.0
+ */
+public class SimpleWebSocketConnectionRegistry implements IWebSocketConnectionRegistry
+{
+ private static final MetaDataKey<ConcurrentMap<String, ConcurrentMap<Integer, WebSocketConnection>>> KEY =
+ new MetaDataKey<ConcurrentMap<String, ConcurrentMap<Integer, WebSocketConnection>>>()
+ {
+ };
+
+ @Override
+ public WebSocketConnection getConnection(Application application, String sessionId, Integer pageId)
+ {
+ Args.notNull(application, "application");
+ Args.notNull(sessionId, "sessionId");
+ Args.notNull(pageId, "pageId");
+
+ WebSocketConnection connection = null;
+ ConcurrentMap<String, ConcurrentMap<Integer, WebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
+ if (connectionsBySession != null)
+ {
+ ConcurrentMap<Integer, WebSocketConnection> connectionsByPage = connectionsBySession.get(sessionId);
+ if (connectionsByPage != null)
+ {
+ connection = connectionsByPage.get(pageId);
+ }
+ }
+ return connection;
+ }
+
+ @Override
+ public void setConnection(Application application, String sessionId, Integer pageId, WebSocketConnection connection)
+ {
+ Args.notNull(application, "application");
+ Args.notNull(sessionId, "sessionId");
+ Args.notNull(pageId, "pageId");
+
+ ConcurrentMap<String, ConcurrentMap<Integer, WebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
+ if (connectionsBySession == null)
+ {
+ synchronized (KEY)
+ {
+ connectionsBySession = application.getMetaData(KEY);
+ if (connectionsBySession == null)
+ {
+ connectionsBySession = Generics.newConcurrentHashMap();
+ application.setMetaData(KEY, connectionsBySession);
+ }
+ }
+ }
+
+ ConcurrentMap<Integer, WebSocketConnection> connectionsByPage = connectionsBySession.get(sessionId);
+ if (connectionsByPage == null && connection != null)
+ {
+ synchronized (connectionsBySession)
+ {
+ connectionsByPage = connectionsBySession.get(sessionId);
+ if (connectionsByPage == null)
+ {
+ connectionsByPage = Generics.newConcurrentHashMap();
+ connectionsBySession.put(sessionId, connectionsByPage);
+ }
+ }
+ }
+
+ if (connection != null)
+ {
+ connectionsByPage.put(pageId, connection);
+ }
+ else if (connectionsByPage != null)
+ {
+ connectionsByPage.remove(pageId);
+ if (connectionsByPage.isEmpty())
+ {
+ connectionsBySession.remove(sessionId);
+ }
+ }
+ }
+
+ @Override
+ public void removeConnection(Application application, String sessionId, Integer pageId)
+ {
+ setConnection(application, sessionId, pageId, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/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
new file mode 100644
index 0000000..2895fd5
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketBehavior.java
@@ -0,0 +1,113 @@
+/*
+ * 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.api;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.protocol.ws.api.event.WebSocketBinaryPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketClosedPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketConnectedPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketPayload;
+import org.apache.wicket.protocol.ws.api.event.WebSocketTextPayload;
+import org.apache.wicket.protocol.ws.api.message.BinaryMessage;
+import org.apache.wicket.protocol.ws.api.message.ClosedMessage;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.TextMessage;
+
+/**
+ * A behavior that contributes {@link WicketWebSocketJQueryResourceReference} and
+ * provides optional callbacks for the WebSocket messages (connect, message, close)
+ *
+ * @since 6.0
+ */
+public abstract class WebSocketBehavior extends Behavior
+{
+ @Override
+ public void onEvent(Component component, IEvent<?> event)
+ {
+ Object payload = event.getPayload();
+ if (payload instanceof WebSocketPayload)
+ {
+ WebSocketPayload wsPayload = (WebSocketPayload) payload;
+ WebSocketRequestHandler webSocketHandler = wsPayload.getHandler();
+
+ if (payload instanceof WebSocketTextPayload)
+ {
+ WebSocketTextPayload textPayload = (WebSocketTextPayload) payload;
+ TextMessage data = textPayload.getMessage();
+ onMessage(webSocketHandler, data);
+ }
+ else if (wsPayload instanceof WebSocketBinaryPayload)
+ {
+ WebSocketBinaryPayload binaryPayload = (WebSocketBinaryPayload) wsPayload;
+ BinaryMessage binaryData = binaryPayload.getMessage();
+ onMessage(webSocketHandler, binaryData);
+ }
+ else if (wsPayload instanceof WebSocketConnectedPayload)
+ {
+ WebSocketConnectedPayload connectedPayload = (WebSocketConnectedPayload) wsPayload;
+ ConnectedMessage message = connectedPayload.getMessage();
+ onConnect(message);
+ }
+ else if (wsPayload instanceof WebSocketClosedPayload)
+ {
+ WebSocketClosedPayload connectedPayload = (WebSocketClosedPayload) wsPayload;
+ ClosedMessage message = connectedPayload.getMessage();
+ onClose(message);
+ }
+ }
+ }
+
+ protected void onConnect(ConnectedMessage message)
+ {
+ }
+
+ protected void onClose(ClosedMessage message)
+ {
+ }
+
+ protected void onMessage(WebSocketRequestHandler handler, TextMessage message)
+ {
+ }
+
+ protected void onMessage(WebSocketRequestHandler handler, BinaryMessage binaryMessage)
+ {
+ }
+
+ @Override
+ public void renderHead(Component component, IHeaderResponse response)
+ {
+ super.renderHead(component, response);
+
+ response.render(JavaScriptHeaderItem.forReference(WicketWebSocketJQueryResourceReference.get()));
+
+ int pageId = component.getPage().getPageId();
+ response.render(JavaScriptHeaderItem.forScript(
+ "jQuery.extend(Wicket.WebSocket, { pageId: " + pageId + "});",
+ "wicket-web-socket"));
+ }
+
+ @Override
+ public boolean getStatelessHint(Component component)
+ {
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketConnection.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketConnection.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketConnection.java
new file mode 100644
index 0000000..1541dc7
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketConnection.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.api;
+
+import java.io.IOException;
+
+/**
+ * Common interface for native WebSocket connections
+ *
+ * @since 6.0
+ */
+public interface WebSocketConnection
+{
+ /**
+ * @return {@code true} when the underlying native web socket
+ * connection is still open.
+ */
+ boolean isOpen();
+
+ /**
+ * Closes the underlying web socket connection
+ *
+ * @param code
+ * the status code
+ * @param reason
+ * the reason to close the connection
+ */
+ void close(int code, String reason);
+
+ /**
+ * Sends a text message to the client.
+ *
+ * @param message
+ * the text message
+ * @return {@code this} object, for chaining methods
+ * @throws IOException when an IO error occurs during the write to the client
+ */
+ WebSocketConnection sendMessage(String message) throws IOException;
+
+ /**
+ * Sends a binary message to the client.
+ *
+ * @param message
+ * the binary message
+ * @param offset
+ * the offset to read from
+ * @param length
+ * how much data to read
+ * @return {@code this} object, for chaining methods
+ * @throws IOException when an IO error occurs during the write to the client
+ */
+ WebSocketConnection sendMessage(byte[] message, int offset, int length) throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequest.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequest.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequest.java
new file mode 100644
index 0000000..3f0fb93
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketRequest.java
@@ -0,0 +1,108 @@
+package org.apache.wicket.protocol.ws.api;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.http.WebRequest;
+import org.apache.wicket.util.lang.Generics;
+import org.apache.wicket.util.time.Time;
+
+/**
+ * A {@link WebRequest} implementation used for the lifecycle of a web socket
+ * connection. It keeps a copy of the HttpServletRequest provided by the web container
+ * during the creation of the web socket connection (the http upgrade).
+ *
+ * @since 6.0
+ */
+public class WebSocketRequest extends WebRequest
+{
+ private final HttpServletRequest request;
+
+ /**
+ * Constructor.
+ *
+ * @param req
+ * the copy of the HttpServletRequest used for the upgrade of the HTTP protocol
+ */
+ public WebSocketRequest(HttpServletRequest req)
+ {
+ this.request = req;
+ }
+
+ @Override
+ public List<Cookie> getCookies()
+ {
+ List<Cookie> cookies = Arrays.asList(request.getCookies());
+ return cookies;
+ }
+
+ @Override
+ public List<String> getHeaders(String name)
+ {
+ Enumeration<String> headers = request.getHeaders(name);
+ List<String> h = Generics.newArrayList();
+ while (headers.hasMoreElements())
+ {
+ h.add(headers.nextElement());
+ }
+
+ return h;
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ return request.getHeader(name);
+ }
+
+ @Override
+ public Time getDateHeader(String name)
+ {
+ long dateHeader = request.getDateHeader(name);
+ return Time.millis(dateHeader);
+ }
+
+ @Override
+ public Url getUrl()
+ {
+ return null;
+ }
+
+ @Override
+ public Url getClientUrl()
+ {
+ return null;
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return request.getLocale();
+ }
+
+ @Override
+ public Charset getCharset()
+ {
+ return RequestUtils.getCharset(request);
+ }
+
+ @Override
+ public Object getContainerRequest()
+ {
+ return request;
+ }
+
+ @Override
+ public boolean isAjax()
+ {
+ return true;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
new file mode 100644
index 0000000..c572436
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WebSocketResponse.java
@@ -0,0 +1,136 @@
+/*
+ * 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.api;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.wicket.request.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link Response} used to cache the written data to the web socket client
+ * when Wicket thread locals are available.
+ *
+ * When the thread locals are not available then you can write directly to the {@link WebSocketConnection}
+ * taken from {@link SimpleWebSocketConnectionRegistry}. In this case the response wont be cached.
+ *
+ * @since 6.0
+ */
+public class WebSocketResponse extends Response
+{
+ private static final Logger LOG = LoggerFactory.getLogger(WebSocketResponse.class);
+
+ private final WebSocketConnection connection;
+
+ private StringBuilder text;
+
+ private ByteArrayOutputStream binary;
+
+ public WebSocketResponse(final WebSocketConnection conn)
+ {
+ this.connection = conn;
+ }
+
+ @Override
+ public void write(CharSequence sequence)
+ {
+ if (text == null)
+ {
+ text = new StringBuilder();
+ }
+ text.append(sequence);
+ }
+
+ @Override
+ public void write(byte[] array)
+ {
+ write(array, 0, array.length);
+ }
+
+ @Override
+ public void write(byte[] array, int offset, int length)
+ {
+ if (binary == null)
+ {
+ binary = new ByteArrayOutputStream();
+ }
+ binary.write(array, offset, length);
+ }
+
+ @Override
+ public void close()
+ {
+ if (connection.isOpen())
+ {
+ try
+ {
+ if (text != null)
+ {
+ connection.sendMessage(text.toString());
+ text = null;
+ }
+ else if (binary != null)
+ {
+ byte[] bytes = binary.toByteArray();
+ connection.sendMessage(bytes, 0, bytes.length);
+ binary.close();
+ binary = null;
+ }
+
+ } catch (IOException iox)
+ {
+ LOG.error("An error occurred while writing response to WebSocket client.", iox);
+ }
+ }
+ super.close();
+ }
+
+ @Override
+ public void reset()
+ {
+ if (text != null)
+ {
+ text = null;
+ }
+ if (binary != null)
+ {
+ try
+ {
+ binary.close();
+ } catch (IOException iox)
+ {
+ LOG.error("An error occurred while resetting the binary content", iox);
+ }
+ binary = null;
+ }
+ super.reset();
+ }
+
+ @Override
+ public String encodeURL(CharSequence url)
+ {
+ return url.toString();
+ }
+
+ @Override
+ public final WebSocketConnection getContainerResponse()
+ {
+ return connection;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WicketWebSocketJQueryResourceReference.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WicketWebSocketJQueryResourceReference.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WicketWebSocketJQueryResourceReference.java
new file mode 100644
index 0000000..734bce0
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/WicketWebSocketJQueryResourceReference.java
@@ -0,0 +1,66 @@
+/*
+ * 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.api;
+
+import java.util.Collections;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.ajax.WicketAjaxJQueryResourceReference;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.ResourceReference;
+
+/**
+ * A resource reference that provides the JavaScript that may be used to create WebSocket
+ * connections in the browser. The benefit over usage of plain <code>window.WebSocket<code>
+ * is that it supports handling of Wicket's <ajax-response> responses.
+ *
+ * @since 6.0
+ */
+public class WicketWebSocketJQueryResourceReference extends JavaScriptResourceReference
+{
+ private static final long serialVersionUID = 1;
+
+ private static final WicketWebSocketJQueryResourceReference instance = new WicketWebSocketJQueryResourceReference();
+
+ /**
+ * @return the singleton instance
+ */
+ public static WicketWebSocketJQueryResourceReference get()
+ {
+ return instance;
+ }
+
+ private WicketWebSocketJQueryResourceReference()
+ {
+ super(WicketWebSocketJQueryResourceReference.class, "res/js/wicket-websocket-jquery.js");
+ }
+
+ @Override
+ public Iterable<? extends HeaderItem> getDependencies()
+ {
+ final ResourceReference wicketAjaxReference;
+ if (Application.exists()) {
+ wicketAjaxReference = Application.get().getJavaScriptLibrarySettings().getWicketAjaxReference();
+ }
+ else {
+ wicketAjaxReference = WicketAjaxJQueryResourceReference.get();
+ }
+ return Collections.singletonList(JavaScriptHeaderItem.forReference(wicketAjaxReference));
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketBinaryPayload.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketBinaryPayload.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketBinaryPayload.java
new file mode 100644
index 0000000..963e2f0
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketBinaryPayload.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.event;
+
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.BinaryMessage;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * Payload for event broadcasting when a binary message comes in the WebSocket connection
+ *
+ * @since 6.0
+ */
+public class WebSocketBinaryPayload extends WebSocketPayload<BinaryMessage>
+{
+ private final BinaryMessage binaryMessage;
+
+ public WebSocketBinaryPayload(BinaryMessage binaryMessage, WebSocketRequestHandler handler)
+ {
+ super(handler);
+
+ this.binaryMessage = Args.notNull(binaryMessage, "binaryMessage");
+ }
+
+ public final BinaryMessage getMessage()
+ {
+ return binaryMessage;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketClosedPayload.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketClosedPayload.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketClosedPayload.java
new file mode 100644
index 0000000..121d64c
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketClosedPayload.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.event;
+
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.ClosedMessage;
+
+/**
+ * Payload for event broadcasting when the client has closed the WebSocket connection
+ *
+ * @since 6.0
+ */
+public class WebSocketClosedPayload extends WebSocketPayload<ClosedMessage>
+{
+ private final ClosedMessage message;
+
+ public WebSocketClosedPayload(ClosedMessage message, WebSocketRequestHandler handler)
+ {
+ super(handler);
+
+ this.message = message;
+ }
+
+ @Override
+ public final ClosedMessage getMessage()
+ {
+ return message;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketConnectedPayload.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketConnectedPayload.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketConnectedPayload.java
new file mode 100644
index 0000000..39534b1
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketConnectedPayload.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.event;
+
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+
+/**
+ * * Payload for event broadcasting when the client has opened a WebSocket connection
+ *
+ * @since 6.0
+ */
+public class WebSocketConnectedPayload extends WebSocketPayload<ConnectedMessage>
+{
+ private final ConnectedMessage message;
+
+ public WebSocketConnectedPayload(ConnectedMessage message, WebSocketRequestHandler handler)
+ {
+ super(handler);
+
+ this.message = message;
+ }
+
+ @Override
+ public final ConnectedMessage getMessage()
+ {
+ return message;
+ }
+}
http://git-wip-us.apache.org/repos/asf/wicket/blob/b3687306/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketPayload.java
----------------------------------------------------------------------
diff --git a/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketPayload.java b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketPayload.java
new file mode 100644
index 0000000..aaf46d3
--- /dev/null
+++ b/wicket-experimental/wicket-native-websocket/wicket-native-websocket-core/src/main/java/org/apache/wicket/protocol/ws/api/event/WebSocketPayload.java
@@ -0,0 +1,42 @@
+/*
+ * 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.api.event;
+
+import org.apache.wicket.ajax.WebSocketRequestHandler;
+import org.apache.wicket.util.lang.Args;
+
+/**
+ * A base class for all event broadcasting payloads for WebSocket messages.
+ *
+ * @since 6.0
+ */
+public abstract class WebSocketPayload<T>
+{
+ private final WebSocketRequestHandler handler;
+
+ public WebSocketPayload(final WebSocketRequestHandler handler)
+ {
+ this.handler = Args.notNull(handler, "handler");
+ }
+
+ public abstract T getMessage();
+
+ public final WebSocketRequestHandler getHandler()
+ {
+ return handler;
+ }
+}