Issue #207 - Support javax.websocket version 1.1

+ More testcase improvements
This commit is contained in:
Joakim Erdfelt 2016-08-25 11:03:42 -07:00
parent 43ef087efd
commit 1a8e79c9bb
20 changed files with 150 additions and 33 deletions

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
@ -64,7 +65,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
out.append("PathMappings[size=").append(Integer.toString(_mappings.size())).append("]\n");
ContainerLifeCycle.dump(out, indent, _mappings);
}
@ManagedAttribute(value = "mappings", readonly = true)
public List<MappedResource<E>> getMappings()
{
@ -206,6 +207,18 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
return pathSpecString.charAt(0) == '^' ? new RegexPathSpec(pathSpecString):new ServletPathSpec(pathSpecString);
}
public E get(PathSpec spec)
{
Optional<E> optionalResource = _mappings.stream()
.filter(mappedResource -> mappedResource.getPathSpec().equals(spec))
.map(mappedResource -> mappedResource.getResource())
.findFirst();
if(!optionalResource.isPresent())
return null;
return optionalResource.get();
}
public boolean put(String pathSpecString, E resource)
{
return put(asPathSpec(pathSpecString),resource);

View File

@ -207,6 +207,27 @@ public class AnnotatedServerEndpointConfig implements ServerEndpointConfig
return userProperties;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnnotatedServerEndpointConfig that = (AnnotatedServerEndpointConfig) o;
if (endpointClass != null ? !endpointClass.equals(that.endpointClass) : that.endpointClass != null)
return false;
return path != null ? path.equals(that.path) : that.path == null;
}
@Override
public int hashCode()
{
int result = endpointClass != null ? endpointClass.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
@Override
public String toString()
{

View File

@ -172,10 +172,28 @@ public class JsrCreator implements WebSocketCreator
return null;
}
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JsrCreator that = (JsrCreator) o;
return baseConfig != null ? baseConfig.equals(that.baseConfig) : that.baseConfig == null;
}
@Override
public int hashCode()
{
int result = (baseConfig != null ? baseConfig.hashCode() : 0);
return result;
}
@Override
public String toString()
{
return String.format("%s[config=%s]",this.getClass().getName(),baseConfig);
return String.format("JsrCreator[%s%s]", (baseConfig instanceof AnnotatedServerEndpointConfig ? "@" : ""), baseConfig.getEndpointClass().getName());
}
}

View File

