JSR-356 working out server endpoint creation

This commit is contained in:
Joakim Erdfelt 2013-04-23 11:06:01 -07:00
parent 346034b44f
commit a4644dc780
44 changed files with 1393 additions and 120 deletions

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -32,6 +33,9 @@ import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -193,6 +197,16 @@ public class FilterHolder extends Holder<Filter>
{
return getName();
}
/* ------------------------------------------------------------ */
@Override
public void dump(Appendable out, String indent) throws IOException
{
super.dump(out, indent);
if(_filter instanceof Dumpable) {
((Dumpable) _filter).dump(out, indent);
}
}
/* ------------------------------------------------------------ */
public FilterRegistration.Dynamic getRegistration()

View File

@ -24,7 +24,7 @@
<dependency>
<groupId>org.eclipse.jetty.drafts</groupId>
<artifactId>javax.websocket-client-api</artifactId>
<version>1.0.0.PFD-SNAPSHOT</version>
<version>1.0.0.DRAFT-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -56,13 +56,7 @@ public class ClientContainer implements ContainerService
private Session connect(Object websocket, ClientEndpointConfig config, URI path) throws IOException
{
ClientEndpointConfig cec = config;
if (cec == null)
{
cec = ClientEndpointConfig.Builder.create().build();
}
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(websocket,cec);
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(websocket,config);
ClientUpgradeRequest req = new ClientUpgradeRequest();
if (config != null)
{

View File

@ -30,6 +30,8 @@ import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@ -37,6 +39,7 @@ import org.junit.Test;
public class EndpointEchoTest
{
private static final Logger LOG = Log.getLogger(EndpointEchoTest.class);
private static Server server;
private static EchoHandler handler;
private static URI serverUri;
@ -87,7 +90,9 @@ public class EndpointEchoTest
EndpointEchoClient echoer = new EndpointEchoClient();
Assert.assertThat(echoer,instanceOf(javax.websocket.Endpoint.class));
Session session = container.connectToServer(echoer,serverUri);
LOG.debug("Client Connected: {}",session);
session.getBasicRemote().sendText("Echo");
LOG.debug("Client Message Sent");
echoer.textCapture.messageQueue.awaitMessages(1,1000,TimeUnit.MILLISECONDS);
}
}

View File

@ -26,10 +26,15 @@
<artifactId>javax-websocket-client-impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.drafts</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0.0.PFD-SNAPSHOT</version>
<version>1.0.0.DRAFT-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>

View File

@ -0,0 +1,60 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server;
import java.util.List;
import javax.websocket.Extension;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
public class JettyServerEndpointConfigurator extends Configurator
{
@Override
public boolean checkOrigin(String originHeaderValue)
{
return super.checkOrigin(originHeaderValue);
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException
{
return super.getEndpointInstance(endpointClass);
}
@Override
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
{
return super.getNegotiatedExtensions(installed,requested);
}
@Override
public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
{
return super.getNegotiatedSubprotocol(supported,requested);
}
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{
super.modifyHandshake(sec,request,response);
}
}

View File

@ -0,0 +1,93 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server;
import java.io.IOException;
import java.util.List;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class JsrCreator implements WebSocketCreator
{
private static final Logger LOG = Log.getLogger(JsrCreator.class);
private final ServerEndpointConfig config;
public JsrCreator(ServerEndpointConfig config)
{
this.config = config;
}
@Override
public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp)
{
JsrHandshakeRequest hsreq = new JsrHandshakeRequest(req);
JsrHandshakeResponse hsresp = new JsrHandshakeResponse(resp);
ServerEndpointConfig.Configurator configurator = config.getConfigurator();
// modify handshake
configurator.modifyHandshake(config,hsreq,hsresp);
// check origin
if (!configurator.checkOrigin(req.getOrigin()))
{
try
{
resp.sendForbidden("Origin mismatch");
}
catch (IOException e)
{
LOG.debug("Unable to send error response",e);
}
return null;
}
// deal with sub protocols
List<String> supported = config.getSubprotocols();
List<String> requested = req.getSubProtocols();
String subprotocol = configurator.getNegotiatedSubprotocol(supported,requested);
if (subprotocol != null)
{
resp.setAcceptedSubProtocol(subprotocol);
}
// create endpoint class
try
{
return config.getEndpointClass().newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
LOG.debug("Unable to create websocket: " + config.getEndpointClass().getName(),e);
return null;
}
}
@Override
public String toString()
{
return String.format("%s[config=%s]",this.getClass().getName(),config);
}
}

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server;
import java.net.URI;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import javax.websocket.server.HandshakeRequest;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
public class JsrHandshakeRequest implements HandshakeRequest
{
private final UpgradeRequest request;
public JsrHandshakeRequest(UpgradeRequest req)
{
this.request = req;
}
@Override
public Map<String, List<String>> getHeaders()
{
return request.getHeaders();
}
@Override
public Object getHttpSession()
{
return request.getSession();
}
@Override
public Map<String, List<String>> getParameterMap()
{
return request.getParameterMap();
}
@Override
public String getQueryString()
{
return request.getQueryString();
}
@Override
public URI getRequestURI()
{
return request.getRequestURI();
}
@Override
public Principal getUserPrincipal()
{
// TODO: need to return User Principal
return null;
}
@Override
public boolean isUserInRole(String role)
{
// TODO: need to return isUserInRole result
return false;
}
}

View File

@ -18,8 +18,25 @@
package org.eclipse.jetty.websocket.jsr356.server;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import java.util.List;
import java.util.Map;
public class WebSocketHandler extends HandlerWrapper
import javax.websocket.HandshakeResponse;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
public class JsrHandshakeResponse implements HandshakeResponse
{
private final UpgradeResponse response;
public JsrHandshakeResponse(UpgradeResponse resp)
{
this.response = resp;
}
@Override
public Map<String, List<String>> getHeaders()
{
return response.getHeaders();
}
}

View File

@ -106,4 +106,9 @@ public class JsrServerMetadata extends JsrMetadata<ServerEndpoint>
// Copy constructor
return new JettyServerEndpointConfig(config);
}
public String getPath()
{
return config.getPath();
}
}

