Merge branch 'jetty-9.3.x' into jetty-9.4.x

This commit is contained in:
Joakim Erdfelt 2016-11-30 14:41:43 -07:00
commit 47fd7e1bd2
31 changed files with 1455 additions and 420 deletions

View File

@ -190,7 +190,7 @@ public class ClientContainer extends ContainerLifeCycle implements WebSocketCont
protected void doStart() throws Exception
{
super.doStart();
// Initialize the default decoder / encoder factories
EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
this.decoderFactory.init(empty);

View File

@ -166,6 +166,12 @@ public class DecoderFactory implements Configurable
{
LOG.debug("init({})",config);
}
if(!containerScope.isRunning())
{
throw new RuntimeException(containerScope.getClass().getName() + " is not running yet");
}
// Instantiate all declared decoders
for (DecoderMetadata metadata : metadatas)
{

View File

@ -23,6 +23,7 @@ import java.util.Map;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
@ -38,7 +39,7 @@ public class PathParamServerEndpointConfig extends BasicServerEndpointConfig imp
super(containerScope, config);
Map<String, String> pathMap = pathSpec.getPathParams(requestPath);
pathParamMap = new HashMap<String, String>();
pathParamMap = new HashMap<>();
if (pathMap != null)
{
pathParamMap.putAll(pathMap);

View File

@ -40,28 +40,26 @@ import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory;
import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
{
private static final Logger LOG = Log.getLogger(ServerContainer.class);
private final MappedWebSocketCreator mappedCreator;
private final WebSocketServerFactory webSocketServerFactory;
private final NativeWebSocketConfiguration configuration;
private List<Class<?>> deferredEndpointClasses;
private List<ServerEndpointConfig> deferredEndpointConfigs;
public ServerContainer(MappedWebSocketCreator creator, WebSocketServerFactory factory, Executor executor)
public ServerContainer(NativeWebSocketConfiguration configuration, Executor executor)
{
super(factory);
this.mappedCreator = creator;
this.webSocketServerFactory = factory;
EventDriverFactory eventDriverFactory = this.webSocketServerFactory.getEventDriverFactory();
super(configuration.getFactory());
this.configuration = configuration;
EventDriverFactory eventDriverFactory = this.configuration.getFactory().getEventDriverFactory();
eventDriverFactory.addImplementation(new JsrServerEndpointImpl());
eventDriverFactory.addImplementation(new JsrServerExtendsEndpointImpl());
this.webSocketServerFactory.addSessionFactory(new JsrSessionFactory(this));
addBean(webSocketServerFactory);
this.configuration.getFactory().addSessionFactory(new JsrSessionFactory(this));
addBean(this.configuration);
}
public EndpointInstance newClientEndpointInstance(Object endpoint, ServerEndpointConfig config, String path)
@ -102,8 +100,8 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
private void addEndpoint(ServerEndpointMetadata metadata) throws DeploymentException
{
JsrCreator creator = new JsrCreator(this,metadata,webSocketServerFactory.getExtensionFactory());
mappedCreator.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator);
JsrCreator creator = new JsrCreator(this,metadata,this.configuration.getFactory().getExtensionFactory());
this.configuration.addMapping(new UriTemplatePathSpec(metadata.getPath()),creator);
}
@Override
@ -154,13 +152,6 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
}
}
@Override
protected void doStop() throws Exception
{
mappedCreator.getMappings().reset();
super.doStop();
}
public ServerEndpointMetadata getServerEndpointMetadata(final Class<?> endpoint, final ServerEndpointConfig config) throws DeploymentException
{
ServerEndpointMetadata metadata = null;
@ -193,36 +184,41 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
return metadata;
}
@Override
public long getDefaultAsyncSendTimeout()
{
return webSocketServerFactory.getPolicy().getAsyncWriteTimeout();
return this.configuration.getPolicy().getAsyncWriteTimeout();
}
@Override
public int getDefaultMaxBinaryMessageBufferSize()
{
return webSocketServerFactory.getPolicy().getMaxBinaryMessageSize();
return this.configuration.getPolicy().getMaxBinaryMessageSize();
}
@Override
public long getDefaultMaxSessionIdleTimeout()
{
return webSocketServerFactory.getPolicy().getIdleTimeout();
return this.configuration.getPolicy().getIdleTimeout();
}
@Override
public int getDefaultMaxTextMessageBufferSize()
{
return webSocketServerFactory.getPolicy().getMaxTextMessageSize();
return this.configuration.getPolicy().getMaxTextMessageSize();
}
public WebSocketServerFactory getWebSocketServerFactory()
{
return this.configuration.getFactory();
}
@Override
public void setAsyncSendTimeout(long ms)
{
super.setAsyncSendTimeout(ms);
webSocketServerFactory.getPolicy().setAsyncWriteTimeout(ms);
this.configuration.getPolicy().setAsyncWriteTimeout(ms);
}
@Override
@ -230,16 +226,16 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
{
super.setDefaultMaxBinaryMessageBufferSize(max);
// overall message limit (used in non-streaming)
webSocketServerFactory.getPolicy().setMaxBinaryMessageSize(max);
this.configuration.getPolicy().setMaxBinaryMessageSize(max);
// incoming streaming buffer size
webSocketServerFactory.getPolicy().setMaxBinaryMessageBufferSize(max);
this.configuration.getPolicy().setMaxBinaryMessageBufferSize(max);
}
@Override
public void setDefaultMaxSessionIdleTimeout(long ms)
{
super.setDefaultMaxSessionIdleTimeout(ms);
webSocketServerFactory.getPolicy().setIdleTimeout(ms);
this.configuration.getPolicy().setIdleTimeout(ms);
}
@Override
@ -247,26 +243,26 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
{
super.setDefaultMaxTextMessageBufferSize(max);
// overall message limit (used in non-streaming)
webSocketServerFactory.getPolicy().setMaxTextMessageSize(max);
this.configuration.getPolicy().setMaxTextMessageSize(max);
// incoming streaming buffer size
webSocketServerFactory.getPolicy().setMaxTextMessageBufferSize(max);
this.configuration.getPolicy().setMaxTextMessageBufferSize(max);
}
@Override
public void onSessionClosed(WebSocketSession session)
{
webSocketServerFactory.onSessionClosed(session);
getWebSocketServerFactory().onSessionClosed(session);
}
@Override
public void onSessionOpened(WebSocketSession session)
{
webSocketServerFactory.onSessionOpened(session);
getWebSocketServerFactory().onSessionOpened(session);
}
@Override
public Set<Session> getOpenSessions()
{
return new HashSet<>(webSocketServerFactory.getBeans(Session.class));
return new HashSet<>(getWebSocketServerFactory().getBeans(Session.class));
}
}

View File

