Merge remote-tracking branch 'eclipse/jetty-10.0.x' into jetty-10.0.x-3298-completableFutures

This commit is contained in:
Lachlan Roberts 2019-01-30 15:00:01 +11:00
commit 684dcd1693
36 changed files with 1266 additions and 348 deletions

View File

@ -23,7 +23,7 @@ A basic adapter for managing the Session object on the WebSocketListener.
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-common/src/test/java/examples/echo/AdapterEchoSocket.java[]
include::{SRCDIR}/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AdapterEchoSocket.java[]
----
This is a convenience class to make using the WebSocketListener easier, and provides some useful methods to check the state of the Session.

View File

@ -24,7 +24,7 @@ provided by the Jetty WebSocket API.
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-common/src/test/java/examples/echo/AnnotatedEchoSocket.java[]
include::{SRCDIR}/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/AnnotatedEchoSocket.java[]
----
The above example is a simple WebSocket echo endpoint that will echo back any TEXT messages it receives.

View File

@ -23,7 +23,7 @@ The basic form of a WebSocket using the link:{JDURL}/org/eclipse/jetty/websocket
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-common/src/test/java/examples/echo/ListenerEchoSocket.java[]
include::{SRCDIR}/jetty-websocket/jetty-websocket-common/src/test/java/org/eclipse/jetty/websocket/common/endpoints/adapters/ListenerEchoSocket.java[]
----
This is by far the most basic and best performing (speed and memory wise) WebSocket implementation you can create.

View File

@ -38,14 +38,14 @@ To use the WebSocketClient you will need to hook up a WebSocket object instance
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoClient.java[]
include::{SRCDIR}/jetty-websocket/jetty-websocket-client/src/test/java/examples/SimpleEchoClient.java[]
----
The above example connects to a remote WebSocket server and hands off a SimpleEchoSocket to perform the logic on the websocket once connected, waiting for the socket to register that it has closed.
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-client/src/test/java/examples/SimpleEchoSocket.java[]
include::{SRCDIR}/jetty-websocket/jetty-websocket-client/src/test/java/examples/SimpleEchoSocket.java[]
----
When the SimpleEchoSocket connects, it sends 2 Text messages and then closes the socket.

View File

@ -31,7 +31,7 @@ To wire up your WebSocket to a specific path via the WebSocketServlet, you will
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-servlet/src/test/java/examples/MyEchoServlet.java[]
include::{SRCDIR}/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/examples/MyEchoServlet.java[]
----
This example will create a Servlet mapped via the http://docs.oracle.com/javaee/6/api/javax/servlet/annotation/WebServlet.html[@WebServlet] annotation to the Servlet path spec of `"/echo"` (or you can do this manually in the `WEB-INF/web.xml` of your web application) which will create MyEchoSocket instances when encountering HTTP Upgrade requests.
@ -57,7 +57,7 @@ If you have a more complicated creation scenario, you might want to provide your
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoCreator.java[]
include::{SRCDIR}/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/examples/MyAdvancedEchoCreator.java[]
----
Here we show a WebSocketCreator that will utilize the http://tools.ietf.org/html/rfc6455#section-1.9[WebSocket subprotocol] information from request to determine what WebSocket type should be
@ -65,7 +65,7 @@ created.
[source, java, subs="{sub-order}"]
----
include::{SRCDIR}/jetty-websocket/websocket-servlet/src/test/java/examples/MyAdvancedEchoServlet.java[]
include::{SRCDIR}/jetty-websocket/websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/examples/MyAdvancedEchoServlet.java[]
----
When you want a custom WebSocketCreator, use link:{JDURL}/org/eclipse/jetty/websocket/servlet/WebSocketServletFactory.html#setCreator(org.eclipse.jetty.websocket.servlet.WebSocketCreator)[`WebSocketServletFactory.setCreator(WebSocketCreator creator)`] and the WebSocketServletFactory will use your creator for all incoming Upgrade requests on this servlet.

View File

@ -73,20 +73,20 @@
javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional,
org.apache.el;version="[8.0.23,9)";resolution:=optional,
org.apache.el.lang;version="[8.0.23,9)";resolution:=optional,
org.apache.el.stream;version="[8.0.23,9)";resolution:=optional,
org.apache.el.util;version="[8.0.23,9)";resolution:=optional,
org.apache.el.parser;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.compiler;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.compiler.tagplugin;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.runtime;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.security;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.servlet;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.tagplugins.jstl;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.util;version="[8.0.23,9)";resolution:=optional,
org.apache.jasper.xmlparser;version="[8.0.23,9)";resolution:=optional,
org.apache.el;version="[8.0.23,10)";resolution:=optional,
org.apache.el.lang;version="[8.0.23,10)";resolution:=optional,
org.apache.el.stream;version="[8.0.23,10)";resolution:=optional,
org.apache.el.util;version="[8.0.23,10)";resolution:=optional,
org.apache.el.parser;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional,
org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional,
org.apache.taglibs.standard;version="1.2";resolution:=optional,
org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional,
org.apache.taglibs.standard.functions;version="1.2";resolution:=optional,
@ -110,7 +110,7 @@
org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional,
org.apache.taglibs.standard.tei;version="1.2";resolution:=optional,
org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional,
org.apache.tomcat;version="[8.0.23,9)";resolution:=optional,
org.apache.tomcat;version="[8.0.23,10)";resolution:=optional,
org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional,
org.osgi.*,
org.xml.*;resolution:=optional,

View File