View File

@ -18,33 +18,78 @@
package org.eclipse.jetty.websocket.jsr356.server;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.DispatcherType;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
{
private static final Logger LOG = Log.getLogger(ServerContainer.class);
public static ServerContainer getInstance()
{
return (ServerContainer)ContainerProvider.getWebSocketContainer();
}
private ConcurrentHashMap<Class<?>, JsrServerMetadata> endpointServerMetadataCache = new ConcurrentHashMap<>();
private MappedWebSocketCreator mappedCreator;
public ServerContainer()
{
super();
WebAppContext webapp = WebAppContext.getCurrentWebAppContext();
if (webapp != null)
{
WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter();
FilterHolder fholder = new FilterHolder(filter);
fholder.setName("Jetty_WebSocketUpgradeFilter");
fholder.setDisplayName("WebSocket Upgrade Filter");
String pathSpec = "/*";
webapp.addFilter(fholder,pathSpec,EnumSet.of(DispatcherType.REQUEST));
LOG.debug("Adding {} mapped to {} to {}",filter,pathSpec,webapp);
mappedCreator = filter;
}
else
{
LOG.debug("No active WebAppContext detected");
}
}
@Override
public void addEndpoint(Class<?> endpointClass) throws DeploymentException
{
// TODO Auto-generated method stub
JsrServerMetadata metadata = getServerEndpointMetadata(endpointClass);
addEndpoint(metadata);
}
public void addEndpoint(JsrServerMetadata metadata)
public void addEndpoint(JsrServerMetadata metadata) throws DeploymentException
{
// TODO Auto-generated method stub
addEndpoint(metadata.getEndpointConfigCopy());
}
@Override
public void addEndpoint(ServerEndpointConfig serverConfig) throws DeploymentException
public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
{
// TODO Auto-generated method stub
JsrCreator creator = new JsrCreator(config);
mappedCreator.addMapping(new WebSocketPathSpec(config.getPath()),creator);
}
public void addMappedCreator(MappedWebSocketCreator mappedCreator)
{
this.mappedCreator = mappedCreator;
}
public JsrServerMetadata getServerEndpointMetadata(Class<?> endpointClass) throws DeploymentException

View File

@ -29,10 +29,14 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
import org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec;
/**
* PathSpec for WebSocket &#064;{@link ServerEndpoint} declarations.
* PathSpec for WebSocket &#064;{@link ServerEndpoint} declarations with support for URI templates and &#064;{@link PathParam} annotations
*/
public class WebSocketPathSpec extends RegexPathSpec
{

View File

@ -0,0 +1 @@
org.eclipse.jetty.websocket.jsr356.server.JettyServerEndpointConfigurator

View File

@ -35,11 +35,14 @@ import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.jsr356.server.samples.BasicEchoSocket;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoSocket;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
/**
* Example of an annotated echo server discovered via annotation scanning.
*/
public class BasicAnnotatedTest
{
@Rule

View File

@ -0,0 +1,90 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server;
import java.net.URI;
import java.util.Queue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerContainer;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
/**
* Example of an {@link Endpoint} extended echo server added programmatically via the
* {@link ServerContainer#addEndpoint(javax.websocket.server.ServerEndpointConfig)}
*/
public class BasicEndpointTest
{
@Rule
public TestingDir testdir = new TestingDir();
@Test
public void testEcho() throws Exception
{
WSServer wsb = new WSServer(testdir,"app");
wsb.copyWebInf("basic-echo-endpoint-config-web.xml");
// the endpoint (extends javax.websocket.Endpoint)
wsb.copyClass(BasicEchoEndpoint.class);
// the configuration (adds the endpoint)
wsb.copyClass(BasicEchoEndpointConfigContextListener.class);
try
{
wsb.start();
URI uri = wsb.getServerBaseURI();
WebAppContext webapp = wsb.createWebAppContext();
// default webapp configuration used (no annotation scanning)
wsb.deployWebapp(webapp);
wsb.dump();
WebSocketClient client = new WebSocketClient();
try
{
client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> future = client.connect(clientEcho,uri.resolve("/echo"));
// wait for connect
future.get(1,TimeUnit.SECONDS);
clientEcho.sendMessage("Hello World");
Queue<String> msgs = clientEcho.awaitMessages(1);
Assert.assertEquals("Expected message","Hello World",msgs.poll());
}
finally
{
client.stop();
}
}
finally
{
wsb.stop();
}
}
}

View File

@ -0,0 +1,51 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Iterator;
import java.util.ServiceLoader;
import javax.websocket.server.ServerEndpointConfig;
import org.junit.Test;
/**
* Test the JettyServerEndpointConfigurator impl.
*/
public class JettyServerEndpointConfiguratorTest
{
@Test
public void testServiceLoader()
{
System.out.printf("Service Name: %s%n",ServerEndpointConfig.Configurator.class.getName());
ServiceLoader<ServerEndpointConfig.Configurator> loader = ServiceLoader.load(javax.websocket.server.ServerEndpointConfig.Configurator.class);
assertThat("loader",loader,notNullValue());
Iterator<ServerEndpointConfig.Configurator> iter = loader.iterator();
assertThat("loader.iterator",iter,notNullValue());
assertThat("loader.iterator.hasNext",iter.hasNext(),is(true));
ServerEndpointConfig.Configurator configr = iter.next();
assertThat("Configurator",configr,notNullValue());
assertThat("COnfigurator type",configr,instanceOf(JettyServerEndpointConfigurator.class));
}
}

View File

@ -34,6 +34,8 @@ import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Assert;
@ -44,6 +46,7 @@ import org.junit.Assert;
*/
public class WSServer
{
private static final Logger LOG = Log.getLogger(WSServer.class);
@SuppressWarnings("unused")
private final TestingDir testdir;
private final File contextDir;
@ -62,18 +65,34 @@ public class WSServer
FS.ensureEmpty(contextDir);
}
public void copyEndpoint(Class<?> endpointClass) throws Exception
public void copyClass(Class<?> clazz) throws Exception
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String endpointPath = endpointClass.getName().replace('.','/') + ".class";
String endpointPath = clazz.getName().replace('.','/') + ".class";
URL classUrl = cl.getResource(endpointPath);
Assert.assertThat("Class URL for: " + endpointClass,classUrl,notNullValue());
Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue());
File destFile = new File(classesDir,OS.separators(endpointPath));
FS.ensureDirExists(destFile.getParentFile());
File srcFile = new File(classUrl.toURI());
IO.copy(srcFile,destFile);
}
public void copyEndpoint(Class<?> endpointClass) throws Exception
{
copyClass(endpointClass);
}
public void copyWebInf(String testResourceName) throws IOException
{
webinf = new File(contextDir,"WEB-INF");
FS.ensureDirExists(webinf);
classesDir = new File(webinf,"classes");
FS.ensureDirExists(classesDir);
File webxml = new File(webinf,"web.xml");
File testWebXml = MavenTestingUtils.getTestResourceFile(testResourceName);
IO.copy(testWebXml,webxml);
}
public WebAppContext createWebAppContext()
{
WebAppContext context = new WebAppContext();
@ -84,13 +103,7 @@ public class WSServer
public void createWebInf() throws IOException
{
webinf = new File(contextDir,"WEB-INF");
FS.ensureDirExists(webinf);
classesDir = new File(webinf,"classes");
FS.ensureDirExists(classesDir);
File webxml = new File(webinf,"web.xml");
File emptyWebXml = MavenTestingUtils.getTestResourceFile("empty-web.xml");
IO.copy(emptyWebXml,webxml);
copyWebInf("empty-web.xml");
}
public void deployWebapp(WebAppContext webapp) throws Exception
@ -134,6 +147,7 @@ public class WSServer
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/",host,port));
LOG.debug("Server started on {}",serverUri);
}
public void stop()

View File

@ -20,7 +20,9 @@ package org.eclipse.jetty.websocket.jsr356.server.pathmap;
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.jsr356.server.pathmap.PathMappings.MappedResource;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
import org.junit.Assert;
import org.junit.Test;
@ -72,49 +74,6 @@ public class PathMappingsTest
assertMatch(p,"/entrance/cam","entranceCam");
}
/**
* Test the match order rules imposed by the Servlet API.
* <p>
* <ul>
* <li>Exact match</li>
* <li>Longest prefix match</li>
* <li>Longest suffix match</li>
* <li>default</li>
* </ul>
*/
@Test
public void testServletMatchOrder()
{
PathMappings<String> p = new PathMappings<>();
p.put(new ServletPathSpec("/abs/path"),"path");
p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
p.put(new ServletPathSpec("/animal/bird/*"),"birds");
p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
p.put(new ServletPathSpec("/animal/*"),"animals");
p.put(new ServletPathSpec("*.tar.gz"),"tarball");
p.put(new ServletPathSpec("*.gz"),"gzipped");
p.put(new ServletPathSpec("/"),"default");
for (MappedResource<String> res : p)
{
System.out.printf(" %s%n",res);
}
assertMatch(p,"/abs/path","path");
assertMatch(p,"/abs/path/longer","longpath");
assertMatch(p,"/abs/path/foo","default");
assertMatch(p,"/main.css","default");
assertMatch(p,"/downloads/script.gz","gzipped");
assertMatch(p,"/downloads/distribution.tar.gz","tarball");
assertMatch(p,"/downloads/readme.txt","default");
assertMatch(p,"/downloads/logs.tgz","default");
assertMatch(p,"/animal/horse/mustang","animals");
assertMatch(p,"/animal/bird/eagle/bald","birds");
assertMatch(p,"/animal/fish/shark/hammerhead","fishes");
assertMatch(p,"/animal/insect/ladybug","animals");
}
/**
* Test the match order rules imposed by the WebSocket API (JSR-356)
* <p>

View File

@ -23,6 +23,7 @@ import static org.junit.Assert.*;
import java.util.Map;
import org.eclipse.jetty.websocket.server.pathmap.PathSpecGroup;
import org.junit.Test;
/**
@ -60,7 +61,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/",spec.getPathSpec());
assertEquals("Spec.pattern","^/$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",1,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
assertEquals("Spec.variableCount",0,spec.getVariableCount());
assertEquals("Spec.variable.length",0,spec.getVariables().length);
@ -73,7 +74,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a",spec.getPathSpec());
assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",1,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
assertEquals("Spec.variableCount",0,spec.getVariableCount());
assertEquals("Spec.variable.length",0,spec.getVariables().length);
@ -86,7 +87,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec());
assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",2,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
assertEquals("Spec.variableCount",0,spec.getVariableCount());
assertEquals("Spec.variable.length",0,spec.getVariables().length);
@ -105,7 +106,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec());
assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",3,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
assertDetectedVars(spec,"var");
@ -129,7 +130,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec());
assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",2,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
assertDetectedVars(spec,"foo");
@ -150,7 +151,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec());
assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",3,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup());
assertDetectedVars(spec,"var");
@ -174,7 +175,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec());
assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",5,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
assertDetectedVars(spec,"var1","var2");
@ -197,7 +198,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec());
assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",4,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
assertDetectedVars(spec,"var1","var2","var3");
@ -221,7 +222,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec());
assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",3,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
assertDetectedVars(spec,"var1","var2");
@ -244,7 +245,7 @@ public class WebSocketPathSpecTest
assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec());
assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",1,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
assertDetectedVars(spec,"var1");

View File

@ -0,0 +1,46 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
/**
* Example of websocket endpoint based on extending {@link Endpoint}
*/
public class BasicEchoEndpoint extends Endpoint implements MessageHandler.Whole<String>
{
private Session session;
@Override
public void onMessage(String msg)
{
// reply with echo
session.getAsyncRemote().sendText(msg);
}
@Override
public void onOpen(Session session, EndpointConfig config)
{
this.session = session;
this.session.addMessageHandler(this);
}
}