@ -33,18 +33,14 @@ import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.jsr356.server.ServerContainer;
import org.eclipse.jetty.websocket.server.DefaultMappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration;
import org.eclipse.jetty.websocket.server.NativeWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
@HandlesTypes(
@ -137,18 +133,12 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
// Create Basic components
WebSocketPolicy policy = WebSocketPolicy.newServerPolicy();
ByteBufferPool bufferPool = new MappedByteBufferPool();
MappedWebSocketCreator creator = new DefaultMappedWebSocketCreator();
WebSocketServerFactory factory = new WebSocketServerFactory(policy, bufferPool);
NativeWebSocketConfiguration nativeWebSocketConfiguration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(creator,factory,context.getServer().getThreadPool());
ServerContainer jettyContainer = new ServerContainer(nativeWebSocketConfiguration, context.getServer().getThreadPool());
context.addBean(jettyContainer);
context.setAttribute(WebSocketUpgradeFilter.CREATOR_KEY, creator);
context.setAttribute(WebSocketUpgradeFilter.FACTORY_KEY, factory);
// Store a reference to the ServerContainer per javax.websocket spec 1.0 final section 6.4 Programmatic Server Deployment
context.setAttribute(javax.websocket.server.ServerContainer.class.getName(),jettyContainer);

View File

@ -18,21 +18,27 @@
package org.eclipse.jetty.websocket.jsr356.server;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class DummyCreator implements MappedWebSocketCreator
{
@Override
public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator)
{
/* do nothing */
}
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
/* do nothing */
}
@Override
public PathMappings<WebSocketCreator> getMappings()
public MappedResource<WebSocketCreator> getMapping(String target)
{
return null;
}

View File

@ -47,7 +47,7 @@ import org.eclipse.jetty.websocket.jsr356.JsrExtension;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
@ -88,8 +88,9 @@ public class ExtensionStackProcessingTest
private void assumeDeflateFrameAvailable()
{
WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)servletContextHandler.getAttribute(WebSocketUpgradeFilter.class.getName());
ExtensionFactory serverExtensionFactory = filter.getFactory().getExtensionFactory();
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) servletContextHandler
.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
ExtensionFactory serverExtensionFactory = configuration.getFactory().getExtensionFactory();
Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate"));
}

View File

@ -85,6 +85,8 @@ public class OnPartialTest
URI requestURI = URI.create("ws://localhost/" + id);
DummyConnection connection = new DummyConnection();
ClientContainer container = new ClientContainer();
container.start();
@SuppressWarnings("resource")
JsrSession session = new JsrSession(container,id,requestURI,driver,connection);
session.setPolicy(policy);

View File

@ -67,7 +67,7 @@ public class SessionTrackingTest
serverContainer = WebSocketServerContainerInitializer.configureContext(servletContextHandler);
serverContainer.addEndpoint(EchoSocket.class);
wsServerFactory = serverContainer.getBean(WebSocketServerFactory.class);
wsServerFactory = serverContainer.getWebSocketServerFactory();
server.start();

View File

@ -36,48 +36,54 @@ public interface WebSocketContainerScope
*
* @return the buffer pool (never null)
*/
public ByteBufferPool getBufferPool();
ByteBufferPool getBufferPool();
/**
* Executor in use by the container.
*
* @return the Executor in use by the container.
*/
public Executor getExecutor();
Executor getExecutor();
/**
* Object Factory used to create objects.
*
* @return Object Factory used to create instances of objects.
*/
public DecoratedObjectFactory getObjectFactory();
DecoratedObjectFactory getObjectFactory();
/**
* The policy the container is running on.
*
* @return the websocket policy
*/
public WebSocketPolicy getPolicy();
WebSocketPolicy getPolicy();
/**
* The SslContextFactory in use by the container.
*
* @return the SslContextFactory in use by the container (can be null if no SSL context is defined)
*/
public SslContextFactory getSslContextFactory();
SslContextFactory getSslContextFactory();
/**
* Test for if the container has been started.
*
* @return true if container is started and running
*/
boolean isRunning();
/**
* A Session has been opened
*
* @param session the session that was opened
*/
public void onSessionOpened(WebSocketSession session);
void onSessionOpened(WebSocketSession session);
/**
* A Session has been closed
*
* @param session the session that was closed
*/
public void onSessionClosed(WebSocketSession session);
void onSessionClosed(WebSocketSession session);
}

View File

@ -79,6 +79,18 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-annotations</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-common</artifactId>

View File

@ -18,8 +18,8 @@
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
/**
@ -27,7 +27,32 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
*/
public interface MappedWebSocketCreator
{
public void addMapping(PathSpec spec, WebSocketCreator creator);
public PathMappings<WebSocketCreator> getMappings();
/**
* Add a mapping.
*
* @param spec the path spec to use
* @param creator the creator for the mapping
* @deprecated use {@link #addMapping(org.eclipse.jetty.http.pathmap.PathSpec, WebSocketCreator)} instead.
* (support classes moved to generic jetty-http project)
*/
@Deprecated
void addMapping(PathSpec spec, WebSocketCreator creator);
/**
* Add a mapping.
*
* @param spec the path spec to use
* @param creator the creator for the mapping
* @since 9.2.20
*/
void addMapping(org.eclipse.jetty.http.pathmap.PathSpec spec, WebSocketCreator creator);
/**
* Get specific MappedResource for associated target.
*
* @param target the target to get mapping for
* @return the MappedResource for the target, or null if no match.
* @since 9.2.20
*/
MappedResource<WebSocketCreator> getMapping(String target);
}

View File

@ -0,0 +1,173 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.ServletContext;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
/**
* Interface for Configuring Jetty Server Native WebSockets
* <p>
* Only applicable if using {@link WebSocketUpgradeFilter}
* </p>
*/
public class NativeWebSocketConfiguration extends ContainerLifeCycle implements Dumpable
{
private final WebSocketServerFactory factory;
private final PathMappings<WebSocketCreator> mappings = new PathMappings<>();
public NativeWebSocketConfiguration(ServletContext context)
{
this(new WebSocketServerFactory(context));
}
public NativeWebSocketConfiguration(WebSocketServerFactory webSocketServerFactory)
{
this.factory = webSocketServerFactory;
addBean(this.factory);
}
@Override
public void doStop() throws Exception
{
mappings.reset();
super.doStop();
}
@Override
public String dump()
{
return ContainerLifeCycle.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
// TODO: show factory/mappings ?
mappings.dump(out, indent);
}
/**
* Get WebSocketServerFactory being used.
*
* @return the WebSocketServerFactory being used.
*/
public WebSocketServerFactory getFactory()
{
return this.factory;
}
/**
* Get the matching {@link MappedResource} for the provided target.
*
* @param target the target path
* @return the matching resource, or null if no match.
*/
public MappedResource<WebSocketCreator> getMatch(String target)
{
return this.mappings.getMatch(target);
}
/**
* Used to configure the Default {@link WebSocketPolicy} used by all endpoints that
* don't redeclare the values.
*
* @return the default policy for all WebSockets
*/
public WebSocketPolicy getPolicy()
{
return this.factory.getPolicy();
}
/**
* Manually add a WebSocket mapping.
*
* @param pathSpec the pathspec to respond on
* @param creator the websocket creator to activate on the provided mapping.
*/
public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
{
mappings.put(pathSpec, creator);
}
/**
* Manually add a WebSocket mapping.
*
* @param spec the pathspec to respond on
* @param creator the websocket creator to activate on the provided mapping
* @deprecated use {@link #addMapping(PathSpec, Class)} instead.
*/
@Deprecated
public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator)
{
if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec)
{
addMapping(new ServletPathSpec(spec.getSpec()), creator);
}
else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec)
{
addMapping(new RegexPathSpec(spec.getSpec()), creator);
}
else
{
throw new RuntimeException("Unsupported (Deprecated) PathSpec implementation type: " + spec.getClass().getName());
}
}
/**
* Manually add a WebSocket mapping.
*
* @param pathSpec the pathspec to respond on
* @param endpointClass the endpoint class to use for new upgrade requests on the provided
* pathspec (can be an {@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated
* POJO, or implementing {@link org.eclipse.jetty.websocket.api.WebSocketListener})
*/
public void addMapping(PathSpec pathSpec, final Class<?> endpointClass)
{
mappings.put(pathSpec, new WebSocketCreator()
{
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
try
{
return endpointClass.newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
throw new WebSocketException("Unable to create instance of " + endpointClass.getName(), e);
}
}
});
}
}

