JSR-356: adding WebSocketConfiguration and reworking ServerContainer init

This commit is contained in:
Joakim Erdfelt 2013-04-24 14:33:46 -07:00
parent cab4826c08
commit c91c3f2f60
29 changed files with 474 additions and 260 deletions

View File

@ -70,9 +70,12 @@ public class Decoders
Objects.requireNonNull(metadataFactory,"DecoderMetadataFactory cannot be null");
this.metadataFactory = metadataFactory;
for (Class<? extends Decoder> decoder : config.getDecoders())
if (config != null)
{
addAllMetadata(decoder);
for (Class<? extends Decoder> decoder : config.getDecoders())
{
addAllMetadata(decoder);
}
}
}
@ -153,6 +156,14 @@ public class Decoders
throw new InvalidSignatureException("Unable to find appropriate Decoder for type: " + type);
}
public void init(EndpointConfig config)
{
for (DecoderWrapper decoder : decoderMap.values())
{
decoder.getDecoder().init(config);
}
}
public Set<Class<?>> keySet()
{
return decoderMap.keySet();

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// 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;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
/**
* Client {@link ContainerProvider} implementation
*/
public class JettyClientContainerProvider extends ContainerProvider
{
@Override
protected WebSocketContainer getContainer()
{
ClientContainer container = new ClientContainer();
container.start();
return container;
}
}

View File

@ -1,95 +0,0 @@
//
// ========================================================================
// 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;
import java.util.LinkedList;
import java.util.ServiceLoader;
import javax.websocket.ContainerProvider;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class JettyContainerProvider extends ContainerProvider
{
private static final Logger LOG = Log.getLogger(JettyContainerProvider.class);
private final WebSocketContainer websocketContainer;
public JettyContainerProvider()
{
// Holds the list of ContainerService implementations found.
LinkedList<ContainerService> services = new LinkedList<ContainerService>();
for (ContainerService impl : ServiceLoader.load(ContainerService.class))
{
if (impl instanceof ClientContainer)
{
// Sort client impls last.
services.addLast(impl);
}
else
{
// All others first. (such as ServiceContainer impls)
services.addFirst(impl);
}
}
if (services.size() <= 0)
{
LOG.warn("Found no {} in classloader",ContainerService.class.getName());
websocketContainer = null;
return;
}
if (LOG.isDebugEnabled())
{
StringBuilder str = new StringBuilder();
int len = services.size();
str.append("Found ").append(len).append(" websocket container");
if (len > 1)
{
str.append('s');
}
for (int i = 0; i < len; i++)
{
ContainerService service = services.get(i);
str.append("\n [").append(i).append("] ").append(service.getClass().getName());
}
LOG.debug(str.toString());
}
// Use first one (in list)
ContainerService chosen = services.getFirst();
LOG.debug("Using WebSocketContainer: {}",chosen.getClass().getName());
chosen.start();
websocketContainer = chosen;
}
@Override
protected WebSocketContainer getContainer()
{
return websocketContainer;
}
}

View File

@ -23,6 +23,7 @@ import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import org.eclipse.jetty.util.log.Log;
@ -42,20 +43,21 @@ public class MessageHandlers
/**
* Factory for MessageHandlerMetadata instances.
*/
private MessageHandlerMetadataFactory factory;
private final MessageHandlerMetadataFactory factory;
/**
* Array of MessageHandlerWrappers, indexed by {@link MessageType#ordinal()}
*/
private final MessageHandlerWrapper wrappers[];
public MessageHandlers()
public MessageHandlers(MessageHandlerMetadataFactory factory)
{
Objects.requireNonNull(factory,"MessageHandlerMetadataFactory cannot be null");
this.factory = factory;
this.wrappers = new MessageHandlerWrapper[MessageType.values().length];
}
public void add(MessageHandler handler)
{
assertFactoryDefined();
Objects.requireNonNull(handler,"MessageHandler cannot be null");
synchronized (wrappers)
@ -89,19 +91,6 @@ public class MessageHandlers
}
}
private void assertFactoryDefined()
{
if (this.factory == null)
{
throw new IllegalStateException("MessageHandlerMetadataFactory has not been set");
}
}
public MessageHandlerMetadataFactory getFactory()
{
return factory;
}
public Set<MessageHandler> getUnmodifiableHandlerSet()
{
Set<MessageHandler> ret = new HashSet<>();
@ -127,8 +116,6 @@ public class MessageHandlers
public void remove(MessageHandler handler)
{
assertFactoryDefined();
try
{
for (MessageHandlerMetadata metadata : factory.getMetadata(handler.getClass()))
@ -141,9 +128,4 @@ public class MessageHandlers
LOG.warn("Unable to identify MessageHandler: " + handler.getClass().getName(),e);
}
}
public void setFactory(MessageHandlerMetadataFactory factory)
{
this.factory = factory;
}
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.jsr356.annotations;
import java.nio.ByteBuffer;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
@ -37,16 +38,21 @@ public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
@Override
public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
{
Class<?> type = param.type;
// Session parameter (optional)
if (param.type.isAssignableFrom(Session.class))
{
param.bind(Role.SESSION);
return true;
}
if (type.isAssignableFrom(ByteBuffer.class))
if (param.type.isAssignableFrom(ByteBuffer.class))
{
param.bind(Role.MESSAGE_BINARY);
callable.setDecoder(ByteBufferDecoder.INSTANCE);
return true;
}
if (type.isAssignableFrom(byte[].class))
if (param.type.isAssignableFrom(byte[].class))
{
param.bind(Role.MESSAGE_BINARY);
callable.setDecoder(ByteArrayDecoder.INSTANCE);
@ -54,7 +60,7 @@ public class JsrParamIdBinary extends JsrParamIdOnMessage implements IJsrParamId
}
// Boolean (for indicating partial message support)
if (type.isAssignableFrom(Boolean.TYPE))
if (param.type.isAssignableFrom(Boolean.TYPE))
{
param.bind(Role.MESSAGE_PARTIAL_FLAG);
return true;

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.jsr356.annotations;
import javax.websocket.PongMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
@ -31,6 +32,13 @@ public class JsrParamIdPong extends JsrParamIdOnMessage implements IJsrParamId
@Override
public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
{
// Session parameter (optional)
if (param.type.isAssignableFrom(Session.class))
{
param.bind(Role.SESSION);
return true;
}
if (param.type.isAssignableFrom(PongMessage.class))
{
assertPartialMessageSupportDisabled(param,callable);

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.jsr356.annotations;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import org.eclipse.jetty.websocket.common.events.annotated.InvalidSignatureException;
import org.eclipse.jetty.websocket.jsr356.annotations.Param.Role;
@ -42,6 +43,13 @@ public class JsrParamIdText extends JsrParamIdOnMessage implements IJsrParamId
@Override
public boolean process(Param param, JsrCallable callable) throws InvalidSignatureException
{
// Session parameter (optional)
if (param.type.isAssignableFrom(Session.class))
{
param.bind(Role.SESSION);
return true;
}
// String for whole message
if (param.type.isAssignableFrom(String.class))
{

View File

@ -26,12 +26,14 @@ import java.nio.ByteBuffer;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCode;
import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.api.extensions.Frame;
import org.eclipse.jetty.websocket.common.CloseInfo;
@ -39,16 +41,23 @@ import org.eclipse.jetty.websocket.common.WebSocketSession;
import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
import org.eclipse.jetty.websocket.common.events.EventDriver;
import org.eclipse.jetty.websocket.common.message.MessageAppender;
import org.eclipse.jetty.websocket.jsr356.ContainerService;
import org.eclipse.jetty.websocket.jsr356.Decoders;
import org.eclipse.jetty.websocket.jsr356.JsrSession;
import org.eclipse.jetty.websocket.jsr356.MessageHandlers;
import org.eclipse.jetty.websocket.jsr356.MessageType;
import org.eclipse.jetty.websocket.jsr356.messages.BinaryPartialMessage;
import org.eclipse.jetty.websocket.jsr356.messages.BinaryStreamMessage;
import org.eclipse.jetty.websocket.jsr356.messages.BinaryWholeMessage;
import org.eclipse.jetty.websocket.jsr356.messages.MessageHandlerMetadataFactory;
import org.eclipse.jetty.websocket.jsr356.messages.MessageHandlerWrapper;
import org.eclipse.jetty.websocket.jsr356.messages.TextPartialMessage;
import org.eclipse.jetty.websocket.jsr356.messages.TextStreamMessage;
import org.eclipse.jetty.websocket.jsr356.messages.TextWholeMessage;
/**
* EventDriver for websocket that extend from {@link javax.websocket.Endpoint}
*/
public class JsrEndpointEventDriver extends AbstractEventDriver implements EventDriver
{
private static final Logger LOG = Log.getLogger(JsrEndpointEventDriver.class);
@ -82,6 +91,11 @@ public class JsrEndpointEventDriver extends AbstractEventDriver implements Event
if (activeMessage == null)
{
MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.BINARY);
if (wrapper == null)
{
LOG.debug("No BINARY MessageHandler declared");
return;
}
if (wrapper.wantsPartialMessages())
{
activeMessage = new BinaryPartialMessage(wrapper);
@ -170,6 +184,11 @@ public class JsrEndpointEventDriver extends AbstractEventDriver implements Event
if (activeMessage == null)
{
MessageHandlerWrapper wrapper = jsrsession.getMessageHandlerWrapper(MessageType.TEXT);
if (wrapper == null)
{
LOG.debug("No TEXT MessageHandler declared");
return;
}
if (wrapper.wantsPartialMessages())
{
activeMessage = new TextPartialMessage(wrapper);
@ -204,14 +223,29 @@ public class JsrEndpointEventDriver extends AbstractEventDriver implements Event
{
// Cast should be safe, as it was created by JsrSessionFactory
this.jsrsession = (JsrSession)session;
// TODO: Create Decoders (Facade)
// TODO: Create MessageHandlers (Facade)
try
{
// Create Decoders
ContainerService container = (ContainerService)jsrsession.getContainer();
Decoders decoders = new Decoders(container.getDecoderMetadataFactory(),endpointconfig);
jsrsession.setDecodersFacade(decoders);
// Create MessageHandlers
MessageHandlerMetadataFactory metadataFactory = new MessageHandlerMetadataFactory(decoders);
MessageHandlers messageHandlers = new MessageHandlers(metadataFactory);
jsrsession.setMessageHandlerFacade(messageHandlers);
}
catch (DeploymentException e)
{
throw new WebSocketException(e);
}
// Allow end-user socket to adjust configuration
super.openSession(session);
// TODO: Initialize Decoders
// TODO: Initialize MessageHandlers
// Initialize Decoders
jsrsession.getDecodersFacade().init(endpointconfig);
}
public void setEndpointconfig(EndpointConfig endpointconfig)

View File

@ -1 +1 @@
org.eclipse.jetty.websocket.jsr356.JettyContainerProvider
org.eclipse.jetty.websocket.jsr356.JettyClientContainerProvider

View File

@ -27,6 +27,8 @@ import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Assert;
/**
@ -34,6 +36,7 @@ import org.junit.Assert;
*/
public class EndpointEchoClient extends Endpoint
{
private static final Logger LOG = Log.getLogger(EndpointEchoClient.class);
private Session session = null;
private CloseReason close = null;
public EchoCaptureHandler textCapture = new EchoCaptureHandler();
@ -46,6 +49,7 @@ public class EndpointEchoClient extends Endpoint
@Override
public void onOpen(Session session, EndpointConfig config)
{
LOG.debug("onOpen({}, {})",session,config);
this.session = session;
Assert.assertThat("Session",session,notNullValue());
Assert.assertThat("EndpointConfig",config,notNullValue());

View File

@ -50,8 +50,7 @@ public class MessageHandlersTest
@Test
public void testGetBinaryHandler() throws DeploymentException
{
MessageHandlers mhs = new MessageHandlers();
mhs.setFactory(factory);
MessageHandlers mhs = new MessageHandlers(factory);
mhs.add(new ByteBufferPartialHandler());
MessageHandlerWrapper wrapper = mhs.getWrapper(MessageType.BINARY);
Assert.assertThat("Binary Handler",wrapper.getHandler(),instanceOf(ByteBufferPartialHandler.class));
@ -60,8 +59,7 @@ public class MessageHandlersTest
@Test
public void testGetBothHandler() throws DeploymentException
{
MessageHandlers mhs = new MessageHandlers();
mhs.setFactory(factory);
MessageHandlers mhs = new MessageHandlers(factory);
mhs.add(new StringWholeHandler());
mhs.add(new ByteArrayWholeHandler());
MessageHandlerWrapper wrapper = mhs.getWrapper(MessageType.TEXT);
@ -73,25 +71,16 @@ public class MessageHandlersTest
@Test
public void testGetTextHandler() throws DeploymentException
{
MessageHandlers mhs = new MessageHandlers();
mhs.setFactory(factory);
MessageHandlers mhs = new MessageHandlers(factory);
mhs.add(new StringWholeHandler());
MessageHandlerWrapper wrapper = mhs.getWrapper(MessageType.TEXT);
Assert.assertThat("Text Handler",wrapper.getHandler(),instanceOf(StringWholeHandler.class));
}
@Test(expected = IllegalStateException.class)
public void testNoFactoryAdd()
{
MessageHandlers mhs = new MessageHandlers();
mhs.add(new ByteBufferPartialHandler());
}
@Test
public void testReplaceTextHandler() throws DeploymentException
{
MessageHandlers mhs = new MessageHandlers();
mhs.setFactory(factory);
MessageHandlers mhs = new MessageHandlers(factory);
MessageHandler oldText = new StringWholeHandler();
mhs.add(oldText); // add a TEXT handler
mhs.add(new ByteArrayWholeHandler()); // add BINARY handler

View File

@ -46,6 +46,36 @@ public class JettyServerEndpointConfig implements ServerEndpointConfig
private List<Extension> extensions;
private Map<String, Object> userProperties;
public JettyServerEndpointConfig(Class<?> endpointClass, ServerEndpoint anno) throws DeploymentException
{
this(endpointClass,anno.value());
addAll(anno.decoders(),this.decoders);
addAll(anno.encoders(),this.encoders);
addAll(anno.subprotocols(),this.subprotocols);
// no extensions declared in annotation
// no userProperties in annotation
if (anno.configurator() == null)
{
this.configurator = BaseConfigurator.INSTANCE;
}
else
{
try
{
this.configurator = anno.configurator().newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
StringBuilder err = new StringBuilder();
err.append("Unable to instantiate ServerEndpoint.configurator() of ");
err.append(anno.configurator().getName());
err.append(" defined as annotation in ");
err.append(anno.getClass().getName());
throw new DeploymentException(err.toString(),e);
}
}
}
public JettyServerEndpointConfig(Class<?> endpointClass, String path)
{
this.endpointClass = endpointClass;
@ -95,36 +125,6 @@ public class JettyServerEndpointConfig implements ServerEndpointConfig
}
}
public JettyServerEndpointConfig(ServerEndpoint anno) throws DeploymentException
{
this(anno.getClass(),anno.value());
addAll(anno.decoders(),this.decoders);
addAll(anno.encoders(),this.encoders);
addAll(anno.subprotocols(),this.subprotocols);
// no extensions declared in annotation
// no userProperties in annotation
if (anno.configurator() == null)
{
this.configurator = BaseConfigurator.INSTANCE;
}
else
{
try
{
this.configurator = anno.configurator().newInstance();
}
catch (InstantiationException | IllegalAccessException e)
{
StringBuilder err = new StringBuilder();
err.append("Unable to instantiate ServerEndpoint.configurator() of ");
err.append(anno.configurator().getName());
err.append(" defined as annotation in ");
err.append(anno.getClass().getName());
throw new DeploymentException(err.toString(),e);
}
}
}
private <T> void addAll(T[] arr, List<T> lst)
{
if (arr == null)
@ -184,4 +184,26 @@ public class JettyServerEndpointConfig implements ServerEndpointConfig
{
return userProperties;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("JettyServerEndpointConfig [endpointClass=");
builder.append(endpointClass);
builder.append(", path=");
builder.append(path);
builder.append(", configurator=");
builder.append(configurator);
builder.append(", decoders=");
builder.append(decoders);
builder.append(", encoders=");
builder.append(encoders);
builder.append(", subprotocols=");
builder.append(subprotocols);
builder.append(", extensions=");
builder.append(extensions);
builder.append("]");
return builder.toString();
}
}

View File

@ -26,35 +26,56 @@ import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class JettyServerEndpointConfigurator extends Configurator
{
private static final Logger LOG = Log.getLogger(JettyServerEndpointConfigurator.class);
@Override
public boolean checkOrigin(String originHeaderValue)
{
return super.checkOrigin(originHeaderValue);
return true;
}
@Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException
{
return super.getEndpointInstance(endpointClass);
LOG.debug(".getEndpointInstance({})",endpointClass);
try
{
return endpointClass.newInstance();
}
catch (IllegalAccessException e)
{
throw new InstantiationException(String.format("%s: %s",e.getClass().getName(),e.getMessage()));
}
}
@Override
public List<Extension> getNegotiatedExtensions(List<Extension> installed, List<Extension> requested)
{
return super.getNegotiatedExtensions(installed,requested);
/* do nothing */
return null;
}
@Override
public String getNegotiatedSubprotocol(List<String> supported, List<String> requested)
{
return super.getNegotiatedSubprotocol(supported,requested);
for (String possible : requested)
{
if (supported.contains(possible))
{
return possible;
}
}
return null;
}
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response)
{
super.modifyHandshake(sec,request,response);
/* do nothing */
}
}

View File

@ -76,9 +76,10 @@ public class JsrCreator implements WebSocketCreator
// create endpoint class
try
{
return config.getEndpointClass().newInstance();
Class<?> endpointClass = config.getEndpointClass();
return config.getConfigurator().getEndpointInstance(endpointClass);
}
catch (InstantiationException | IllegalAccessException e)
catch (InstantiationException e)
{
LOG.debug("Unable to create websocket: " + config.getEndpointClass().getName(),e);
return null;

View File

@ -50,7 +50,7 @@ public class JsrServerMetadata extends JsrMetadata<ServerEndpoint>
}
this.endpoint = anno;
this.config = new JettyServerEndpointConfig(anno);
this.config = new JettyServerEndpointConfig(websocket,anno);
this.decoders = new Decoders(container.getDecoderMetadataFactory(),config);
}

View File

@ -18,54 +18,37 @@
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.common.events.EventDriverFactory;
import org.eclipse.jetty.websocket.jsr356.ClientContainer;
import org.eclipse.jetty.websocket.jsr356.JsrSessionFactory;
import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEndpointImpl;
import org.eclipse.jetty.websocket.jsr356.server.pathmap.WebSocketPathSpec;
import org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer
public class ServerContainer extends ClientContainer implements javax.websocket.server.ServerContainer, WebSocketServerFactory.Listener
{
private static final Logger LOG = Log.getLogger(ServerContainer.class);
public static ServerContainer getInstance()
public static ServerContainer get(WebAppContext context)
{
return (ServerContainer)ContainerProvider.getWebSocketContainer();
return (ServerContainer)context.getAttribute(WebSocketConfiguration.JAVAX_WEBSOCKET_SERVER_CONTAINER);
}
private ConcurrentHashMap<Class<?>, JsrServerMetadata> endpointServerMetadataCache = new ConcurrentHashMap<>();
private MappedWebSocketCreator mappedCreator;
public ServerContainer()
private final MappedWebSocketCreator mappedCreator;
private WebSocketServerFactory webSocketServletFactory;
private ConcurrentHashMap<Class<?>, JsrServerMetadata> endpointServerMetadataCache = new ConcurrentHashMap<>();
public ServerContainer(MappedWebSocketCreator creator)
{
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");
}
this.mappedCreator = creator;
}
@Override
@ -87,11 +70,6 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
mappedCreator.addMapping(new WebSocketPathSpec(config.getPath()),creator);
}
public void addMappedCreator(MappedWebSocketCreator mappedCreator)
{
this.mappedCreator = mappedCreator;
}
public JsrServerMetadata getServerEndpointMetadata(Class<?> endpointClass) throws DeploymentException
{
JsrServerMetadata basemetadata = endpointServerMetadataCache.get(endpointClass);
@ -105,4 +83,25 @@ public class ServerContainer extends ClientContainer implements javax.websocket.
return basemetadata;
}
public WebSocketServletFactory getWebSocketServletFactory()
{
return webSocketServletFactory;
}
@Override
public void onWebSocketServerFactoryStarted(WebSocketServerFactory factory)
{
this.webSocketServletFactory = factory;
EventDriverFactory eventDriverFactory = this.webSocketServletFactory.getEventDriverFactory();
eventDriverFactory.addImplementation(new JsrServerEndpointImpl(this));
eventDriverFactory.addImplementation(new JsrEndpointImpl());
this.webSocketServletFactory.setSessionFactory(new JsrSessionFactory(this));
}
@Override
public void onWebSocketServerFactoryStopped(WebSocketServerFactory factory)
{
/* do nothing */
}
}

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.websocket.jsr356.server;
import javax.websocket.ContainerProvider;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerEndpoint;
@ -58,7 +57,7 @@ public class ServerEndpointAnnotation extends DiscoveredAnnotation
String path = annotation.value();
LOG.info("Got path: \"{}\"",path);
ServerContainer container = (ServerContainer)ContainerProvider.getWebSocketContainer();
ServerContainer container = ServerContainer.get(_context);
try
{

View File

@ -0,0 +1,71 @@
//
// ========================================================================
// 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.EnumSet;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
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.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter;
/**
* WebSocket Server Configuration component
*/
public class WebSocketConfiguration extends AbstractConfiguration
{
public static final String JAVAX_WEBSOCKET_SERVER_CONTAINER = "javax.websocket.server.ServerContainer";
private static final Logger LOG = Log.getLogger(WebSocketConfiguration.class);
@Override
public void configure(WebAppContext context) throws Exception
{
WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter();
FilterHolder fholder = new FilterHolder(filter);
fholder.setName("Jetty_WebSocketUpgradeFilter");
fholder.setDisplayName("WebSocket Upgrade Filter");
String pathSpec = "/*";
context.addFilter(fholder,pathSpec,EnumSet.of(DispatcherType.REQUEST));
LOG.debug("Adding {} mapped to {} to {}",filter,pathSpec,context);
ServerContainer container = new ServerContainer(filter);
filter.setWebSocketServerFactoryListener(container);
context.setAttribute(JAVAX_WEBSOCKET_SERVER_CONTAINER,container);
}
@Override
public void preConfigure(WebAppContext context) throws Exception
{
// Add the annotation scanning handlers (if annotation scanning enabled)
for (Configuration config : context.getConfigurations())
{
if (config instanceof AnnotationConfiguration)
{
AnnotationConfiguration annocfg = (AnnotationConfiguration)config;
annocfg.addDiscoverableAnnotationHandler(new ServerEndpointAnnotationHandler(context));
}
}
}
}

View File

@ -23,16 +23,8 @@ import java.util.Queue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.toolchain.test.TestingDir;
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.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoSocket;
@ -61,27 +53,15 @@ public class BasicAnnotatedTest
URI uri = wsb.getServerBaseURI();
WebAppContext webapp = wsb.createWebAppContext();
AnnotationConfiguration annocfg = new AnnotationConfiguration();
annocfg.addDiscoverableAnnotationHandler(new ServerEndpointAnnotationHandler(webapp));
// @formatter:off
webapp.setConfigurations(new Configuration[] {
annocfg,
new WebXmlConfiguration(),
new WebInfConfiguration(),
new PlusConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration()});
// @formatter:on
wsb.deployWebapp(webapp);
// wsb.dump();
wsb.dump();
WebSocketClient client = new WebSocketClient();
try
{
client.start();
JettyEchoSocket clientEcho = new JettyEchoSocket();
Future<Session> foo = client.connect(clientEcho,uri.resolve("/echo"));
Future<Session> foo = client.connect(clientEcho,uri.resolve("echo"));
// wait for connect
foo.get(1,TimeUnit.SECONDS);
clientEcho.sendMessage("Hello World");

View File

@ -61,7 +61,6 @@ public class BasicEndpointTest
URI uri = wsb.getServerBaseURI();
WebAppContext webapp = wsb.createWebAppContext();
// default webapp configuration used (no annotation scanning)
wsb.deployWebapp(webapp);
wsb.dump();

View File

@ -0,0 +1,39 @@
//
// ========================================================================
// 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 org.eclipse.jetty.websocket.server.MappedWebSocketCreator;
import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class DummyCreator implements MappedWebSocketCreator
{
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
/* do nothing */
}
@Override
public PathMappings<WebSocketCreator> getMappings()
{
return null;
}
}

View File

@ -46,6 +46,7 @@ import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSessionSocket;
import org.eclipse.jetty.websocket.jsr356.server.samples.BasicOpenSocket;
import org.eclipse.jetty.websocket.jsr356.server.samples.BasicPongMessageSocket;
import org.eclipse.jetty.websocket.jsr356.server.samples.BasicTextMessageStringSocket;
import org.eclipse.jetty.websocket.jsr356.server.samples.StatelessTextMessageStringSocket;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -81,7 +82,7 @@ public class ServerAnnotatedEndpointScanner_GoodSignaturesTest
}
}
private static ServerContainer container = new ServerContainer();
private static ServerContainer container = new ServerContainer(new DummyCreator());
@Parameters
public static Collection<Case[]> data() throws Exception
@ -111,6 +112,7 @@ public class ServerAnnotatedEndpointScanner_GoodSignaturesTest
Case.add(data, BasicErrorThrowableSessionSocket.class, fError, Throwable.class, Session.class);
// -- Text Events
Case.add(data, BasicTextMessageStringSocket.class, fText, String.class);
Case.add(data, StatelessTextMessageStringSocket.class, fText, Session.class, String.class);
// -- Binary Events
Case.add(data, BasicBinaryMessageByteBufferSocket.class, fBinary, ByteBuffer.class);
// -- Pong Events

View File

@ -55,7 +55,7 @@ public class ServerAnnotatedEndpointScanner_InvalidSignaturesTest
{
private static final Logger LOG = Log.getLogger(ServerAnnotatedEndpointScanner_InvalidSignaturesTest.class);
private static ServerContainer container = new ServerContainer();
private static ServerContainer container = new ServerContainer(new DummyCreator());
@Parameters
public static Collection<Class<?>[]> data()

View File

@ -25,6 +25,9 @@ import java.io.IOException;
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;
@ -36,7 +39,12 @@ 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.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;
/**
@ -98,6 +106,19 @@ public class WSServer
WebAppContext context = new WebAppContext();
context.setContextPath(this.contextPath);
context.setWar(this.contextDir.getAbsolutePath());
// @formatter:off
context.setConfigurations(new Configuration[] {
new WebSocketConfiguration(),
new AnnotationConfiguration(),
new WebXmlConfiguration(),
new WebInfConfiguration(),
new PlusConfiguration(),
new MetaInfConfiguration(),
new FragmentConfiguration(),
new EnvConfiguration()});
// @formatter:on
return context;
}
@ -146,7 +167,7 @@ public class WSServer
host = "localhost";
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("ws://%s:%d/",host,port));
serverUri = new URI(String.format("ws://%s:%d%s/",host,port,contextPath));
LOG.debug("Server started on {}",serverUri);
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// 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;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value = "/stateless")
public class StatelessTextMessageStringSocket extends TrackingSocket
{
@OnMessage
public void onText(Session session, String message)
{
addEvent("onText(%s,%s)",session,message);
dataLatch.countDown();
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -62,8 +63,14 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
*/
public class WebSocketServerFactory extends ContainerLifeCycle implements WebSocketCreator, WebSocketServletFactory
{
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
public static interface Listener
{
void onWebSocketServerFactoryStarted(WebSocketServerFactory factory);
void onWebSocketServerFactoryStopped(WebSocketServerFactory factory);
}
private static final Logger LOG = Log.getLogger(WebSocketServerFactory.class);
private static final ThreadLocal<UpgradeContext> ACTIVE_CONTEXT = new ThreadLocal<>();
public static UpgradeContext getActiveUpgradeContext()
@ -81,15 +88,16 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
handshakes.put(HandshakeRFC6455.VERSION,new HandshakeRFC6455());
}
private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
/**
* Have the factory maintain 1 and only 1 scheduler. All connections share this scheduler.
*/
private final Scheduler scheduler = new ScheduledExecutorScheduler();
private final Queue<WebSocketSession> sessions = new ConcurrentLinkedQueue<>();
private final String supportedVersions;
private final WebSocketPolicy basePolicy;
private final EventDriverFactory eventDriverFactory;
private final WebSocketExtensionFactory extensionFactory;
private List<WebSocketServerFactory.Listener> listeners = new ArrayList<>();
private SessionFactory sessionFactory;
private WebSocketCreator creator;
private List<Class<?>> registeredSocketClasses;
@ -177,6 +185,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
return upgrade(sockreq,sockresp,driver);
}
public void addListener(WebSocketServerFactory.Listener listener)
{
listeners.add(listener);
}
@Override
public void cleanup()
{
@ -232,10 +245,24 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
}
}
@Override
protected void doStart() throws Exception
{
for (WebSocketServerFactory.Listener listener : listeners)
{
listener.onWebSocketServerFactoryStarted(this);
}
super.doStart();
}
@Override
protected void doStop() throws Exception
{
closeAllConnections();
for (WebSocketServerFactory.Listener listener : listeners)
{
listener.onWebSocketServerFactoryStopped(this);
}
super.doStop();
}
@ -256,6 +283,11 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
return extensionFactory;
}
public Collection<WebSocketServerFactory.Listener> getListeners()
{
return listeners;
}
@Override
public WebSocketPolicy getPolicy()
{
@ -336,17 +368,17 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc
return protocols;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jetty.websocket.server.WebSocketServletFactory#register(java.lang.Class)
*/
@Override
public void register(Class<?> websocketPojo)
{
registeredSocketClasses.add(websocketPojo);
}
public void removeListener(WebSocketServerFactory.Listener listener)
{
listeners.remove(listener);
}
public boolean sessionClosed(WebSocketSession session)
{
return isRunning() && sessions.remove(session);

View File

@ -41,7 +41,6 @@ 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.
@ -51,7 +50,8 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
{
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
private PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
private WebSocketServletFactory factory;
private WebSocketServerFactory factory;
private WebSocketServerFactory.Listener listener;
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
@ -76,21 +76,26 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
return;
}
if ((request instanceof HttpServletRequest) && (request instanceof HttpServletResponse))
LOG.debug("doFilter({})",request);
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse))
{
HttpServletRequest httpreq = (HttpServletRequest)request;
HttpServletResponse httpresp = (HttpServletResponse)response;
String target = httpreq.getPathInfo();
String target = httpreq.getServletPath();
LOG.debug("target = [{}]",target);
if (factory.isUpgradeRequest(httpreq,httpresp))
{
MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
if (resource == null)
{
LOG.debug("WebSocket Upgrade on {} has no associated endpoint",target);
// no match.
httpresp.sendError(HttpServletResponse.SC_NOT_FOUND,"No websocket endpoint matching path: " + target);
return;
}
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}",target,resource);
WebSocketCreator creator = resource.getResource();
@ -171,7 +176,8 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
policy.setInputBufferSize(Integer.parseInt(max));
}
factory = WebSocketServletFactory.Loader.create(policy);
factory = new WebSocketServerFactory(policy);
factory.addListener(this.listener);
factory.init();
}
catch (Exception x)
@ -180,6 +186,11 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
}
}
public void setWebSocketServerFactoryListener(WebSocketServerFactory.Listener listener)
{
this.listener = listener;
}
@Override
public String toString()
{