View File

@ -0,0 +1,56 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
/**
* Example of adding a server WebSocket (extending {@link Endpoint}) programmatically via config
*/
public class BasicEchoEndpointConfigContextListener implements ServletContextListener
{
@Override
public void contextDestroyed(ServletContextEvent sce)
{
/* do nothing */
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
ServerContainer container = (ServerContainer)ContainerProvider.getWebSocketContainer();
// Build up a configuration with a specific path
String path = "/echo";
ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class,path);
try
{
container.addEndpoint(builder.build());
}
catch (DeploymentException e)
{
throw new RuntimeException("Unable to add endpoint via config file",e);
}
}
}

View File

@ -0,0 +1,55 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerContainer;
/**
* Example of adding a server WebSocket (extending {@link Endpoint}) programmatically directly.
* <p>
* NOTE: this shouldn't work as the endpoint has no path associated with it.
*/
public class BasicEchoEndpointContextListener implements ServletContextListener
{
@Override
public void contextDestroyed(ServletContextEvent sce)
{
/* do nothing */
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
ServerContainer container = (ServerContainer)ContainerProvider.getWebSocketContainer();
try
{
// Should fail as there is no path associated with this endpoint
container.addEndpoint(BasicEchoEndpoint.class);
}
catch (DeploymentException e)
{
throw new RuntimeException("Unable to add endpoint directly",e);
}
}
}