View File

@ -0,0 +1,48 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public class NativeWebSocketServletContainerInitializer implements ServletContainerInitializer
{
public static NativeWebSocketConfiguration getDefaultFrom(ServletContext context)
{
final String KEY = NativeWebSocketConfiguration.class.getName();
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(KEY);
if (configuration == null)
{
configuration = new NativeWebSocketConfiguration(context);
context.setAttribute(KEY, configuration);
}
return configuration;
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
{
// initialize
getDefaultFrom(ctx);
}
}

View File

@ -53,8 +53,9 @@ public abstract class WebSocketHandler extends HandlerWrapper
factory.register(websocketPojo);
}
}
private final WebSocketServletFactory webSocketFactory;
private final ByteBufferPool bufferPool;
private WebSocketServletFactory webSocketFactory;
public WebSocketHandler()
{
@ -63,10 +64,7 @@ public abstract class WebSocketHandler extends HandlerWrapper
public WebSocketHandler(ByteBufferPool bufferPool)
{
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
configurePolicy(policy);
webSocketFactory = new WebSocketServerFactory(policy, bufferPool);
addBean(webSocketFactory);
this.bufferPool = bufferPool;
}
public abstract void configure(WebSocketServletFactory factory);
@ -79,12 +77,18 @@ public abstract class WebSocketHandler extends HandlerWrapper
@Override
protected void doStart() throws Exception
{
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
configurePolicy(policy);
webSocketFactory = new WebSocketServerFactory(policy, getServer().getThreadPool(), bufferPool);
addBean(webSocketFactory);
configure(webSocketFactory);
super.doStart();
}
public WebSocketServletFactory getWebSocketFactory()
{
if (!isRunning())
throw new IllegalStateException("Not Started yet");
return webSocketFactory;
}

View File

@ -28,12 +28,12 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -48,7 +48,6 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -83,12 +82,10 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketContainerScope, WebSocketServletFactory
{
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
private final ClassLoader contextClassloader;
private final Map<Integer, WebSocketHandshake> handshakes = new HashMap<>();
/**
* Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
*/
// TODO: obtain shared (per server scheduler, somehow)
private final Scheduler scheduler = new ScheduledExecutorScheduler();
private final List<WebSocketSession.Listener> listeners = new CopyOnWriteArrayList<>();
private final String supportedVersions;
@ -96,47 +93,72 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
private final EventDriverFactory eventDriverFactory;
private final ByteBufferPool bufferPool;
private final WebSocketExtensionFactory extensionFactory;
private final ServletContext context; // can be null when this factory is used from WebSocketHandler
private final List<SessionFactory> sessionFactories = new ArrayList<>();
private final List<Class<?>> registeredSocketClasses = new ArrayList<>();
private Executor executor;
private List<SessionFactory> sessionFactories;
private WebSocketCreator creator;
private List<Class<?>> registeredSocketClasses;
private DecoratedObjectFactory objectFactory;
public WebSocketServerFactory()
private WebSocketCreator creator;
public WebSocketServerFactory(ServletContext context)
{
this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
this(context, WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
}
public WebSocketServerFactory(WebSocketPolicy policy)
public WebSocketServerFactory(ServletContext context, ByteBufferPool bufferPool)
{
this(policy, new MappedByteBufferPool());
this(context, WebSocketPolicy.newServerPolicy(), bufferPool);
}
public WebSocketServerFactory(ByteBufferPool bufferPool)
/**
* Entry point for {@link org.eclipse.jetty.websocket.servlet.WebSocketServletFactory.Loader}
*
* @param context the servlet context
* @param policy the policy to use
*/
public WebSocketServerFactory(ServletContext context, WebSocketPolicy policy)
{
this(WebSocketPolicy.newServerPolicy(), bufferPool);
this(context, policy, new MappedByteBufferPool());
}
public WebSocketServerFactory(WebSocketPolicy policy, ByteBufferPool bufferPool)
public WebSocketServerFactory(ServletContext context, WebSocketPolicy policy, ByteBufferPool bufferPool)
{
this(Objects.requireNonNull(context, ServletContext.class.getName()), policy, null, null, bufferPool);
}
/**
* Protected entry point for {@link WebSocketHandler}
*
* @param policy the policy to use
* @param executor the executor to use
* @param bufferPool the buffer pool to use
*/
protected WebSocketServerFactory(WebSocketPolicy policy, Executor executor, ByteBufferPool bufferPool)
{
this(null, policy, new DecoratedObjectFactory(), executor, bufferPool);
}
private WebSocketServerFactory(ServletContext context, WebSocketPolicy policy, DecoratedObjectFactory objectFactory, Executor executor, ByteBufferPool bufferPool)
{
this.context = context;
this.objectFactory = objectFactory;
this.executor = executor;
handshakes.put(HandshakeRFC6455.VERSION, new HandshakeRFC6455());
addBean(scheduler);
addBean(bufferPool);
this.contextClassloader = Thread.currentThread().getContextClassLoader();
this.registeredSocketClasses = new ArrayList<>();
this.defaultPolicy = policy;
this.eventDriverFactory = new EventDriverFactory(defaultPolicy);
this.bufferPool = bufferPool;
this.extensionFactory = new WebSocketExtensionFactory(this);
this.sessionFactories = new ArrayList<>();
this.sessionFactories.add(new WebSocketSessionFactory(this));
this.creator = this;
// Create supportedVersions
List<Integer> versions = new ArrayList<>();
for (int v : handshakes.keySet())
@ -155,23 +177,23 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
supportedVersions = rv.toString();
}
public void addSessionListener(WebSocketSession.Listener listener)
{
listeners.add(listener);
}
public void removeSessionListener(WebSocketSession.Listener listener)
{
listeners.remove(listener);
}
@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
{
@ -179,32 +201,32 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
try
{
Thread.currentThread().setContextClassLoader(contextClassloader);
// Create Servlet Specific Upgrade Request/Response objects
ServletUpgradeRequest sockreq = new ServletUpgradeRequest(request);
ServletUpgradeResponse sockresp = new ServletUpgradeResponse(response);
Object websocketPojo = creator.createWebSocket(sockreq, sockresp);
// Handle response forbidden (and similar paths)
if (sockresp.isCommitted())
{
return false;
}
if (websocketPojo == null)
{
// no creation, sorry
sockresp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Endpoint Creation Failed");
return false;
}
// Allow Decorators to do their thing
websocketPojo = getObjectFactory().decorate(websocketPojo);
// Get the original HTTPConnection
HttpConnection connection = (HttpConnection)request.getAttribute("org.eclipse.jetty.server.HttpConnection");
HttpConnection connection = (HttpConnection) request.getAttribute("org.eclipse.jetty.server.HttpConnection");
// Send the upgrade
EventDriver driver = eventDriverFactory.wrap(websocketPojo);
return upgrade(connection, sockreq, sockresp, driver);
@ -218,7 +240,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
Thread.currentThread().setContextClassLoader(old);
}
}
public void addSessionFactory(SessionFactory sessionFactory)
{
if (sessionFactories.contains(sessionFactory))
@ -227,33 +249,14 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
this.sessionFactories.add(sessionFactory);
}
@Override
public void cleanup()
{
try
{
this.stop();
}
catch (Exception e)
{
LOG.warn(e);
}
}
@Override
public WebSocketServletFactory createFactory(WebSocketPolicy policy)
{
return new WebSocketServerFactory(policy, bufferPool);
}
private WebSocketSession createSession(URI requestURI, EventDriver websocket, LogicalConnection connection)
{
if (websocket == null)
{
throw new InvalidWebSocketException("Unable to create Session from null websocket");
}
for (SessionFactory impl : sessionFactories)
{
if (impl.supports(websocket))
@ -268,10 +271,10 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
}
}
throw new InvalidWebSocketException("Unable to create Session: unrecognized internal EventDriver type: " + websocket.getClass().getName());
}
/**
* Default Creator logic
*/
@ -282,12 +285,12 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
{
throw new WebSocketException("No WebSockets have been registered with the factory. Cannot use default implementation of WebSocketCreator.");
}
if (registeredSocketClasses.size() > 1)
{
LOG.warn("You have registered more than 1 websocket object, and are using the default WebSocketCreator! Using first registered websocket.");
}
Class<?> firstClass = registeredSocketClasses.get(0);
try
{
@ -298,63 +301,76 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
throw new WebSocketException("Unable to create instance of " + firstClass, e);
}
}
@Override
protected void doStart() throws Exception
{
if(this.objectFactory == null)
if(this.objectFactory == null && context != null)
{
this.objectFactory = new DecoratedObjectFactory();
this.objectFactory = (DecoratedObjectFactory) context.getAttribute(DecoratedObjectFactory.ATTR);
if (this.objectFactory == null)
{
throw new IllegalStateException("Unable to find required ServletContext attribute: " + DecoratedObjectFactory.ATTR);
}
}
if(this.executor == null && context != null)
{
ContextHandler contextHandler = ContextHandler.getContextHandler(context);
this.executor = contextHandler.getServer().getThreadPool();
}
Objects.requireNonNull(this.objectFactory, DecoratedObjectFactory.class.getName());
Objects.requireNonNull(this.executor, Executor.class.getName());
super.doStart();
}
@Override
public ByteBufferPool getBufferPool()
{
return this.bufferPool;
}
@Override
public WebSocketCreator getCreator()
{
return this.creator;
}
@Override
public Executor getExecutor()
{
return this.executor;
}
public DecoratedObjectFactory getObjectFactory()
{
return objectFactory;
}
public EventDriverFactory getEventDriverFactory()
{
return eventDriverFactory;
}
@Override
public ExtensionFactory getExtensionFactory()
{
return extensionFactory;
}
public Collection<WebSocketSession> getOpenSessions()
{
return getBeans(WebSocketSession.class);
}
@Override
public WebSocketPolicy getPolicy()
{
return defaultPolicy;
}
@Override
public SslContextFactory getSslContextFactory()
{
@ -363,53 +379,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
*/
return null;
}
public void init(ServletContextHandler context) throws ServletException
{
this.objectFactory = (DecoratedObjectFactory)context.getServletContext().getAttribute(DecoratedObjectFactory.ATTR);
if (this.objectFactory == null)
{
this.objectFactory = new DecoratedObjectFactory();
}
this.executor = context.getServer().getThreadPool();
}
@Override
public void init(ServletContext context) throws ServletException
{
// Setup ObjectFactory
this.objectFactory = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR);
if (this.objectFactory == null)
{
this.objectFactory = new DecoratedObjectFactory();
}
// Validate Environment
ContextHandler handler = ContextHandler.getContextHandler(context);
if (handler == null)
{
throw new ServletException("Not running on Jetty, WebSocket support unavailable");
}
this.executor = handler.getServer().getThreadPool();
try
{
// start lifecycle
start();
}
catch (ServletException e)
{
throw e;
}
catch (Exception e)
{
throw new ServletException(e);
}
}
@Override
public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response)
{
@ -434,7 +404,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// no "Connection: upgrade" header present.
return false;
}
// Test for "Upgrade" token
boolean foundUpgradeToken = false;
Iterator<String> iter = QuoteUtil.splitAt(connection, ",");
@ -447,7 +417,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
break;
}
}
if (!foundUpgradeToken)
{
return false;
@ -458,30 +428,30 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// not a "GET" request (not a websocket upgrade)
return false;
}
if (!"HTTP/1.1".equals(request.getProtocol()))
{
LOG.debug("Not a 'HTTP/1.1' request (was [" + request.getProtocol() + "])");
return false;
}
return true;
}
@Override
public void onSessionOpened(WebSocketSession session)
{
addManaged(session);
notifySessionListeners(listener -> listener.onOpened(session));
}
@Override
public void onSessionClosed(WebSocketSession session)
{
removeBean(session);
notifySessionListeners(listener -> listener.onClosed(session));
}
private void notifySessionListeners(Consumer<WebSocketSession.Listener> consumer)
{
for (WebSocketSession.Listener listener : listeners)
@ -496,30 +466,29 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
}
}
@Override
public void register(Class<?> websocketPojo)
{
registeredSocketClasses.add(websocketPojo);
}
@Override
public void setCreator(WebSocketCreator creator)
{
this.creator = creator;
}
/**
* Upgrade the request/response to a WebSocket Connection.
* <p/>
* This method will not normally return, but will instead throw a UpgradeConnectionException, to exit HTTP handling and initiate WebSocket handling of the
* connection.
*
* @param http the raw http connection
* @param request The request to upgrade
* @param http the raw http connection
* @param request The request to upgrade
* @param response The response to upgrade
* @param driver The websocket handler implementation to use
* @throws IOException
* @param driver The websocket handler implementation to use
*/
private boolean upgrade(HttpConnection http, ServletUpgradeRequest request, ServletUpgradeResponse response, EventDriver driver) throws IOException
{
@ -531,14 +500,14 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
{
throw new IllegalStateException("Not a 'HTTP/1.1' request");
}
int version = request.getHeaderInt("Sec-WebSocket-Version");
if (version < 0)
{
// Old pre-RFC version specifications (header not present in RFC-6455)
version = request.getHeaderInt("Sec-WebSocket-Draft");
}
WebSocketHandshake handshaker = handshakes.get(version);
if (handshaker == null)
{
@ -563,14 +532,14 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
warn.append(": [").append(supportedVersions).append("]");
LOG.warn(warn.toString());
// Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
// Using the examples as outlined
response.setHeader("Sec-WebSocket-Version", supportedVersions);
response.sendError(HttpStatus.BAD_REQUEST_400, "Unsupported websocket version specification");
return false;
}
// Initialize / Negotiate Extensions
ExtensionStack extensionStack = new ExtensionStack(getExtensionFactory());
// The JSR allows for the extensions to be pre-negotiated, filtered, etc...
@ -585,26 +554,26 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
// Use raw extension list from request
extensionStack.negotiate(request.getExtensions());
}
// Get original HTTP connection
EndPoint endp = http.getEndPoint();
Connector connector = http.getConnector();
Executor executor = connector.getExecutor();
ByteBufferPool bufferPool = connector.getByteBufferPool();
// Setup websocket connection
AbstractWebSocketConnection wsConnection = new WebSocketServerConnection(endp, executor, scheduler, driver.getPolicy(), bufferPool);
extensionStack.setPolicy(driver.getPolicy());
extensionStack.configure(wsConnection.getParser());
extensionStack.configure(wsConnection.getGenerator());
if (LOG.isDebugEnabled())
{
LOG.debug("HttpConnection: {}", http);
LOG.debug("WebSocketConnection: {}", wsConnection);
}
// Setup Session
WebSocketSession session = createSession(request.getRequestURI(), driver, wsConnection);
session.setPolicy(driver.getPolicy());
@ -613,51 +582,51 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
response.setExtensions(extensionStack.getNegotiatedExtensions());
session.setUpgradeResponse(response);
wsConnection.addListener(session);
// Setup Incoming Routing
wsConnection.setNextIncomingFrames(extensionStack);
extensionStack.setNextIncoming(session);
// Setup Outgoing Routing
session.setOutgoingHandler(extensionStack);
extensionStack.setNextOutgoing(wsConnection);
// Start Components
session.addManaged(extensionStack);
this.addManaged(session);
if (session.isFailed())
{
throw new IOException("Session failed to start");
}
// Tell jetty about the new upgraded connection
request.setServletAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, wsConnection);
if (LOG.isDebugEnabled())
LOG.debug("Handshake Response: {}", handshaker);
if (getSendServerVersion(connector))
response.setHeader("Server",HttpConfiguration.SERVER_VERSION);
response.setHeader("Server", HttpConfiguration.SERVER_VERSION);
// Process (version specific) handshake response
handshaker.doHandshakeResponse(request, response);
if (LOG.isDebugEnabled())
LOG.debug("Websocket upgrade {} {} {} {}", request.getRequestURI(), version, response.getAcceptedSubProtocol(), wsConnection);
return true;
}
private boolean getSendServerVersion(Connector connector)
{
ConnectionFactory connFactory = connector.getConnectionFactory(HttpVersion.HTTP_1_1.asString());
if (connFactory == null)
return false;
if (connFactory instanceof HttpConnectionFactory)
{
HttpConfiguration httpConf = ((HttpConnectionFactory)connFactory).getHttpConfiguration();
HttpConfiguration httpConf = ((HttpConnectionFactory) connFactory).getHttpConfiguration();
if (httpConf != null)
return httpConf.getSendServerVersion();
}

View File

@ -33,35 +33,28 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
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;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
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 extends AbstractLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, Dumpable
{
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
private boolean localMapper;
private boolean localFactory;
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
public static final String CONFIG_ATTRIBUTE_KEY = "configAttributeKey";
public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException
{
@ -73,7 +66,8 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
}
// Dynamically add filter
filter = new WebSocketUpgradeFilter();
NativeWebSocketConfiguration configuration = NativeWebSocketServletContainerInitializer.getDefaultFrom(context.getServletContext());
filter = new WebSocketUpgradeFilter(configuration);
filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
String name = "Jetty_WebSocketUpgradeFilter";
@ -115,69 +109,71 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
return configureContext((ServletContextHandler) handler);
}
public static final String CREATOR_KEY = "org.eclipse.jetty.websocket.server.creator";
public static final String FACTORY_KEY = "org.eclipse.jetty.websocket.server.factory";
private final WebSocketPolicy policy;
private final ByteBufferPool bufferPool;
private WebSocketServerFactory factory;
private MappedWebSocketCreator mappedWebSocketCreator;
private String fname;
private NativeWebSocketConfiguration configuration;
private boolean localConfiguration = false;
private boolean alreadySetToAttribute = false;
public WebSocketUpgradeFilter()
{
this(WebSocketPolicy.newServerPolicy(), new MappedByteBufferPool());
// do nothing
}
public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool)
public WebSocketUpgradeFilter(WebSocketServerFactory factory)
{
this.policy = policy;
this.bufferPool = bufferPool;
this(new NativeWebSocketConfiguration(factory));
}
public WebSocketUpgradeFilter(NativeWebSocketConfiguration configuration)
{
this.configuration = configuration;
}
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
mappedWebSocketCreator.addMapping(spec, creator);
configuration.addMapping(spec, creator);
}
/**
* @deprecated use new {@link #addMapping(org.eclipse.jetty.http.pathmap.PathSpec, WebSocketCreator)} instead
*/
@Deprecated
@Override
public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator)
{
if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec)
{
addMapping(new ServletPathSpec(spec.getSpec()), creator);
}
else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec)
{
addMapping(new RegexPathSpec(spec.getSpec()), creator);
}
else
{
throw new RuntimeException("Unsupported (Deprecated) PathSpec implementation: " + spec.getClass().getName());
}
configuration.addMapping(spec, creator);
}
@Override
public void destroy()
{
if (localFactory)
try
{
factory.cleanup();
alreadySetToAttribute = false;
if(localConfiguration)
{
configuration.stop();
}
}
if (localMapper)
catch (Exception e)
{
mappedWebSocketCreator.getMappings().reset();
LOG.ignore(e);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
if (configuration == null)
{
// no configuration, cannot operate
LOG.debug("WebSocketUpgradeFilter is not operational - missing " + NativeWebSocketConfiguration.class.getName());
chain.doFilter(request, response);
return;
}
WebSocketServletFactory factory = configuration.getFactory();
if (factory == null)
{
// no factory, cannot operate
@ -206,14 +202,9 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
target = target.substring(contextPath.length());
}
MappedResource<WebSocketCreator> resource = mappedWebSocketCreator.getMappings().getMatch(target);
MappedResource<WebSocketCreator> resource = configuration.getMatch(target);
if (resource == null)
{
if (LOG.isDebugEnabled())
{
LOG.debug("WebSocket Upgrade on {} has no associated endpoint", target);
LOG.debug("PathMappings: {}", mappedWebSocketCreator.getMappings().dump());
}
// no match.
chain.doFilter(request, response);
return;
@ -267,68 +258,89 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(indent).append(" +- pathmap=").append(mappedWebSocketCreator.toString()).append("\n");
mappedWebSocketCreator.getMappings().dump(out, indent + " ");
out.append(indent).append(" +- configuration=").append(configuration.toString()).append("\n");
configuration.dump(out, indent);
}
public WebSocketServerFactory getFactory()
public WebSocketServletFactory getFactory()
{
return factory;
return configuration.getFactory();
}
@ManagedAttribute(value = "configuration", readonly = true)
public NativeWebSocketConfiguration getConfiguration()
{
if (configuration == null)
{
throw new IllegalStateException(this.getClass().getName() + " not initialized yet");
}
return configuration;
}
@ManagedAttribute(value = "mappings", readonly = true)
@Override
public PathMappings<WebSocketCreator> getMappings()
public MappedResource<WebSocketCreator> getMapping(String target)
{
return mappedWebSocketCreator.getMappings();
return getConfiguration().getMatch(target);
}
@Override
public void init(FilterConfig config) throws ServletException
{
fname = config.getFilterName();
try
{
ServletContext context = config.getServletContext();
mappedWebSocketCreator = (MappedWebSocketCreator) context.getAttribute(CREATOR_KEY);
if (mappedWebSocketCreator == null)
String configurationKey = config.getInitParameter(CONFIG_ATTRIBUTE_KEY);
if (configurationKey == null)
{
mappedWebSocketCreator = new DefaultMappedWebSocketCreator();
localMapper = true;
configurationKey = NativeWebSocketConfiguration.class.getName();
}
factory = (WebSocketServerFactory) context.getAttribute(FACTORY_KEY);
if (factory == null)
if (configuration == null)
{
factory = new WebSocketServerFactory(policy, bufferPool);
localFactory = true;
this.configuration = (NativeWebSocketConfiguration) config.getServletContext().getAttribute(configurationKey);
if (this.configuration == null)
{
// The NativeWebSocketConfiguration should have arrived from the NativeWebSocketServletContainerInitializer
throw new ServletException("Unable to find required instance of " +
NativeWebSocketConfiguration.class.getName() + " at ServletContext attribute '" + configurationKey + "'");
}
}
else
{
// We have a NativeWebSocketConfiguration already present, make sure it exists on the ServletContext
if (config.getServletContext().getAttribute(configurationKey) == null)
{
config.getServletContext().setAttribute(configurationKey, this.configuration);
}
}
if(!this.configuration.isRunning())
{
localConfiguration = true;
this.configuration.start();
}
factory.init(context);
String max = config.getInitParameter("maxIdleTime");
if (max != null)
{
factory.getPolicy().setIdleTimeout(Long.parseLong(max));
getFactory().getPolicy().setIdleTimeout(Long.parseLong(max));
}
max = config.getInitParameter("maxTextMessageSize");
if (max != null)
{
factory.getPolicy().setMaxTextMessageSize(Integer.parseInt(max));
getFactory().getPolicy().setMaxTextMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("maxBinaryMessageSize");
if (max != null)
{
factory.getPolicy().setMaxBinaryMessageSize(Integer.parseInt(max));
getFactory().getPolicy().setMaxBinaryMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("inputBufferSize");
if (max != null)
{
factory.getPolicy().setInputBufferSize(Integer.parseInt(max));
getFactory().getPolicy().setInputBufferSize(Integer.parseInt(max));
}
String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
@ -338,33 +350,22 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
key = WebSocketUpgradeFilter.class.getName();
}
setToAttribute(context, key);
factory.start();
// Set instance of this filter to context attribute
setToAttribute(config.getServletContext(), key);
}
catch (Exception x)
catch (ServletException e)
{
throw new ServletException(x);
throw e;
}
catch (Throwable t)
{
throw new ServletException(t);
}
}
private void setToAttribute(ServletContextHandler context, String key) throws ServletException
{
if (alreadySetToAttribute)
{
return;
}
if (context.getAttribute(key) != null)
{
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
" is defined twice for the same context attribute key '" + key
+ "'. Make sure you have different init-param '" +
CONTEXT_ATTRIBUTE_KEY + "' values set");
}
context.setAttribute(key, this);
alreadySetToAttribute = true;
setToAttribute(context.getServletContext(), key);
}
public void setToAttribute(ServletContext context, String key) throws ServletException
@ -389,6 +390,6 @@ public class WebSocketUpgradeFilter extends AbstractLifeCycle implements Filter,
@Override
public String toString()
{
return String.format("%s[factory=%s,creator=%s]", this.getClass().getSimpleName(), factory, mappedWebSocketCreator);
return String.format("%s[configuration=%s]", this.getClass().getSimpleName(), configuration);
}
}

View File

@ -25,47 +25,60 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements MappedWebSocketCreator
{
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
private final WebSocketServerFactory factory;
private NativeWebSocketConfiguration configuration;
public WebSocketUpgradeHandlerWrapper()
public WebSocketUpgradeHandlerWrapper(ServletContextHandler context)
{
this(new MappedByteBufferPool());
this(context, new MappedByteBufferPool());
}
public WebSocketUpgradeHandlerWrapper(ByteBufferPool bufferPool)
public WebSocketUpgradeHandlerWrapper(ServletContextHandler context, ByteBufferPool bufferPool)
{
factory = new WebSocketServerFactory(bufferPool);
this.configuration = new NativeWebSocketConfiguration(new WebSocketServerFactory(context.getServletContext(), bufferPool));
}
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
pathmap.put(spec,creator);
this.configuration.addMapping(spec, creator);
}
/**
* Add a mapping.
*
* @param spec the path spec to use
* @param creator the creator for the mapping
* @deprecated use {@link #addMapping(PathSpec, WebSocketCreator)} instead.
*/
@Override
public PathMappings<WebSocketCreator> getMappings()
@Deprecated
public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator)
{
return pathmap;
configuration.addMapping(spec, creator);
}
@Override
public MappedResource<WebSocketCreator> getMapping(String target)
{
return this.configuration.getMatch(target);
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (factory.isUpgradeRequest(request,response))
if (configuration.getFactory().isUpgradeRequest(request,response))
{
MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
MappedResource<WebSocketCreator> resource = configuration.getMatch(target);
if (resource == null)
{
// no match.
@ -79,7 +92,7 @@ public class WebSocketUpgradeHandlerWrapper extends HandlerWrapper implements Ma
request.setAttribute(PathSpec.class.getName(),resource);
// We have an upgrade request
if (factory.acceptWebSocket(creator,request,response))
if (configuration.getFactory().acceptWebSocket(creator,request,response))
{
// We have a socket instance created
return;

View File

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

View File

@ -0,0 +1,52 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class InfoContextAltAttributeListener implements WebSocketCreator, ServletContextListener
{
private static final String ATTR = "alt.config";
@Override
public void contextInitialized(ServletContextEvent sce)
{
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(sce.getServletContext());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), this);
sce.getServletContext().setAttribute(ATTR, configuration);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new InfoSocket();
}
}

View File

@ -0,0 +1,49 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class InfoContextAttributeListener implements WebSocketCreator, ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) sce.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), this);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new InfoSocket();
}
}

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class InfoContextListener implements WebSocketCreator, ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(sce.getServletContext());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), this);
sce.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new InfoSocket();
}
}