@ -616,12 +616,10 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
{
//get the session, if its not in memory, this will load it
Session session = get(id);
//Always delete it from the backing data store
if (_sessionDataStore != null)
{
boolean dsdel = _sessionDataStore.delete(id);
if (LOG.isDebugEnabled()) LOG.debug("Session {} deleted in session data store {}",id, dsdel);
}
@ -635,10 +633,6 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
return doDelete(id);
}
/**
* @see org.eclipse.jetty.server.session.SessionCache#checkExpiration(Set)

View File

@ -116,7 +116,6 @@ public class Session implements SessionHandler.SessionIf
public class SessionInactivityTimer
{
protected final CyclicTimeout _timer;
protected long _msec = -1;
public SessionInactivityTimer()
{
@ -127,31 +126,45 @@ public class Session implements SessionHandler.SessionIf
{
if (LOG.isDebugEnabled())
LOG.debug("Timer expired for session {}", getId());
getSessionHandler().sessionInactivityTimerExpired(Session.this);
long now = System.currentTimeMillis();
//handle what to do with the session after the timer expired
getSessionHandler().sessionInactivityTimerExpired(Session.this, now);
try (Lock lock = Session.this.lock())
{
//grab the lock and check what happened to the session: if it didn't get evicted and
//it hasn't expired, we need to reset the timer
if (Session.this.isResident() && Session.this.getRequests() <= 0 && Session.this.isValid() && !Session.this.isExpiredAt(now))
{
//session wasn't expired or evicted, we need to reset the timer
SessionInactivityTimer.this.schedule(Session.this.calculateInactivityTimeout(now));
}
}
}
};
}
/**
* @param ms the timeout to set; -1 means that the timer will not be
* scheduled
* For backward api compatibility only.
* @see #schedule(long)
*/
public void setTimeout(long ms)
@Deprecated
public void schedule ()
{
_msec = ms;
if (LOG.isDebugEnabled())
LOG.debug("Session {} timer={}ms", getId(), ms);
schedule(calculateInactivityTimeout(System.currentTimeMillis()));
}
public void schedule()
/**
* @param time the timeout to set; -1 means that the timer will not be
* scheduled
*/
public void schedule (long time)
{
if (_msec > 0)
if (time >= 0)
{
if (LOG.isDebugEnabled())
LOG.debug("(Re)starting timer for session {} at {}ms", getId(), _msec);
_timer.schedule(_msec, TimeUnit.MILLISECONDS);
LOG.debug("(Re)starting timer for session {} at {}ms", getId(), time);
_timer.schedule(time, TimeUnit.MILLISECONDS);
}
else
{
@ -279,9 +292,15 @@ public class Session implements SessionHandler.SessionIf
if (LOG.isDebugEnabled())
LOG.debug("Session {} complete, active requests={}", getId(), _requests);
// start the inactivity timer
// start the inactivity timer if necessary
if (_requests == 0)
_sessionInactivityTimer.schedule();
{
//update the expiry time to take account of the time all requests spent inside of the
//session.
long now = System.currentTimeMillis();
_sessionData.calcAndSetExpiry(now);
_sessionInactivityTimer.schedule(calculateInactivityTimeout(now));
}
}
}
@ -513,7 +532,7 @@ public class Session implements SessionHandler.SessionIf
_sessionData.setMaxInactiveMs((long) secs * 1000L);
_sessionData.calcAndSetExpiry();
_sessionData.setDirty(true);
updateInactivityTimer();
if (LOG.isDebugEnabled())
{
if (secs <= 0)
@ -524,14 +543,29 @@ public class Session implements SessionHandler.SessionIf
}
}
/**
* Set the inactivity timer to the smaller of the session maxInactivity (ie
* session-timeout from web.xml), or the inactive eviction time.
*/
public void updateInactivityTimer()
@Deprecated
public void updateInactivityTimer()
{
//for backward api compatibility only
}
/**
* Calculate what the session timer setting should be based on:
* the time remaining before the session expires
* and any idle eviction time configured.
* The timer value will be the lesser of the above.
*
* @param now the time at which to calculate remaining expiry
* @return the time remaining before expiry or inactivity timeout
*/
public long calculateInactivityTimeout (long now)
{
long time = 0;
try (Lock lock = _lock.lock())
{
long remaining = _sessionData.getExpiry() - now;
long maxInactive = _sessionData.getMaxInactiveMs();
int evictionPolicy = getSessionHandler().getSessionCache().getEvictionPolicy();
@ -541,7 +575,7 @@ public class Session implements SessionHandler.SessionIf
if (evictionPolicy < SessionCache.EVICT_ON_INACTIVITY)
{
// we do not want to evict inactive sessions
_sessionInactivityTimer.setTimeout(-1);
time = -1;
if (LOG.isDebugEnabled())
LOG.debug("Session {} is immortal && no inactivity eviction", getId());
}
@ -549,7 +583,7 @@ public class Session implements SessionHandler.SessionIf
{
// sessions are immortal but we want to evict after
// inactivity
_sessionInactivityTimer.setTimeout(TimeUnit.SECONDS.toMillis(evictionPolicy));
time = TimeUnit.SECONDS.toMillis(evictionPolicy);
if (LOG.isDebugEnabled())
LOG.debug("Session {} is immortal; evict after {} sec inactivity", getId(), evictionPolicy);
}
@ -559,31 +593,33 @@ public class Session implements SessionHandler.SessionIf
// sessions are not immortal
if (evictionPolicy == SessionCache.NEVER_EVICT)
{
// timeout is just the maxInactive setting
_sessionInactivityTimer.setTimeout(_sessionData.getMaxInactiveMs());
// timeout is the time remaining until its expiry
time = (remaining > 0 ? remaining : 0);
if (LOG.isDebugEnabled())
LOG.debug("Session {} no eviction", getId());
}
else if (evictionPolicy == SessionCache.EVICT_ON_SESSION_EXIT)
{
// session will not remain in the cache, so no timeout
_sessionInactivityTimer.setTimeout(-1);
time = -1;
if (LOG.isDebugEnabled())
LOG.debug("Session {} evict on exit", getId());
}
else
{
// want to evict on idle: timer is lesser of the session's
// maxInactive and eviction timeout
_sessionInactivityTimer.setTimeout(Math.min(maxInactive, TimeUnit.SECONDS.toMillis(evictionPolicy)));
// expiration remaining and the time to evict
time = (remaining > 0 ? (Math.min(maxInactive, TimeUnit.SECONDS.toMillis(evictionPolicy))) : 0);
if (LOG.isDebugEnabled())
LOG.debug("Session {} timer set to lesser of maxInactive={} and inactivityEvict={}", getId(), maxInactive, evictionPolicy);
}
}
}
return time;
}
/**
* @see javax.servlet.http.HttpSession#getMaxInactiveInterval()
*/
@ -963,16 +999,6 @@ public class Session implements SessionHandler.SessionIf
return _lock.lock();
}
/* ------------------------------------------------------------- */
/**
* Grab the lock on the session if it isn't locked already
*
* @return the lock
*/
public Lock lockIfNotHeld()
{
return _lock.lock();
}
/* ------------------------------------------------------------- */
/**
@ -1132,13 +1158,14 @@ public class Session implements SessionHandler.SessionIf
}
/* ------------------------------------------------------------- */
/**
* @param resident
*/
public void setResident(boolean resident)
{
_resident = resident;
if (_resident)
updateInactivityTimer();
else
if (!_resident)
_sessionInactivityTimer.destroy();
}

View File

