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 &lt;code&gt;window.WebSocket&lt;code&gt;
+ * is that it supports handling of Wicket's &lt;ajax-response&gt; 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;
+	}
+}