View File

@ -0,0 +1,47 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class InfoServlet extends HttpServlet implements WebSocketCreator
{
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new InfoSocket();
}
@Override
public void init(ServletConfig config) throws ServletException
{
ServletContext context = config.getServletContext();
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getAttribute(NativeWebSocketConfiguration.class.getName());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), this);
}
}

View File

@ -18,23 +18,27 @@
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
public class DefaultMappedWebSocketCreator implements MappedWebSocketCreator
@WebSocket
public class InfoSocket
{
private final PathMappings<WebSocketCreator> mappings = new PathMappings<>();
private Session session;
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
@OnWebSocketConnect
public void onConnect(Session session)
{
this.mappings.put(spec, creator);
this.session = session;
}
@Override
public PathMappings<WebSocketCreator> getMappings()
@OnWebSocketMessage
public void onMessage(String msg)
{
return this.mappings;
RemoteEndpoint remote = this.session.getRemote();
remote.sendStringByFuture("session.maxTextMessageSize=" + session.getPolicy().getMaxTextMessageSize());
}
}

View File

@ -0,0 +1,204 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 static org.hamcrest.Matchers.notNullValue;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.toolchain.test.FS;
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.util.resource.Resource;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
import org.junit.Assert;
/**
* Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints.
* <p>
* This is particularly useful when the WebSocket endpoints are discovered via the javax.websocket annotation scanning.
*/
public class WSServer
{
private static final Logger LOG = Log.getLogger(WSServer.class);
private final File contextDir;
private final String contextPath;
private Server server;
private URI serverUri;
private ContextHandlerCollection contexts;
private File webinf;
private File classesDir;
public WSServer(TestingDir testdir, String contextName)
{
this(testdir.getDir(),contextName);
}
public WSServer(File testdir, String contextName)
{
this.contextDir = new File(testdir,contextName);
this.contextPath = "/" + contextName;
FS.ensureEmpty(contextDir);
}
public void copyClass(Class<?> clazz) throws Exception
{
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String endpointPath = clazz.getName().replace('.','/') + ".class";
URL classUrl = cl.getResource(endpointPath);
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() throws MalformedURLException, IOException
{
WebAppContext context = new WebAppContext();
context.setContextPath(this.contextPath);
context.setBaseResource(Resource.newResource(this.contextDir));
context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE);
// @formatter:off
context.setConfigurations(new Configuration[] {
new AnnotationConfiguration(),
new WebXmlConfiguration(),
new WebInfConfiguration(),
new PlusConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration()});
// @formatter:on
return context;
}
public void createWebInf() throws IOException
{
copyWebInf("empty-web.xml");
}
public void deployWebapp(WebAppContext webapp) throws Exception
{
contexts.addHandler(webapp);
contexts.manage(webapp);
webapp.start();
if (LOG.isDebugEnabled())
{
webapp.dump(System.err);
}
}
public void dump()
{
server.dumpStdErr();
}
public URI getServerBaseURI()
{
return serverUri;
}
public Server getServer()
{
return server;
}
public File getWebAppDir()
{
return this.contextDir;
}
public void start() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
HandlerCollection handlers = new HandlerCollection();
contexts = new ContextHandlerCollection();
handlers.addHandler(contexts);
server.setHandler(handlers);
server.start();
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d%s/",host,port,contextPath));
if (LOG.isDebugEnabled())
LOG.debug("Server started on {}",serverUri);
}
public void stop()
{
if (server != null)
{
try
{
server.stop();
}
catch (Exception e)
{
e.printStackTrace(System.err);
}
}
}
}