@ -1372,6 +1372,17 @@ public class SessionHandler extends ScopedHandler
}
}
/**
* @see #sessionInactivityTimerExpired(Session, long)
*/
@Deprecated
public void sessionInactivityTimerExpired (Session session)
{
//for backwards compilation compatibility only
sessionInactivityTimerExpired(session, System.currentTimeMillis());
}
/* ------------------------------------------------------------ */
/**
* Each session has a timer that is configured to go off
@ -1379,20 +1390,28 @@ public class SessionHandler extends ScopedHandler
* configurable amount of time, or the session itself
* has passed its expiry.
*
* If it has passed its expiry, then we will mark it for
* scavenging by next run of the HouseKeeper; if it has
* been idle longer than the configured eviction period,
* we evict from the cache.
*
* If none of the above are true, then the System timer
* is inconsistent and the caller of this method will
* need to reset the timer.
*
* @param session the session
* @param now the time at which to check for expiry
*/
public void sessionInactivityTimerExpired (Session session)
public void sessionInactivityTimerExpired (Session session, long now)
{
if (session == null)
return;
//check if the session is:
//1. valid
//2. expired
//3. idle
boolean expired = false;
try (Lock lock = session.lockIfNotHeld())
try (Lock lock = session.lock())
{
if (session.getRequests() > 0)
return; //session can't expire or be idle if there is a request in it
@ -1402,27 +1421,27 @@ public class SessionHandler extends ScopedHandler
if (!session.isValid())
return; //do nothing, session is no longer valid
if (session.isExpiredAt(System.currentTimeMillis()) && session.getRequests() <=0)
expired = true;
}
if (expired)
{
//instead of expiring the session directly here, accumulate a list of
//session ids that need to be expired. This is an efficiency measure: as
//the expiration involves the SessionDataStore doing a delete, it is
//most efficient if it can be done as a bulk operation to eg reduce
//roundtrips to the persistent store. Only do this if the HouseKeeper that
//does the scavenging is configured to actually scavenge
if (_sessionIdManager.getSessionHouseKeeper() != null && _sessionIdManager.getSessionHouseKeeper().getIntervalSec() > 0)
if (session.isExpiredAt(now))
{
_candidateSessionIdsForExpiry.add(session.getId());
if (LOG.isDebugEnabled())LOG.debug("Session {} is candidate for expiry", session.getId());
//instead of expiring the session directly here, accumulate a list of
//session ids that need to be expired. This is an efficiency measure: as
//the expiration involves the SessionDataStore doing a delete, it is
//most efficient if it can be done as a bulk operation to eg reduce
//roundtrips to the persistent store. Only do this if the HouseKeeper that
//does the scavenging is configured to actually scavenge
if (_sessionIdManager.getSessionHouseKeeper() != null && _sessionIdManager.getSessionHouseKeeper().getIntervalSec() > 0)
{
_candidateSessionIdsForExpiry.add(session.getId());
if (LOG.isDebugEnabled())LOG.debug("Session {} is candidate for expiry", session.getId());
}
}
else
{
//possibly evict the session
_sessionCache.checkInactiveSession(session);
}
}
else
_sessionCache.checkInactiveSession(session); //if inactivity eviction is enabled the session will be deleted from the cache
}

View File