View File

@ -16,12 +16,15 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples;
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
* Annotated echo socket
*/
@ServerEndpoint("/echo")
public class BasicEchoSocket
{

View File

@ -0,0 +1,56 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
/**
* Example of adding a server socket (annotated) programmatically via config
*/
public class BasicEchoSocketConfigContextListener implements ServletContextListener
{
@Override
public void contextDestroyed(ServletContextEvent sce)
{
/* do nothing */
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
ServerContainer container = (ServerContainer)ContainerProvider.getWebSocketContainer();
// Build up a configuration with a specific path
// Intentionally using alternate path in config (which differs from @ServerEndpoint declaration)
String path = "/echo-alt";
ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoSocket.class,path);
try
{
container.addEndpoint(builder.build());
}
catch (DeploymentException e)
{
throw new RuntimeException("Unable to add endpoint via config file",e);
}
}
}

View File

@ -0,0 +1,51 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.samples.echo;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
/**
* Example of adding a server socket (annotated) programmatically directly with no config
*/
public class BasicEchoSocketContextListener implements ServletContextListener
{
@Override
public void contextDestroyed(ServletContextEvent sce)
{
/* do nothing */
}
@Override
public void contextInitialized(ServletContextEvent sce)
{
ServerContainer container = (ServerContainer)ContainerProvider.getWebSocketContainer();
try
{
container.addEndpoint(BasicEchoSocket.class);
}
catch (DeploymentException e)
{
throw new RuntimeException("Unable to add endpoint directly",e);
}
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
metadata-complete="false"
version="3.0">
<listener>
<listener-class>org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpointConfigContextListener</listener-class>
</listener>
</web-app>

View File

@ -0,0 +1,7 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG

View File

@ -137,7 +137,7 @@ public class WebSocketClient extends ContainerLifeCycle
}
// Validate websocket URI
LOG.debug("connect websocket:{} to:{}",websocket,toUri);
LOG.debug("connect websocket {} to {}",websocket,toUri);
// Grab Connection Manager
ConnectionManager manager = getConnectionManager();
@ -163,6 +163,8 @@ public class WebSocketClient extends ContainerLifeCycle
// Create the appropriate (physical vs virtual) connection task
ConnectPromise promise = manager.connect(this,driver,request);
LOG.debug("Connect Promise: {}",promise);
// Execute the connection on the executor thread
executor.execute(promise);

View File

@ -489,7 +489,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
{
super.onOpen();
this.ioState.setState(ConnectionState.OPEN);
LOG.debug("fillInterested");
LOG.debug("{} fillInterested",policy.getBehavior());
fillInterested();
}

View File

@ -0,0 +1,33 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
/**
* Common interface for MappedWebSocketCreator
*/
public interface MappedWebSocketCreator
{
public void addMapping(PathSpec spec, WebSocketCreator creator);
public PathMappings<WebSocketCreator> getMappings();
}

View File

@ -138,12 +138,16 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
@Override
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException
{
return acceptWebSocket(getCreator(),request,response);
}
@Override
public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException
{
ServletWebSocketRequest sockreq = new ServletWebSocketRequest(request);
ServletWebSocketResponse sockresp = new ServletWebSocketResponse(response);
WebSocketCreator creator = getCreator();
UpgradeContext context = getActiveUpgradeContext();
if (context == null)
{
@ -272,22 +276,41 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
@Override
public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
{
if (!"GET".equalsIgnoreCase(request.getMethod()))
{
// not a "GET" request (not a websocket upgrade)
return false;
}
String connection = request.getHeader("connection");
if (connection == null)
{
// no "Connection: upgrade" header present.
return false;
}
if (!"upgrade".equalsIgnoreCase(connection))
{
LOG.debug("Not a 'Connection: Upgrade' (was [Connection: " + connection + "])");
return false;
}
String upgrade = request.getHeader("Upgrade");
if (upgrade == null)
{
// Quietly fail
// no "Upgrade: websocket" header present.
return false;
}
if (!"websocket".equalsIgnoreCase(upgrade))
{
LOG.warn("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
LOG.debug("Not a 'Upgrade: WebSocket' (was [Upgrade: " + upgrade + "])");
return false;
}
if (!"HTTP/1.1".equals(request.getProtocol()))
{
LOG.warn("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
return false;
}

View File

@ -0,0 +1,188 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
/**
* Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
*/
@ManagedObject("WebSocket Upgrade Filter")
public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, Dumpable
{
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
private WebSocketServletFactory factory;
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
pathmap.put(spec,creator);
}
@Override
public void destroy()
{
factory.cleanup();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
if (factory == null)
{
// no factory, cannot operate
LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
chain.doFilter(request,response);
return;
}
if ((request instanceof HttpServletRequest) && (request instanceof HttpServletResponse))
{
HttpServletRequest httpreq = (HttpServletRequest)request;
HttpServletResponse httpresp = (HttpServletResponse)response;
String target = httpreq.getPathInfo();
if (factory.isUpgradeRequest(httpreq,httpresp))
{
MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
if (resource == null)
{
// no match.
httpresp.sendError(HttpServletResponse.SC_NOT_FOUND,"No websocket endpoint matching path: " + target);
return;
}
WebSocketCreator creator = resource.getResource();
// Store PathSpec resource mapping as request attribute
httpreq.setAttribute(PathSpec.class.getName(),resource);
// We have an upgrade request
if (factory.acceptWebSocket(creator,httpreq,httpresp))
{
// We have a socket instance created
return;
}
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (response.isCommitted())
{
// not much we can do at this point.
return;
}
}
}
// not an Upgrade request
chain.doFilter(request,response);
return;
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
pathmap.dump(out,indent + " ");
}
@ManagedAttribute(value = "mappings", readonly = true)
@Override
public PathMappings<WebSocketCreator> getMappings()
{
return pathmap;
}
@Override
public void init(FilterConfig config) throws ServletException
{
try
{
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
String max = config.getInitParameter("maxIdleTime");
if (max != null)
{
policy.setIdleTimeout(Long.parseLong(max));
}
max = config.getInitParameter("maxTextMessageSize");
if (max != null)
{
policy.setMaxTextMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("maxBinaryMessageSize");
if (max != null)
{
policy.setMaxBinaryMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("inputBufferSize");
if (max != null)
{
policy.setInputBufferSize(Integer.parseInt(max));
}
factory = WebSocketServletFactory.Loader.create(policy);
factory.init();
}
catch (Exception x)
{
throw new ServletException(x);
}
}
@Override
public String toString()
{
return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
}
}

View File

@ -0,0 +1,92 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator
{
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
private final WebSocketServerFactory factory;
public WebSocketUpgradeHandlerWrapper()
{
factory = new WebSocketServerFactory();
}
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
pathmap.put(spec,creator);
}
@Override
public PathMappings<WebSocketCreator> getMappings()
{
return pathmap;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (factory.isUpgradeRequest(request,response))
{
MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
if (resource == null)
{
// no match.
response.sendError(HttpServletResponse.SC_NOT_FOUND,"No websocket endpoint matching path: " + target);
return;
}
WebSocketCreator creator = resource.getResource();
// Store PathSpec resource mapping as request attribute
request.setAttribute(PathSpec.class.getName(),resource);
// We have an upgrade request
if (factory.acceptWebSocket(creator,request,response))
{
// We have a socket instance created
return;
}
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (response.isCommitted())
{
// not much we can do at this point.
return;
}
}
super.handle(target,baseRequest,request,response);
}
}

View File

@ -16,14 +16,21 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jetty.websocket.jsr356.server.pathmap.PathMappings.MappedResource;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
/**
* Path Mappings of PathSpec to Resource.
@ -32,8 +39,10 @@ import org.eclipse.jetty.websocket.jsr356.server.pathmap.PathMappings.MappedReso
*
* @param <E>
*/
public class PathMappings<E> implements Iterable<MappedResource<E>>
@ManagedObject("Path Mappings")
public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
{
@ManagedObject("Mapped Resource")
public static class MappedResource<E> implements Comparable<MappedResource<E>>
{
private final PathSpec pathSpec;
@ -84,11 +93,13 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>
return true;
}
@ManagedAttribute(value = "path spec", readonly = true)
public PathSpec getPathSpec()
{
return pathSpec;
}
@ManagedAttribute(value = "resource", readonly = true)
public E getResource()
{
return resource;
@ -110,9 +121,28 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>
}
}
private static final Logger LOG = Log.getLogger(PathMappings.class);
private List<MappedResource<E>> mappings = new ArrayList<MappedResource<E>>();
private MappedResource<E> defaultResource = null;
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dump(out,indent,mappings);
}
@ManagedAttribute(value = "mappings", readonly = true)
public List<MappedResource<E>> getMappings()
{
return mappings;
}
public MappedResource<E> getMatch(String path)
{
int len = mappings.size();
@ -142,6 +172,13 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>
}
// TODO: warning on replacement of existing mapping?
mappings.add(entry);
LOG.debug("Added {} to {}",entry,this);
Collections.sort(mappings);
}
@Override
public String toString()
{
return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
/**
* The base PathSpec, what all other path specs are based on
@ -79,6 +79,11 @@ public abstract class PathSpec implements Comparable<PathSpec>
return true;
}
public PathSpecGroup getGroup()
{
return group;
}
/**
* Get the number of path elements that this path spec declares.
* <p>

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
/**
* Types of path spec groups.

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import org.eclipse.jetty.util.URIUtil;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
@ -26,6 +26,8 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.annotation.Stress;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@ -0,0 +1,117 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.server.pathmap;
import static org.hamcrest.Matchers.*;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
import org.junit.Assert;
import org.junit.Test;
public class PathMappingsTest
{
private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
{
String msg = String.format(".getMatch(\"%s\")",path);
MappedResource<String> match = pathmap.getMatch(path);
Assert.assertThat(msg,match,notNullValue());
String actualMatch = match.getResource();
Assert.assertEquals(msg,expectedValue,actualMatch);
}
/**
* Test the match order rules with a mixed Servlet and WebSocket path specs
* <p>
* <ul>
* <li>Exact match</li>
* <li>Longest prefix match</li>
* <li>Longest suffix match</li>
* </ul>
*/
@Test
public void testMixedMatchOrder()
{
PathMappings<String> p = new PathMappings<>();
p.put(new ServletPathSpec("/"),"default");
p.put(new ServletPathSpec("/animal/bird/*"),"birds");
p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
p.put(new ServletPathSpec("/animal/*"),"animals");
p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat");
p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam");
p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam");
for (MappedResource<String> res : p)
{
System.out.printf(" %s%n",res);
}
assertMatch(p,"/animal/bird/eagle","birds");
assertMatch(p,"/animal/fish/bass/sea","fishes");
assertMatch(p,"/animal/peccary/javalina/evolution","animals");
assertMatch(p,"/","default");
assertMatch(p,"/animal/bird/eagle/chat","animalChat");
assertMatch(p,"/animal/bird/penguin/chat","animalChat");
assertMatch(p,"/animal/fish/trout/cam","animalCam");
assertMatch(p,"/entrance/cam","entranceCam");
}
/**
* Test the match order rules imposed by the Servlet API.
* <p>
* <ul>
* <li>Exact match</li>
* <li>Longest prefix match</li>
* <li>Longest suffix match</li>
* <li>default</li>
* </ul>
*/
@Test
public void testServletMatchOrder()
{
PathMappings<String> p = new PathMappings<>();
p.put(new ServletPathSpec("/abs/path"),"path");
p.put(new ServletPathSpec("/abs/path/longer"),"longpath");
p.put(new ServletPathSpec("/animal/bird/*"),"birds");
p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
p.put(new ServletPathSpec("/animal/*"),"animals");
p.put(new ServletPathSpec("*.tar.gz"),"tarball");
p.put(new ServletPathSpec("*.gz"),"gzipped");
p.put(new ServletPathSpec("/"),"default");
for (MappedResource<String> res : p)
{
System.out.printf(" %s%n",res);
}
assertMatch(p,"/abs/path","path");
assertMatch(p,"/abs/path/longer","longpath");
assertMatch(p,"/abs/path/foo","default");
assertMatch(p,"/main.css","default");
assertMatch(p,"/downloads/script.gz","gzipped");
assertMatch(p,"/downloads/distribution.tar.gz","tarball");
assertMatch(p,"/downloads/readme.txt","default");
assertMatch(p,"/downloads/logs.tgz","default");
assertMatch(p,"/animal/horse/mustang","animals");
assertMatch(p,"/animal/bird/eagle/bald","birds");
assertMatch(p,"/animal/fish/shark/hammerhead","fishes");
assertMatch(p,"/animal/insect/ladybug","animals");
}
}

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -73,6 +73,27 @@ public class RegexPathSpecTest
assertNotMatches(spec,"/rest/list");
}
@Test
public void testMiddleSpecNoGrouping()
{
RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$");
assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec());
assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern());
assertEquals("Spec.pathDepth",3,spec.getPathDepth());
assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
assertMatches(spec,"/rest/api/list");
assertMatches(spec,"/rest/1.0/list");
assertMatches(spec,"/rest/2.0/list");
assertMatches(spec,"/rest/accounts/list");
assertNotMatches(spec,"/a");
assertNotMatches(spec,"/aa");
assertNotMatches(spec,"/aa/bb");
assertNotMatches(spec,"/rest/admin/delete");
assertNotMatches(spec,"/rest/list");
}
@Test
public void testPrefixSpec()
{

View File

@ -16,11 +16,12 @@
// ========================================================================
//
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
package org.eclipse.jetty.websocket.server.pathmap;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec;
import org.junit.Test;
public class ServletPathSpecTest

View File

@ -19,8 +19,6 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.util.Iterator;
import java.util.ServiceLoader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -128,24 +126,7 @@ public abstract class WebSocketServlet extends HttpServlet
policy.setInputBufferSize(Integer.parseInt(max));
}
WebSocketServletFactory baseFactory;
Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
if (factories.hasNext())
{
baseFactory = factories.next();
}
else
{
// Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi)
ClassLoader loader = Thread.currentThread().getContextClassLoader();
@SuppressWarnings("unchecked")
Class<WebSocketServletFactory> wssf = (Class<WebSocketServletFactory>)loader
.loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory");
baseFactory = wssf.newInstance();
}
factory = baseFactory.createFactory(policy);
factory = WebSocketServletFactory.Loader.create(policy);
configure(factory);

View File

@ -19,6 +19,8 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.util.Iterator;
import java.util.ServiceLoader;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -32,8 +34,47 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
*/
public interface WebSocketServletFactory
{
public static class Loader
{
private static WebSocketServletFactory INSTANCE;
public static WebSocketServletFactory create(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
return load(policy).createFactory(policy);
}
public static WebSocketServletFactory load(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
if (INSTANCE != null)
{
return INSTANCE;
}
WebSocketServletFactory baseFactory;
Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
if (factories.hasNext())
{
baseFactory = factories.next();
}
else
{
// Load the default class if ServiceLoader mechanism isn't valid in this environment. (such as OSGi)
ClassLoader loader = Thread.currentThread().getContextClassLoader();
@SuppressWarnings("unchecked")
Class<WebSocketServletFactory> wssf = (Class<WebSocketServletFactory>)loader
.loadClass("org.eclipse.jetty.websocket.server.WebSocketServerFactory");
baseFactory = wssf.newInstance();
}
INSTANCE = baseFactory;
return INSTANCE;
}
}
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException;
public boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException;
public void cleanup();
public WebSocketServletFactory createFactory(WebSocketPolicy policy);