View File

@ -0,0 +1,314 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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 static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.toolchain.test.EventQueue;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.common.WebSocketFrame;
import org.eclipse.jetty.websocket.common.frames.TextFrame;
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class WebSocketUpgradeFilterTest
{
interface ServerProvider
{
Server newServer() throws Exception;
}
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> data()
{
/**
* Case A:
* 1. embedded-jetty WSUF.configureContext() / app-ws configured at ...
* a. during server construction / before server.start (might not be possible with current impl, native SCI not run (yet))
* might require NativeSCI.getDefaultFrom() first
* b. during server construction / after server.start
* c. during server start / via CustomServlet.init()
* 2. embedded-jetty WSUF addFilter / app-ws configured at server construction (before server.start)
* Case B:
* 1. web.xml WSUF / app-ws configured in CustomServlet.init() load-on-start
* Case C:
* 1. embedded-jetty WSUF.configureContext() / app-ws configured via ServletContextListener.contextInitialized
* 2. embedded-jetty WSUF addFilter / app-ws configured via ServletContextListener.contextInitialized
* Case D:
* 1. web.xml WSUF / app-ws configured via ServletContextListener.contextInitialized
*
* Every "app-ws configured" means it should access/set ws policy and add ws mappings
*/
final WebSocketCreator infoCreator = new WebSocketCreator()
{
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
return new InfoSocket();
}
};
List<Object[]> cases = new ArrayList<>();
// Embedded WSUF.configureContext(), directly app-ws configuration
cases.add(new Object[]{"wsuf.configureContext/Direct configure", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
WebSocketUpgradeFilter wsuf = WebSocketUpgradeFilter.configureContext(context);
// direct configuration via WSUF
wsuf.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
wsuf.addMapping(new ServletPathSpec("/info/*"), infoCreator);
server.start();
return server;
}
}});
// Embedded WSUF.configureContext(), apply app-ws configuration via attribute
cases.add(new Object[]{"wsuf.configureContext/Attribute based configure", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
WebSocketUpgradeFilter.configureContext(context);
// configuration via attribute
NativeWebSocketConfiguration configuration = (NativeWebSocketConfiguration) context.getServletContext().getAttribute(NativeWebSocketConfiguration.class.getName());
assertThat("NativeWebSocketConfiguration", configuration, notNullValue());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
server.start();
return server;
}
}});
// Embedded WSUF, added as filter, apply app-ws configuration via attribute
cases.add(new Object[]{"wsuf/addFilter/Attribute based configure", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
NativeWebSocketConfiguration configuration = new NativeWebSocketConfiguration(context.getServletContext());
configuration.getFactory().getPolicy().setMaxTextMessageSize(10 * 1024 * 1024);
configuration.addMapping(new ServletPathSpec("/info/*"), infoCreator);
context.getServletContext().setAttribute(NativeWebSocketConfiguration.class.getName(), configuration);
server.start();
return server;
}
}});
// Embedded WSUF, added as filter, apply app-ws configuration via ServletContextListener
cases.add(new Object[]{"wsuf.configureContext/ServletContextListener configure", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
context.addFilter(WebSocketUpgradeFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
context.addEventListener(new InfoContextListener());
server.start();
return server;
}
}});
// WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
WSServer server = new WSServer(testDir, "/");
server.copyWebInf("wsuf-config-via-listener.xml");
server.copyClass(InfoSocket.class);
server.copyClass(InfoContextAttributeListener.class);
server.start();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
return server.getServer();
}
}});
// WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
WSServer server = new WSServer(testDir, "/");
server.copyWebInf("wsuf-config-via-servlet-init.xml");
server.copyClass(InfoSocket.class);
server.copyClass(InfoServlet.class);
server.start();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
return server.getServer();
}
}});
// xml based, wsuf, on alternate url-pattern and config attribute location
cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/alt-config", new ServerProvider()
{
@Override
public Server newServer() throws Exception
{
File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml");
WSServer server = new WSServer(testDir, "/");
server.copyWebInf("wsuf-alt-config-via-listener.xml");
server.copyClass(InfoSocket.class);
server.copyClass(InfoContextAltAttributeListener.class);
server.start();
WebAppContext webapp = server.createWebAppContext();
server.deployWebapp(webapp);
return server.getServer();
}
}});
return cases;
}
private final Server server;
private final URI serverUri;
public WebSocketUpgradeFilterTest(String testId, ServerProvider serverProvider) throws Exception
{
this.server = serverProvider.newServer();
ServerConnector connector = (ServerConnector) server.getConnectors()[0];
// Establish the Server URI
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/", host, port));
}
@Test
public void testConfiguration() throws Exception
{
URI destUri = serverUri.resolve("/info/");
try (BlockheadClient client = new BlockheadClient(destUri))
{
client.connect();
client.sendStandardRequest();
client.expectUpgradeResponse();
client.write(new TextFrame().setPayload("hello"));
EventQueue<WebSocketFrame> frames = client.readFrames(1, 1000, TimeUnit.MILLISECONDS);
String payload = frames.poll().getPayloadAsUTF8();
// If we can connect and send a text message, we know that the endpoint was
// added properly, and the response will help us verify the policy configuration too
assertThat("payload", payload, containsString("session.maxTextMessageSize=" + (10 * 1024 * 1024)));
}
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>org.eclipse.jetty.websocket.server.InfoContextAltAttributeListener</listener-class>
</listener>
<filter>
<filter-name>wsuf-alt</filter-name>
<filter-class>org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter</filter-class>
<init-param>
<param-name>configAttributeKey</param-name>
<param-value>alt.config</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>wsuf-alt</filter-name>
<url-pattern>/info/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<listener>
<listener-class>org.eclipse.jetty.websocket.server.InfoContextAttributeListener</listener-class>
</listener>
<filter>
<filter-name>wsuf</filter-name>
<filter-class>org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>wsuf</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>info-servlet</servlet-name>
<servlet-class>org.eclipse.jetty.websocket.server.InfoServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<filter>
<filter-name>wsuf</filter-name>
<filter-class>org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>wsuf</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -90,7 +90,14 @@ public abstract class WebSocketServlet extends HttpServlet
@Override
public void destroy()
{
factory.cleanup();
try
{
factory.stop();
}
catch (Exception ignore)
{
// ignore;
}
}
/**
@ -126,14 +133,13 @@ public abstract class WebSocketServlet extends HttpServlet
{
policy.setInputBufferSize(Integer.parseInt(max));
}
factory = WebSocketServletFactory.Loader.create(policy);
ServletContext ctx = getServletContext();
factory = WebSocketServletFactory.Loader.load(ctx, policy);
configure(factory);
ServletContext ctx = getServletContext();
factory.init(ctx);
factory.start();
ctx.setAttribute(WebSocketServletFactory.class.getName(),factory);
}

View File

@ -19,8 +19,8 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@ -35,77 +35,60 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
*/
public interface WebSocketServletFactory
{
public static class Loader
class Loader
{
private static WebSocketServletFactory INSTANCE;
public static WebSocketServletFactory create(WebSocketPolicy policy) throws ClassNotFoundException, InstantiationException, IllegalAccessException
final static String DEFAULT_IMPL = "org.eclipse.jetty.websocket.server.WebSocketServerFactory";
public static WebSocketServletFactory load(ServletContext ctx, WebSocketPolicy policy)
{
return load().createFactory(policy);
}
public static WebSocketServletFactory load() throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
if (INSTANCE != null)
try
{
return INSTANCE;
Class<? extends WebSocketServletFactory> wsClazz =
(Class<? extends WebSocketServletFactory>) Class.forName(DEFAULT_IMPL);
Constructor<? extends WebSocketServletFactory> ctor = wsClazz.getDeclaredConstructor(new Class<?>[]{ServletContext.class, WebSocketPolicy.class});
return ctor.newInstance(ctx, policy);
}
WebSocketServletFactory baseFactory;
Iterator<WebSocketServletFactory> factories = ServiceLoader.load(WebSocketServletFactory.class).iterator();
if (factories.hasNext())
catch (ClassNotFoundException e)
{
baseFactory = factories.next();
throw new RuntimeException("Unable to load " + DEFAULT_IMPL, e);
}
else
catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e)
{
// 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();
throw new RuntimeException("Unable to instantiate " + DEFAULT_IMPL, e);
}
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);
public abstract WebSocketCreator getCreator();
public abstract ExtensionFactory getExtensionFactory();
boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response) throws IOException;
boolean acceptWebSocket(WebSocketCreator creator, HttpServletRequest request, HttpServletResponse response) throws IOException;
void start() throws Exception;
void stop() throws Exception;
WebSocketCreator getCreator();
ExtensionFactory getExtensionFactory();
/**
* Get the base policy in use for WebSockets.
* <p>
* Note: individual WebSocket implementations can override some of the values in here by using the {@link WebSocket &#064;WebSocket} annotation.
*
*
* @return the base policy
*/
public WebSocketPolicy getPolicy();
public void init(ServletContext servletContext) throws Exception;
public boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response);
WebSocketPolicy getPolicy();
boolean isUpgradeRequest(HttpServletRequest request, HttpServletResponse response);
/**
* Register a websocket class pojo with the default {@link WebSocketCreator}.
* <p>
* Note: only required if using the default {@link WebSocketCreator} provided by this factory.
*
* @param websocketPojo
* the class to instantiate for each incoming websocket upgrade request.
*
* @param websocketPojo the class to instantiate for each incoming websocket upgrade request.
*/
public void register(Class<?> websocketPojo);
public abstract void setCreator(WebSocketCreator creator);
void register(Class<?> websocketPojo);
void setCreator(WebSocketCreator creator);
}