@ -29,7 +29,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import javax.websocket.CloseReason;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
@ -566,7 +565,10 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
{
// No message sink is active
if (activeMessageSink == null)
{
callback.succeeded();
return;
}
// Accept the payload into the message sink
activeMessageSink.accept(frame, callback);

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.websocket.javax.server;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
@ -29,10 +30,11 @@ import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketException;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer;
import org.eclipse.jetty.websocket.javax.common.InvalidWebSocketException;
import org.eclipse.jetty.websocket.javax.server.internal.AnnotatedServerEndpointConfig;
import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketCreator;
import org.eclipse.jetty.websocket.javax.server.internal.UndefinedServerEndpointConfig;
@ -80,7 +82,7 @@ public class JavaxWebSocketServerContainer
return (javax.websocket.WebSocketContainer)handler.getServletContext().getAttribute("javax.websocket.server.ServerContainer");
}
public static JavaxWebSocketServerContainer ensureContainer(ServletContext servletContext) throws ServletException
public static JavaxWebSocketServerContainer ensureContainer(ServletContext servletContext)
{
ContextHandler contextHandler = ServletContextHandler.getServletContextHandler(servletContext, "Javax Websocket");
if (contextHandler.getServer() == null)
@ -106,7 +108,9 @@ public class JavaxWebSocketServerContainer
// Create the Jetty ServerContainer implementation
container = new JavaxWebSocketServerContainer(
WebSocketMapping.ensureMapping(servletContext), httpClient, executor);
WebSocketMapping.ensureMapping(servletContext, WebSocketMapping.DEFAULT_KEY),
WebSocketComponents.ensureWebSocketComponents(servletContext),
httpClient, executor);
contextHandler.addManaged(container);
contextHandler.addLifeCycleListener(container);
}
@ -116,6 +120,7 @@ public class JavaxWebSocketServerContainer
}
private final WebSocketMapping webSocketMapping;
private final WebSocketComponents webSocketComponents;
private final JavaxWebSocketServerFrameHandlerFactory frameHandlerFactory;
private final Executor executor;
private final FrameHandler.ConfigurationCustomizer customizer = new FrameHandler.ConfigurationCustomizer();
@ -123,12 +128,19 @@ public class JavaxWebSocketServerContainer
private List<Class<?>> deferredEndpointClasses;
private List<ServerEndpointConfig> deferredEndpointConfigs;
public JavaxWebSocketServerContainer(WebSocketMapping webSocketMapping, HttpClient httpClient, Executor executor)
{
this(webSocketMapping, new WebSocketComponents(), httpClient, executor);
}
/**
* Main entry point for {@link JavaxWebSocketServletContainerInitializer}.
* @param webSocketMapping the {@link WebSocketMapping} that this container belongs to
* @param httpClient the {@link HttpClient} instance to use
* @param webSocketComponents the {@link WebSocketComponents} instance to use
* @param httpClient the {@link HttpClient} instance to use
*/
public JavaxWebSocketServerContainer(WebSocketMapping webSocketMapping, HttpClient httpClient, Executor executor)
public JavaxWebSocketServerContainer(WebSocketMapping webSocketMapping, WebSocketComponents webSocketComponents, HttpClient httpClient, Executor executor)
{
super(() ->
{
@ -138,6 +150,7 @@ public class JavaxWebSocketServerContainer
return client;
});
this.webSocketMapping = webSocketMapping;
this.webSocketComponents = webSocketComponents;
this.executor = executor;
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
}
@ -158,7 +171,7 @@ public class JavaxWebSocketServerContainer
@Override
public ByteBufferPool getBufferPool()
{
return this.webSocketMapping.getBufferPool();
return webSocketComponents.getBufferPool();
}
@Override
@ -170,7 +183,7 @@ public class JavaxWebSocketServerContainer
@Override
public WebSocketExtensionRegistry getExtensionRegistry()
{
return this.webSocketMapping.getExtensionRegistry();
return webSocketComponents.getExtensionRegistry();
}
@Override
@ -182,7 +195,7 @@ public class JavaxWebSocketServerContainer
@Override
public DecoratedObjectFactory getObjectFactory()
{
return this.webSocketMapping.getObjectFactory();
return webSocketComponents.getObjectFactory();
}
@Override
@ -225,7 +238,7 @@ public class JavaxWebSocketServerContainer
ServerEndpointConfig config = new AnnotatedServerEndpointConfig(this, endpointClass, anno);
addEndpointMapping(config);
}
catch (InvalidWebSocketException e)
catch (WebSocketException e)
{
throw new DeploymentException("Unable to deploy: " + endpointClass.getName(), e);
}
@ -252,7 +265,15 @@ public class JavaxWebSocketServerContainer
{
LOG.debug("addEndpoint({}) path={} endpoint={}", config, config.getPath(), config.getEndpointClass());
}
addEndpointMapping(config);
try
{
addEndpointMapping(config);
}
catch (WebSocketException e)
{
throw new DeploymentException("Unable to deploy: " + config.getEndpointClass().getName(), e);
}
}
else
{
@ -264,15 +285,15 @@ public class JavaxWebSocketServerContainer
}
}
private void addEndpointMapping(ServerEndpointConfig config)
private void addEndpointMapping(ServerEndpointConfig config) throws WebSocketException
{
frameHandlerFactory.getMetadata(config.getEndpointClass(), config);
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this.webSocketMapping
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, webSocketComponents
.getExtensionRegistry());
this.webSocketMapping
.addMapping(new UriTemplatePathSpec(config.getPath()), creator, frameHandlerFactory, customizer);
PathSpec pathSpec = new UriTemplatePathSpec(config.getPath());
webSocketMapping.addMapping(pathSpec, creator, frameHandlerFactory, customizer);
}
@Override

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.javax.server;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -37,6 +36,7 @@ import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
@ -54,69 +54,61 @@ public class JavaxWebSocketServletContainerInitializer implements ServletContain
*
* @param context the context to search
* @param keyName the key name
* @param defValue the default value, if the value is not specified in the context
* @return the value for the feature key
* @return the value for the feature key, otherwise null if key is not set in context
*/
public static Boolean isEnabledViaContext(ServletContext context, String keyName, Boolean defValue)
private static Boolean isEnabledViaContext(ServletContext context, String keyName)
{
// Try context parameters first
String cp = context.getInitParameter(keyName);
if (cp != null)
{
if (TypeUtil.isTrue(cp))
{
return true;
}
if (TypeUtil.isFalse(cp))
{
else
return false;
}
return defValue;
}
// Next, try attribute on context
Object enable = context.getAttribute(keyName);
if (enable != null)
{
if (TypeUtil.isTrue(enable))
{
return true;
}
if (TypeUtil.isFalse(enable))
{
else
return false;
}
}
return defValue;
return null;
}
public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context)
throws ServletException
{
WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext());
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(context.getServletContext());
WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(context.getServletContext());
FilterHolder filterHolder = WebSocketUpgradeFilter.ensureFilter(context.getServletContext());
WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext(), WebSocketMapping.DEFAULT_KEY);
JavaxWebSocketServerContainer container = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext());
if (LOG.isDebugEnabled())
LOG.debug("configureContext {} {} {}",mapping,upgradeFilter,container);
LOG.debug("configureContext {} {} {} {}", mapping, components, filterHolder, container);
return container;
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext context) throws ServletException
{
Boolean dft = isEnabledViaContext(context, DEPRECATED_ENABLE_KEY, null);
if (dft==null)
dft = Boolean.TRUE;
else
Boolean enableKey = isEnabledViaContext(context, ENABLE_KEY);
Boolean deprecatedEnabledKey = isEnabledViaContext(context, DEPRECATED_ENABLE_KEY);
if (deprecatedEnabledKey != null)
LOG.warn("Deprecated parameter used: " + DEPRECATED_ENABLE_KEY);
if (!isEnabledViaContext(context, ENABLE_KEY, dft))
boolean websocketEnabled = true;
if (enableKey != null)
websocketEnabled = enableKey;
else if (deprecatedEnabledKey != null)
websocketEnabled = deprecatedEnabledKey;
if (!websocketEnabled)
{
LOG.info("Javax Websocket is disabled by configuration for context {}", context.getContextPath());
return;

View File

@ -168,8 +168,7 @@ public class JavaxWebSocketCreator implements WebSocketCreator
}
catch (InstantiationException e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to create websocket: " + config.getEndpointClass().getName(), e);
LOG.warn("Unable to create websocket: " + config.getEndpointClass().getName(), e);
return null;
}
}

View File