@ -82,6 +82,33 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
}
}
/**
* Jetty Native approach.
* <p>
* Note: this will add the Upgrade filter to the existing list, with no regard for order. It will just be tacked onto the end of the list.
*
* @param context the servlet context handler
* @return the created websocket server container
* @throws ServletException if unable to create the websocket server container
*/
@SuppressWarnings("Duplicates")
public static ServerContainer configureContext(ServletContextHandler context) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),context.getServer().getThreadPool());
context.addBean(jettyContainer, true);
// 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);
return jettyContainer;
}
/**
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for
* true or false setting that determines if the specified feature is enabled (or not).
@ -91,7 +118,23 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
* @param defValue the default value, if the value is not specified in the context
* @return the value for the feature key
*/
public static boolean isEnabledViaContext(ServletContext context, String keyName, boolean defValue)
@SuppressWarnings("Duplicates")
public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
// Create Filter
WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context);
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = new ServerContainer(filter,filter.getFactory(),jettyContext.getServer().getThreadPool());
jettyContext.addBean(jettyContainer, true);
// 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);
return jettyContainer;
}
private boolean isEnabled(Set<Class<?>> c, ServletContext context)
{
// Try context parameters first
String cp = context.getInitParameter(keyName);
@ -203,19 +246,19 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit
try(ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
{
// Create the Jetty ServerContainer implementation
ServerContainer jettyContainer = configureContext(jettyContext);
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
if (c.isEmpty())
{
if (LOG.isDebugEnabled())
{
LOG.debug("No JSR-356 annotations or interfaces discovered");
}
return;
}
ServerContainer jettyContainer = configureContext(context,jettyContext);
context.addListener(new ContextDestroyListener()); //make sure context is cleaned up when the context stops
// // Establish the DecoratedObjectFactory thread local
// // for various ServiceLoader initiated components to use.
// DecoratedObjectFactory instantiation = (DecoratedObjectFactory)context.getAttribute(DecoratedObjectFactory.ATTR);
// if (instantiation == null)
// {
// LOG.info("Using WebSocket local DecoratedObjectFactory - none found in ServletContext");
// instantiation = new DecoratedObjectFactory();
// }
if (LOG.isDebugEnabled())
{
LOG.debug("Found {} classes",c.size());

View File

@ -1 +1 @@
org.eclipse.jetty.websocket.common.reflect.NameArgIdentifier
org.eclipse.jetty.websocket.jsr356.server.PathParamArgIdentifier

View File

@ -295,7 +295,7 @@ public class EchoTest
System.err.println(testcase);
}
@Test(timeout=2000)
@Test(timeout=10000)
public void testEcho() throws Exception
{
int messageCount = testcase.getMessageCount();
@ -306,7 +306,7 @@ public class EchoTest
{
// Connect
client.connectToServer(socket,toUri);
socket.waitForConnected(2,TimeUnit.SECONDS);
socket.waitForConnected(10,TimeUnit.SECONDS);
// Send Messages
for (Object msg : testcase.messages)

View File

@ -25,7 +25,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/close/reason/session")
public class BasicCloseReasonSessionSocket extends TrackingSocket
{
@OnClose

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/close/reason")
public class BasicCloseReasonSocket extends TrackingSocket
{
@OnClose

View File

@ -25,7 +25,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/close/session/reason")
public class BasicCloseSessionReasonSocket extends TrackingSocket
{
@OnClose

View File

@ -23,7 +23,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value = "/basic")
@ServerEndpoint(value = "/echo/close")
public class BasicCloseSocket extends TrackingSocket
{
@OnClose

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/error/session")
public class BasicErrorSessionSocket extends TrackingSocket
{
@OnError

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/error/session/throwable")
public class BasicErrorSessionThrowableSocket extends TrackingSocket
{
@OnError

View File

@ -23,7 +23,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/error")
public class BasicErrorSocket extends TrackingSocket
{
@OnError

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/error/throwable/session")
public class BasicErrorThrowableSessionSocket extends TrackingSocket
{
@OnError

View File

@ -23,7 +23,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/error/throwable")
public class BasicErrorThrowableSocket extends TrackingSocket
{
@OnError

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/open/session")
public class BasicOpenSessionSocket extends TrackingSocket
{
@OnOpen

View File

@ -23,7 +23,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/open")
public class BasicOpenSocket extends TrackingSocket
{
@OnOpen

View File

@ -24,7 +24,7 @@ import javax.websocket.server.ServerEndpoint;
import org.eclipse.jetty.websocket.jsr356.server.TrackingSocket;
@ServerEndpoint(value="/basic")
@ServerEndpoint(value="/echo/pong")
public class BasicPongMessageSocket extends TrackingSocket
{
@OnMessage

View File

@ -664,8 +664,10 @@ public class CommonEndpointFunctions<T extends Session> extends AbstractLifeCycl
{
assertIsStarted();
// Always set session in endpoint functions
this.session = session;
// Call (optional) on open method
if (onOpenFunction != null)
onOpenFunction.apply(this.session);
}

View File

@ -43,6 +43,8 @@ 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.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@ -132,7 +134,25 @@ public class WebSocketUpgradeFilter implements Filter, MappedWebSocketCreator, D
@Override
public void addMapping(PathSpec spec, WebSocketCreator creator)
{
configuration.addMapping(spec, creator);
WebSocketCreator existingCreator = pathmap.get(spec);
if (existingCreator != null)
{
if(existingCreator.equals(creator))
{
// Entry already exists, don't add it again.
return;
}
StringBuilder err = new StringBuilder();
err.append("Duplicate path mapping for \"");
err.append(spec.getDeclaration());
err.append("\" both ");
err.append(existingCreator);
err.append(" and ");
err.append(creator);
throw new InvalidWebSocketException(err.toString());
}
pathmap.put(spec,creator);
}
/**