@ -18,6 +18,20 @@
package org.eclipse.jetty.websocket.javax.tests.server;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.DeploymentException;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.Callback;
@ -34,19 +48,6 @@ import org.eclipse.jetty.websocket.javax.tests.framehandlers.FrameHandlerTracker
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.websocket.DeploymentException;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@ -62,6 +63,7 @@ public class EndpointViaConfigTest
@ServerEndpoint("/echo")
public static class BasicEchoEndpoint extends WSEventTracker implements MessageHandler.Whole<String>
{
@Override
public void onMessage(String msg)
{
super.onWsText(msg);

View File

@ -18,25 +18,17 @@
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import java.util.Collections;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
/**
* ServletContext configuration for Jetty Native WebSockets API.
@ -80,11 +72,10 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain
@Override
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException
{
WebSocketMapping mapping = WebSocketMapping.ensureMapping(servletContext);
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(servletContext);
WebSocketComponents components = WebSocketComponents.ensureWebSocketComponents(servletContext);
JettyServerFrameHandlerFactory factory = JettyServerFrameHandlerFactory.ensureFactory(servletContext);
if (LOG.isDebugEnabled())
LOG.debug("onStartup {} {} {}",mapping, upgradeFilter, factory);
LOG.debug("onStartup {} {}", components, factory);
}
}

View File

@ -25,7 +25,6 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -148,10 +147,10 @@ public class BrowserDebugTool
factory.addMapping(new ServletPathSpec("/"), new BrowserSocketCreator());
// Set the timeout
factory.setDefaultIdleTimeout(Duration.ofSeconds(30));
factory.setIdleTimeout(Duration.ofSeconds(30));
// Set top end message size
factory.setDefaultMaxTextMessageSize(15 * 1024 * 1024);
factory.setMaxTextMessageSize(15 * 1024 * 1024);
}
@Override

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-parent</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jetty-websocket-tests</artifactId>
<name>Jetty :: Websocket :: org.eclipse.jetty.websocket :: Tests</name>
<properties>
<bundle-symbolic-name>${project.groupId}.jetty.tests</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>ban-java-servlet-api</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<includes>
<include>javax.servlet</include>
<include>servletapi</include>
<include>org.eclipse.jetty.orbit:javax.servlet</include>
<include>org.mortbay.jetty:servlet-api</include>
<include>jetty:servlet-api</include>
</includes>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,146 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests;
import java.net.URI;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class JettyWebsocketTest
{
@WebSocket
public static class EventSocket
{
CountDownLatch closed = new CountDownLatch(1);
String behavior;
@OnWebSocketConnect
public void onOpen(Session sess)
{
behavior = sess.getPolicy().getBehavior().name();
System.err.println(toString() + " Socket Connected: " + sess);
}
@OnWebSocketMessage
public void onMessage(String message)
{
System.err.println(toString() + " Received TEXT message: " + message);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
System.err.println(toString() + " Socket Closed: " + statusCode + ":" + reason);
closed.countDown();
}
@OnWebSocketError
public void onError(Throwable cause)
{
cause.printStackTrace(System.err);
}
@Override
public String toString()
{
return String.format("[%s@%s]", behavior, Integer.toHexString(hashCode()));
}
}
public static class MyWebSocketServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
factory.addMapping("/",(req, resp)->new EventSocket());
}
}
@Test
public void test() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
server.setHandler(contextHandler);
contextHandler.addServlet(MyWebSocketServlet.class, "/testPath1");
contextHandler.addServlet(MyWebSocketServlet.class, "/testPath2");
try
{
JettyWebSocketServletContainerInitializer.configure(contextHandler);
server.start();
WebSocketClient client = new WebSocketClient();
client.start();
URI uri = URI.create("ws://localhost:8080/testPath1");
EventSocket socket = new EventSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
try(Session session = connect.get(5, TimeUnit.SECONDS))
{
session.getRemote().sendString("hello world");
}
assertTrue(socket.closed.await(10, TimeUnit.SECONDS));
uri = URI.create("ws://localhost:8080/testPath2");
socket = new EventSocket();
connect = client.connect(socket, uri);
try(Session session = connect.get(5, TimeUnit.SECONDS))
{
session.getRemote().sendString("hello world");
}
assertTrue(socket.closed.await(10, TimeUnit.SECONDS));
server.stop();
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}

View File

@ -0,0 +1,226 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests;
import java.net.URI;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.UserStore;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.tests.examples.MyAdvancedEchoServlet;
import org.eclipse.jetty.websocket.tests.examples.MyAuthedServlet;
import org.eclipse.jetty.websocket.tests.examples.MyEchoServlet;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class WebSocketServletExamplesTest
{
@WebSocket
public static class ClientSocket
{
CountDownLatch closed = new CountDownLatch(1);
ArrayBlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(2);
@OnWebSocketConnect
public void onOpen(Session sess)
{
System.err.println("ClientSocket Connected: " + sess);
}
@OnWebSocketMessage
public void onMessage(String message)
{
messageQueue.offer(message);
System.err.println("Received TEXT message: " + message);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
System.err.println("ClientSocket Closed: " + statusCode + ":" + reason);
closed.countDown();
}
@OnWebSocketError
public void onError(Throwable cause)
{
cause.printStackTrace(System.err);
}
}
static Server _server;
static ServletContextHandler _context;
@BeforeAll
public static void setup() throws Exception
{
_server = new Server();
ServerConnector connector = new ServerConnector(_server);
connector.setPort(8080);
_server.addConnector(connector);
_context = new ServletContextHandler(ServletContextHandler.SESSIONS);
_context.setContextPath("/");
_context.setSecurityHandler(getSecurityHandler("user", "password", "testRealm"));
_server.setHandler(_context);
_context.addServlet(MyEchoServlet.class, "/echo");
_context.addServlet(MyAdvancedEchoServlet.class, "/advancedEcho");
_context.addServlet(MyAuthedServlet.class, "/authed");
JettyWebSocketServletContainerInitializer.configure(_context);
_server.start();
}
@AfterAll
public static void stop() throws Exception
{
_server.stop();
}
private static SecurityHandler getSecurityHandler(String username, String password, String realm) {
HashLoginService loginService = new HashLoginService();
UserStore userStore = new UserStore();
userStore.addUser(username, Credential.getCredential(password), new String[] {"websocket"});
loginService.setUserStore(userStore);
loginService.setName(realm);
Constraint constraint = new Constraint();
constraint.setName("auth");
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{"**"});
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/authed/*");
mapping.setConstraint(constraint);
ConstraintSecurityHandler security = new ConstraintSecurityHandler();
security.addConstraintMapping(mapping);
security.setAuthenticator(new BasicAuthenticator());
security.setLoginService(loginService);
return security;
}
@Test
public void testEchoServlet() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
URI uri = URI.create("ws://localhost:8080/echo");
ClientSocket socket = new ClientSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
String message = "hello world";
session.getRemote().sendString(message);
String response = socket.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat(response, is(message));
}
assertTrue(socket.closed.await(10, TimeUnit.SECONDS));
}
@Test
public void testAdvancedEchoServlet() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
URI uri = URI.create("ws://localhost:8080/advancedEcho");
ClientSocket socket = new ClientSocket();
UpgradeRequest upgradeRequest = new ClientUpgradeRequest();
upgradeRequest.setSubProtocols("text");
CompletableFuture<Session> connect = client.connect(socket, uri, upgradeRequest);
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
String message = "hello world";
session.getRemote().sendString(message);
String response = socket.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat(response, is(message));
}
assertTrue(socket.closed.await(10, TimeUnit.SECONDS));
}
@Test
public void testAuthedServlet() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
AuthenticationStore authenticationStore = client.getHttpClient().getAuthenticationStore();
URI uri = URI.create("ws://localhost:8080/authed");
BasicAuthentication basicAuthentication = new BasicAuthentication(uri, "testRealm", "user", "password");
authenticationStore.addAuthentication(basicAuthentication);
ClientSocket socket = new ClientSocket();
CompletableFuture<Session> connect = client.connect(socket, uri);
try (Session session = connect.get(5, TimeUnit.SECONDS))
{
String message = "hello world";
session.getRemote().sendString(message);
String response = socket.messageQueue.poll(5, TimeUnit.SECONDS);
assertThat(response, is(message));
}
assertTrue(socket.closed.await(10, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,57 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class MyAdvancedEchoCreator implements WebSocketCreator
{
private MyBinaryEchoSocket binaryEcho;
private MyEchoSocket textEcho;
public MyAdvancedEchoCreator()
{
// Create the reusable sockets
this.binaryEcho = new MyBinaryEchoSocket();
this.textEcho = new MyEchoSocket();
}
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
for (String subprotocol : req.getSubProtocols())
{
if ("binary".equals(subprotocol))
{
resp.setAcceptedSubProtocol(subprotocol);
return binaryEcho;
}
if ("text".equals(subprotocol))
{
resp.setAcceptedSubProtocol(subprotocol);
return textEcho;
}
}
// No valid subprotocol in request, ignore the request
return null;
}
}

View File

@ -0,0 +1,41 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import java.time.Duration;
import javax.servlet.annotation.WebServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@SuppressWarnings("serial")
@WebServlet(name = "MyAdvanced Echo WebSocket Servlet", urlPatterns = { "/advecho" })
public class MyAdvancedEchoServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
// set a 10 second timeout
factory.setIdleTimeout(Duration.ofSeconds(10));
// set a custom WebSocket creator
factory.setCreator(new MyAdvancedEchoCreator());
}
}

View File

@ -0,0 +1,60 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import java.io.IOException;
import java.security.Principal;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
public class MyAuthedCreator implements WebSocketCreator
{
@Override
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp)
{
try
{
// Is Authenticated?
Principal principal = req.getUserPrincipal();
if (principal == null)
{
resp.sendForbidden("Not authenticated yet");
return null;
}
// Is Authorized?
if (!req.isUserInRole("websocket"))
{
resp.sendForbidden("Not authenticated yet");
return null;
}
// Return websocket
return new MyEchoSocket();
}
catch (IOException e)
{
e.printStackTrace(System.err);
}
// no websocket
return null;
}
}

View File

@ -0,0 +1,32 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@SuppressWarnings("serial")
public class MyAuthedServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
factory.setCreator(new MyAuthedCreator());
}
}

View File

@ -0,0 +1,39 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import java.nio.ByteBuffer;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
/**
* Echo BINARY messages
*/
@WebSocket
public class MyBinaryEchoSocket
{
@OnWebSocketMessage
public void onWebSocketText(Session session, byte buf[], int offset, int len)
{
// Echo message back, asynchronously
session.getRemote().sendBytes(ByteBuffer.wrap(buf,offset,len), null);
}
}

View File

@ -0,0 +1,41 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import java.time.Duration;
import javax.servlet.annotation.WebServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@SuppressWarnings("serial")
@WebServlet(name = "MyEcho WebSocket Servlet", urlPatterns = { "/echo" })
public class MyEchoServlet extends WebSocketServlet
{
@Override
public void configure(WebSocketServletFactory factory)
{
// set a 10 second timeout
factory.setIdleTimeout(Duration.ofSeconds(10));
// register MyEchoSocket as the WebSocket to create on Upgrade
factory.register(MyEchoSocket.class);
}
}

View File

@ -0,0 +1,37 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.tests.examples;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
/**
* Example WebSocket, simple echo
*/
@WebSocket
public class MyEchoSocket
{
@OnWebSocketMessage
public void onWebSocketText(Session session, String message)
{
// Echo message back, asynchronously
session.getRemote().sendString(message, null);
}
}

View File

@ -0,0 +1,43 @@
#
#
# ========================================================================
# Copyright (c) 1995-2017 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.
# ========================================================================
#
#
# org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.util.log.stderr.LONG=true
# org.eclipse.jetty.server.AbstractConnector.LEVEL=DEBUG
# org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG
# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG
# org.eclipse.jetty.client.LEVEL=DEBUG
# org.eclipse.jetty.io.LEVEL=DEBUG
# org.eclipse.jetty.io.ManagedSelector.LEVEL=INFO
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.core.internal.WebSocketChannel.LEVEL=DEBUG
# org.eclipse.jetty.websocket.jsr356.tests.LEVEL=DEBUG
# org.eclipse.jetty.websocket.LEVEL=INFO
# org.eclipse.jetty.websocket.jsr356.messages.LEVEL=DEBUG
# org.eclipse.jetty.websocket.tests.LEVEL=DEBUG
# org.eclipse.jetty.websocket.tests.client.LEVEL=DEBUG
# org.eclipse.jetty.websocket.tests.client.jsr356.LEVEL=DEBUG
# org.eclipse.jetty.websocket.tests.server.LEVEL=DEBUG
# org.eclipse.jetty.websocket.tests.server.jsr356.LEVEL=DEBUG
### Showing any unintended (ignored) errors from CompletionCallback
# org.eclipse.jetty.websocket.common.CompletionCallback.LEVEL=ALL
### Disabling intentional error out of RFCSocket
org.eclipse.jetty.websocket.tests.server.RFCSocket.LEVEL=OFF

View File

@ -22,6 +22,7 @@
<module>jetty-websocket-common</module>
<module>jetty-websocket-client</module>
<module>jetty-websocket-server</module>
<module>jetty-websocket-tests</module>
<!-- Javax WebSocket Implementation -->
<module>javax-websocket-common</module>
<module>javax-websocket-client</module>

View File

@ -0,0 +1,83 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.core;
import javax.servlet.ServletContext;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
/**
* A collection of components which are the resources needed for websockets such as
* {@link ByteBufferPool}, {@link WebSocketExtensionRegistry}, and {@link DecoratedObjectFactory}.
*
* These components should be accessed through {@link WebSocketComponents#ensureWebSocketComponents} so that
* the instance can be shared by being stored as a bean on the ContextHandler.
*/
public class WebSocketComponents
{
public static WebSocketComponents ensureWebSocketComponents(ServletContext servletContext)
{
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
// Ensure a mapping exists
WebSocketComponents components = contextHandler.getBean(WebSocketComponents.class);
if (components == null)
{
components = new WebSocketComponents();
contextHandler.addBean(components);
}
return components;
}
public WebSocketComponents()
{
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
}
public WebSocketComponents(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
{
this.extensionRegistry = extensionRegistry;
this.objectFactory = objectFactory;
this.bufferPool = bufferPool;
}
private DecoratedObjectFactory objectFactory;
private WebSocketExtensionRegistry extensionRegistry;
private ByteBufferPool bufferPool;
public ByteBufferPool getBufferPool()
{
return bufferPool;
}
public WebSocketExtensionRegistry getExtensionRegistry()
{
return extensionRegistry;
}
public DecoratedObjectFactory getObjectFactory()
{
return objectFactory;
}
}

View File

@ -31,6 +31,12 @@
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-api</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -23,7 +23,6 @@ import java.net.URISyntaxException;
import java.util.function.Consumer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -33,17 +32,15 @@ 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.http.pathmap.UriTemplatePathSpec;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketException;
import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.Negotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
@ -64,41 +61,42 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
{
private static final Logger LOG = Log.getLogger(WebSocketMapping.class);
public static WebSocketMapping ensureMapping(ServletContext servletContext) throws ServletException
public static WebSocketMapping ensureMapping(ServletContext servletContext, String mappingKey)
{
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
// Ensure a mapping exists
WebSocketMapping mapping = contextHandler.getBean(WebSocketMapping.class);
if (mapping == null)
Object mappingObject = contextHandler.getAttribute(mappingKey);
if (mappingObject!=null)
{
mapping = new WebSocketMapping();
mapping.setContextClassLoader(servletContext.getClassLoader());
contextHandler.addBean(mapping);
contextHandler.addLifeCycleListener(mapping);
if (WebSocketMapping.class.isInstance(mappingObject))
return (WebSocketMapping)mappingObject;
else
throw new IllegalStateException(
String.format("ContextHandler attribute %s is not of type WebSocketMapping: {%s}",
mappingKey, mappingObject.toString()));
}
else
{
WebSocketMapping mapping = new WebSocketMapping(WebSocketComponents.ensureWebSocketComponents(servletContext));
contextHandler.setAttribute(mappingKey, mapping);
return mapping;
}
return mapping;
}
private final PathMappings<Negotiator> mappings = new PathMappings<>();
private final Handshaker handshaker = Handshaker.newInstance();
public static final String DEFAULT_KEY = "org.eclipse.jetty.websocket.servlet.WebSocketMapping";
private DecoratedObjectFactory objectFactory;
private ClassLoader contextClassLoader;
private WebSocketExtensionRegistry extensionRegistry;
private ByteBufferPool bufferPool;
private final PathMappings<Negotiator> mappings = new PathMappings<>();
private final WebSocketComponents components;
private final Handshaker handshaker = Handshaker.newInstance();
public WebSocketMapping()
{
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
this(new WebSocketComponents());
}
public WebSocketMapping(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
public WebSocketMapping(WebSocketComponents components)
{
this.extensionRegistry = extensionRegistry;
this.objectFactory = objectFactory;
this.bufferPool = bufferPool;
this.components = components;
}
@Override
@ -127,10 +125,12 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
* @param customizer the customizer to use to customize the WebSocket session.
*/
public void addMapping(PathSpec pathSpec, WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
throws WebSocketException
{
// Handling for response forbidden (and similar paths)
// no creation, sorry
// No factory worked!
// TODO evaluate why this can't be done
//if (getMapping(pathSpec) != null)
// throw new WebSocketException("Duplicate WebSocket Mapping for PathSpec");
mappings.put(pathSpec, new Negotiator(creator, factory, customizer));
}
@ -157,31 +157,6 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
Dumpable.dumpObjects(out, indent, this, mappings);
}
public ByteBufferPool getBufferPool()
{
return bufferPool;
}
public void setContextClassLoader(ClassLoader classLoader)
{
this.contextClassLoader = classLoader;
}
public ClassLoader getContextClassloader()
{
return contextClassLoader;
}
public WebSocketExtensionRegistry getExtensionRegistry()
{
return this.extensionRegistry;
}
public DecoratedObjectFactory getObjectFactory()
{
return this.objectFactory;
}
/**
* Get the matching {@link MappedResource} for the provided target.
*
@ -280,9 +255,7 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
public Negotiator(WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
{
super(WebSocketMapping.this.getExtensionRegistry(), WebSocketMapping.this.getObjectFactory(),
WebSocketMapping.this.getBufferPool(),
customizer);
super(components.getExtensionRegistry(), components.getObjectFactory(), components.getBufferPool(), customizer);
this.creator = creator;
this.factory = factory;
}
@ -296,10 +269,16 @@ public class WebSocketMapping implements Dumpable, LifeCycle.Listener
@Override
public FrameHandler negotiate(Negotiation negotiation)
{
ServletContext servletContext = negotiation.getRequest().getServletContext();
if (servletContext == null)
throw new IllegalStateException("null servletContext from request");
ClassLoader loader = servletContext.getClassLoader();
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(getContextClassloader());
Thread.currentThread().setContextClassLoader(loader);
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);

View File

@ -19,8 +19,8 @@
package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.time.Duration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -33,6 +33,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
/**
@ -94,6 +95,7 @@ public abstract class WebSocketServlet extends HttpServlet
private final CustomizedWebSocketServletFactory customizer = new CustomizedWebSocketServletFactory();
private WebSocketMapping mapping;
private WebSocketComponents components;
/**
* Configure the WebSocketServletFactory for this servlet instance by setting default
@ -111,7 +113,8 @@ public abstract class WebSocketServlet extends HttpServlet
{
ServletContext servletContext = getServletContext();
mapping = WebSocketMapping.ensureMapping(servletContext);
components = WebSocketComponents.ensureWebSocketComponents(servletContext);
mapping = new WebSocketMapping(components);
String max = getInitParameter("maxIdleTime");
if (max != null)
@ -176,79 +179,7 @@ public abstract class WebSocketServlet extends HttpServlet
{
public WebSocketExtensionRegistry getExtensionRegistry()
{
return mapping.getExtensionRegistry();
}
@Override
public Duration getDefaultIdleTimeout()
{
return getIdleTimeout();
}
@Override
public void setDefaultIdleTimeout(Duration duration)
{
setIdleTimeout(duration);
}
@Override
public int getDefaultInputBufferSize()
{
return getInputBufferSize();
}
@Override
public void setDefaultInputBufferSize(int bufferSize)
{
setInputBufferSize(bufferSize);
}
@Override
public long getDefaultMaxAllowedFrameSize()
{
return getMaxFrameSize();
}
@Override
public void setDefaultMaxAllowedFrameSize(long maxFrameSize)
{
setMaxFrameSize(maxFrameSize);
}
@Override
public long getDefaultMaxBinaryMessageSize()
{
return getMaxBinaryMessageSize();
}
@Override
public void setDefaultMaxBinaryMessageSize(long size)
{
setMaxBinaryMessageSize(size);
}
@Override
public long getDefaultMaxTextMessageSize()
{
return getMaxTextMessageSize();
}
@Override
public void setDefaultMaxTextMessageSize(long size)
{
setMaxTextMessageSize(size);
}
@Override
public int getDefaultOutputBufferSize()
{
return getOutputBufferSize();
}
@Override
public void setDefaultOutputBufferSize(int bufferSize)
{
setOutputBufferSize(bufferSize);
return components.getExtensionRegistry();
}
@Override
@ -271,6 +202,41 @@ public abstract class WebSocketServlet extends HttpServlet
mapping.addMapping(pathSpec, creator, frameHandlerFactory, this);
}
@Override
public void register(Class<?> endpointClass)
{
Constructor<?> constructor;
try
{
constructor = endpointClass.getDeclaredConstructor(null);
}
catch (NoSuchMethodException e)
{
throw new RuntimeException(e);
}
WebSocketCreator creator = (req, resp) ->
{
try
{
return constructor.newInstance();
}
catch (Throwable t)
{
t.printStackTrace();
return null;
}
};
addMapping("/", creator);
}
@Override
public void setCreator(WebSocketCreator creator)
{
addMapping("/", creator);
}
@Override
public WebSocketCreator getMapping(PathSpec pathSpec)
{

View File

@ -18,39 +18,39 @@
package org.eclipse.jetty.websocket.servlet;
import java.time.Duration;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.time.Duration;
public interface WebSocketServletFactory
{
WebSocketExtensionRegistry getExtensionRegistry();
Duration getDefaultIdleTimeout();
Duration getIdleTimeout();
void setDefaultIdleTimeout(Duration duration);
void setIdleTimeout(Duration duration);
int getDefaultInputBufferSize();
int getInputBufferSize();
void setDefaultInputBufferSize(int bufferSize);
void setInputBufferSize(int bufferSize);
long getDefaultMaxAllowedFrameSize();
long getMaxFrameSize();
void setDefaultMaxAllowedFrameSize(long maxFrameSize);
void setMaxFrameSize(long maxFrameSize);
long getDefaultMaxBinaryMessageSize();
long getMaxBinaryMessageSize();
void setDefaultMaxBinaryMessageSize(long bufferSize);
void setMaxBinaryMessageSize(long bufferSize);
long getDefaultMaxTextMessageSize();
long getMaxTextMessageSize();
void setDefaultMaxTextMessageSize(long bufferSize);
void setMaxTextMessageSize(long bufferSize);
int getDefaultOutputBufferSize();
int getOutputBufferSize();
void setDefaultOutputBufferSize(int bufferSize);
void setOutputBufferSize(int bufferSize);
boolean isAutoFragment();
@ -72,6 +72,20 @@ public interface WebSocketServletFactory
*/
void addMapping(PathSpec pathSpec, WebSocketCreator creator);
/**
* Add a WebSocket mapping at PathSpec "/" for a creator which creates the endpointClass
*
* @param endpointClass the WebSocket class to use
*/
void register(Class<?> endpointClass);
/**
* Add a WebSocket mapping at PathSpec "/" for a creator
*
* @param creator the WebSocketCreator to use
*/
void setCreator(WebSocketCreator creator);
/**
* Returns the creator for the given path spec.
*

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.time.Duration;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@ -33,7 +32,6 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHandler;
@ -43,8 +41,7 @@ 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.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
/**
* Inline Servlet Filter to capture WebSocket upgrade requests.
@ -78,38 +75,46 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
{
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
public static FilterHolder ensureFilter(ServletContext servletContext) throws ServletException
private static FilterHolder getFilter(ServletContext servletContext)
{
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
for (FilterHolder holder : servletHandler.getFilters())
{
if (holder.getClassName().equals(WebSocketUpgradeFilter.class.getName()))
return holder;
if (holder.getHeldClass()!=null && WebSocketUpgradeFilter.class.isAssignableFrom(holder.getHeldClass()))
if (holder.getInitParameter(MAPPING_INIT_PARAM) != null)
return holder;
}
return null;
}
public static FilterHolder ensureFilter(ServletContext servletContext)
{
FilterHolder existingFilter = WebSocketUpgradeFilter.getFilter(servletContext);
if (existingFilter != null)
return existingFilter;
String name = "WebSocketUpgradeFilter";
String pathSpec = "/*";
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
holder.setName(name);
holder.setInitParameter(MAPPING_INIT_PARAM, WebSocketMapping.DEFAULT_KEY);
holder.setAsyncSupported(true);
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
servletHandler.addFilterWithMapping(holder, pathSpec, dispatcherTypes);
if (LOG.isDebugEnabled())
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
return holder;
}
public final static String MAPPING_INIT_PARAM = "org.eclipse.jetty.websocket.servlet.WebSocketMapping.key";
private final FrameHandler.ConfigurationCustomizer defaultCustomizer = new FrameHandler.ConfigurationCustomizer();
private WebSocketMapping mapping;
public WebSocketUpgradeFilter()
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
@ -151,7 +156,12 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
public void init(FilterConfig config) throws ServletException
{
final ServletContext context = config.getServletContext();
mapping = WebSocketMapping.ensureMapping(context);
String mappingKey = config.getInitParameter(MAPPING_INIT_PARAM);
if (mappingKey != null)
mapping = WebSocketMapping.ensureMapping(context, mappingKey);
else
mapping = new WebSocketMapping(WebSocketComponents.ensureWebSocketComponents(context));
String max = config.getInitParameter("maxIdleTime");
if (max != null)

View File

@ -18,6 +18,19 @@
package org.eclipse.jetty.websocket.servlet.internal;
import java.io.BufferedReader;
import java.net.InetSocketAddress;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
@ -31,18 +44,10 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
import java.io.BufferedReader;
import java.net.InetSocketAddress;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
/**
* An immutable, feature limited, HttpServletRequest that will not be recycled by Jetty.
@ -67,6 +72,8 @@ public class UpgradeHttpServletRequest implements HttpServletRequest
private final Cookie[] cookies;
private final String remoteUser;
private final Principal principal;
private final Authentication authentication;
private final UserIdentity.Scope scope;
private final Map<String, List<String>> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
private final Map<String, String[]> parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
@ -104,6 +111,8 @@ public class UpgradeHttpServletRequest implements HttpServletRequest
remoteUser = httpRequest.getRemoteUser();
principal = httpRequest.getUserPrincipal();
authentication = Request.getBaseRequest(httpRequest).getAuthentication();
scope = Request.getBaseRequest(httpRequest).getUserIdentityScope();
Enumeration<String> headerNames = httpRequest.getHeaderNames();
while (headerNames.hasMoreElements())
@ -220,7 +229,10 @@ public class UpgradeHttpServletRequest implements HttpServletRequest
@Override
public boolean isUserInRole(String role)
{
throw new UnsupportedOperationException(UNSUPPORTED_WITH_WEBSOCKET_UPGRADE);
if (authentication instanceof Authentication.User)
return ((Authentication.User)authentication).isUserInRole(scope, role);
return false;
}
@Override

View File

@ -50,7 +50,7 @@
<jetty-test-policy.version>1.2</jetty-test-policy.version>
<servlet.api.version>4.0.1</servlet.api.version>
<servlet.schema.version>4.0.3</servlet.schema.version>
<jsp.version>8.5.35.1</jsp.version>
<jsp.version>9.0.14.1</jsp.version>
<!-- default values are unsupported, but required to be defined for reactor sanity reasons -->
<alpn.version>undefined</alpn.version>
<conscrypt.version>1.4.1</conscrypt.version>