Issue #3167 Improve websocket initialization

Improve on #3167 with major refactor of the context initialization of
websocket:
  + The Javax and Jetty sides are more symmetric - both use shared
    filter and mapping.
  + Regularised usage of beans rather than attributes for ws components
  + Customization is now part of the mapping, so ws are configured by
    how they were mapped and not by who does the upgrade.
  + Filter still can be configured to customize defaults
  + Servlet can be configured to customize any ws mappings added via the
    servlet

There is still some strangeness as the WebSocketServlet is mostly
generic, yet can only map Jetty API websockets.

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2018-12-22 10:30:37 +11:00
parent 3779b0384d
commit 8c7c5a5d01
66 changed files with 2167 additions and 1450 deletions

20
Jenkinsfile vendored
View File

@ -9,7 +9,7 @@ pipeline {
agent { node { label 'linux' } }
options { timeout(time: 120, unit: 'MINUTES') }
steps {
mavenBuild("jdk11", "-Pmongodb install")
mavenBuild("jdk11", "-Pmongodb install", "maven3")
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
// Collect up the jacoco execution results (only on main build)
jacoco inclusionPattern: '**/org/eclipse/jetty/**/*.class',
@ -35,11 +35,7 @@ pipeline {
classPattern: '**/target/classes',
sourcePattern: '**/src/main/java'
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'Java']]
script {
step([$class : 'MavenInvokerRecorder', reportsFilenamePattern: "**/target/invoker-reports/BUILD*.xml",
invokerBuildDir: "**/target/its"])
}
maven_invoker reportsFilenamePattern: "**/target/invoker-reports/BUILD*.xml", invokerBuildDir: "**/target/its"
}
}
@ -47,7 +43,7 @@ pipeline {
agent { node { label 'linux' } }
options { timeout(time: 30, unit: 'MINUTES') }
steps {
mavenBuild("jdk11", "install javadoc:javadoc -DskipTests")
mavenBuild("jdk11", "install javadoc:javadoc -DskipTests", "maven3")
warnings consoleParsers: [[parserName: 'Maven'], [parserName: 'JavaDoc'], [parserName: 'Java']]
}
}
@ -74,13 +70,13 @@ pipeline {
*
* @param jdk the jdk tool name (in jenkins) to use for this build
* @param cmdline the command line in "<profiles> <goals> <properties>"`format.
* @paran mvnName maven installation to use
* @return the Jenkinsfile step representing a maven build
*/
def mavenBuild(jdk, cmdline) {
def mvnName = 'maven3.5'
def localRepo = "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}" // ".repository" //
def settingsName = 'oss-settings.xml'
def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true'
def mavenBuild(jdk, cmdline, mvnName) {
def localRepo = "${env.JENKINS_HOME}/${env.EXECUTOR_NUMBER}" // ".repository" //
def settingsName = 'oss-settings.xml'
def mavenOpts = '-Xms1g -Xmx4g -Djava.awt.headless=true'
withMaven(
maven: mvnName,

View File

@ -11,7 +11,7 @@
<name>Example Async Rest :: Jar</name>
<url>http://www.eclipse.org/jetty</url>
<properties>
<bundle-symbolic-name>${project.groupId}.examples.asyc.rest</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.examples.async.rest</bundle-symbolic-name>
</properties>
<dependencies>
<dependency>

View File

@ -24,6 +24,8 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

View File

@ -681,6 +681,21 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (context == null)
throw new IllegalArgumentException("WebAppContext null");
//if we don't know where its from it can't be excluded
if (sciResource == null)
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} null resource", sci);
return false;
}
//A ServletContainerInitialier that came from WEB-INF/classes or equivalent cannot be excluded by an ordering
if (isFromWebInfClasses(context, sciResource))
{
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} from web-inf/classes", sci);
return false;
}
//A ServletContainerInitializer that came from the container's classpath cannot be excluded by an ordering
//of WEB-INF/lib jars
@ -709,14 +724,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
return true;
}
if (sciResource == null)
{
//not from a jar therefore not from WEB-INF so not excludable
if (LOG.isDebugEnabled())
LOG.debug("!Excluded {} not from jar", sci);
return false;
}
//Check if it is excluded by an ordering
URI loadingJarURI = sciResource.getURI();
boolean found = false;
Iterator<Resource> itor = orderedJars.iterator();
@ -762,7 +770,46 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
if (sci == null)
return false;
return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
ClassLoader sciLoader = sci.getClass().getClassLoader();
//if loaded by bootstrap loader, then its the container classpath
if ( sciLoader == null)
return true;
//if there is no context classloader, then its the container classpath
if (context.getClassLoader() == null)
return true;
ClassLoader loader = sciLoader;
while (loader != null)
{
if (loader == context.getClassLoader())
return false; //the webapp classloader is in the ancestry of the classloader for the sci
else
loader = loader.getParent();
}
return true;
}
/**
* Test if the ServletContainerInitializer is from WEB-INF/classes
*
* @param context the webapp to test
* @param sci a Resource representing the SCI
* @return true if the sci Resource is inside a WEB-INF/classes directory, false otherwise
*/
public boolean isFromWebInfClasses (WebAppContext context, Resource sci)
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
if (dir.equals(sci))
{
return true;
}
}
return false;
}
/**
@ -849,27 +896,47 @@ public class AnnotationConfiguration extends AbstractConfiguration
//No jetty-specific ordering specified, or just the wildcard value "*" specified.
//Fallback to ordering the ServletContainerInitializers according to:
//container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar ordering)
//no web.xml ordering defined, add SCIs in any order
//First add in all SCIs that can't be excluded
int lastContainerSCI = -1;
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
{
nonExcludedInitializers.add(++lastContainerSCI, entry.getKey()); //add all container SCIs before any webapp SCIs
}
else if (entry.getValue() == null) //can't work out provenance of SCI, so can't be ordered/excluded
{
nonExcludedInitializers.add(entry.getKey()); //add at end of list
}
else
{
for (Resource dir : context.getMetaData().getWebInfClassesDirs())
{
if (dir.equals(entry.getValue()))//from WEB-INF/classes so can't be ordered/excluded
{
nonExcludedInitializers.add(entry.getKey());
}
}
}
}
//throw out the ones we've already accounted for
for (ServletContainerInitializer s:nonExcludedInitializers)
sciResourceMap.remove(s);
if (context.getMetaData().getOrdering() == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
//add the rest of the scis
nonExcludedInitializers.addAll(sciResourceMap.keySet());
}
else
{
if (LOG.isDebugEnabled())
LOG.debug("Ordering ServletContainerInitializers with ordering {}",context.getMetaData().getOrdering());
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
{
//add in SCIs from the container classpath
if (entry.getKey().getClass().getClassLoader()==context.getClassLoader().getParent())
nonExcludedInitializers.add(entry.getKey());
else if (entry.getValue() == null) //add in SCIs not in a jar, as they must be from WEB-INF/classes and can't be ordered
nonExcludedInitializers.add(entry.getKey());
}
//add SCIs according to the ordering of its containing jar
for (Resource webInfJar:context.getMetaData().getOrderedWebInfJars())
{
@ -889,7 +956,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
ListIterator<ServletContainerInitializer> it = nonExcludedInitializers.listIterator();
while (it.hasNext())
{
ServletContainerInitializer sci = it.next();
ServletContainerInitializer sci = it.next();
if (!isFromContainerClassPath(context, sci))
{
if (LOG.isDebugEnabled())

Binary file not shown.

View File

@ -1,54 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.annotations;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* ServerServletContainerInitializer
*
*
*/
public class ServerServletContainerInitializer implements ServletContainerInitializer
{
/**
*
*/
public ServerServletContainerInitializer()
{
// TODO Auto-generated constructor stub
}
/**
* @see javax.servlet.ServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)
*/
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
{
// TODO Auto-generated method stub
}
}

View File

@ -18,29 +18,30 @@
package org.eclipse.jetty.annotations;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.JAR;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.FragmentDescriptor;
import org.eclipse.jetty.webapp.RelativeOrdering;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestAnnotationConfiguration
{
public class TestableAnnotationConfiguration extends AnnotationConfiguration
@ -54,14 +55,67 @@ public class TestAnnotationConfiguration
}
}
public File web25;
public File web31false;
public File web31true;
public File jarDir;
public File testSciJar;
public File testContainerSciJar;
public File testWebInfClassesJar;
public File unpacked;
public URLClassLoader containerLoader;
public URLClassLoader webAppLoader;
public List<Resource> classes;
public Resource targetClasses;
public Resource webInfClasses;
@BeforeEach
public void setup() throws Exception
{
web25 = MavenTestingUtils.getTestResourceFile("web25.xml");
web31false = MavenTestingUtils.getTestResourceFile("web31false.xml");
web31true = MavenTestingUtils.getTestResourceFile("web31true.xml");
// prepare an sci that will be on the webapp's classpath
jarDir = new File(MavenTestingUtils.getTestResourcesDir().getParentFile(), "jar");
testSciJar = new File(jarDir, "test-sci.jar");
assertTrue(testSciJar.exists());
testContainerSciJar = new File(jarDir, "test-sci-for-container-path.jar");
testWebInfClassesJar = new File(jarDir, "test-sci-for-webinf.jar");
// unpack some classes to pretend that are in WEB-INF/classes
unpacked = new File(MavenTestingUtils.getTargetTestingDir(), "test-sci-for-webinf");
unpacked.mkdirs();
FS.cleanDirectory(unpacked);
JAR.unpack(testWebInfClassesJar, unpacked);
webInfClasses = Resource.newResource(unpacked);
containerLoader = new URLClassLoader(new URL[] { testContainerSciJar.toURI().toURL() }, Thread.currentThread().getContextClassLoader());
targetClasses = Resource.newResource(MavenTestingUtils.getTargetDir().toURI()).addPath("/test-classes");
classes = Arrays.asList(new Resource[] { webInfClasses, targetClasses });
webAppLoader = new URLClassLoader(new URL[] { testSciJar.toURI().toURL(), targetClasses.getURI().toURL(), webInfClasses.getURI().toURL() },
containerLoader);
}
@Test
public void testAnnotationScanControl() throws Exception
{
File web25 = MavenTestingUtils.getTestResourceFile("web25.xml");
File web31true = MavenTestingUtils.getTestResourceFile("web31true.xml");
File web31false = MavenTestingUtils.getTestResourceFile("web31false.xml");
{
//check that a 2.5 webapp won't discover annotations
TestableAnnotationConfiguration config25 = new TestableAnnotationConfiguration();
WebAppContext context25 = new WebAppContext();
@ -111,85 +165,190 @@ public class TestAnnotationConfiguration
config31b.configure(context31b);
config31b.assertAnnotationDiscovery(true);
}
@Test
@Disabled("See issue #3000. Fails because a SCI service is added in src/test/resources, but the module system cannot find it because it's not declared in the module-info.")
public void testSCIControl () throws Exception
public void testServerAndWebappSCIs() throws Exception
{
File web25 = MavenTestingUtils.getTestResourceFile("web25.xml");
File web31false = MavenTestingUtils.getTestResourceFile("web31false.xml");
File web31true = MavenTestingUtils.getTestResourceFile("web31true.xml");
Set<String> sciNames = new HashSet<>(Arrays.asList("org.eclipse.jetty.annotations.ServerServletContainerInitializer", "com.acme.initializer.FooInitializer"));
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(webAppLoader);
//prepare an sci that will be on the webapp's classpath
File jarDir = new File(MavenTestingUtils.getTestResourcesDir().getParentFile(), "jar");
File testSciJar = new File(jarDir, "test-sci.jar");
assertTrue(testSciJar.exists());
URLClassLoader webAppLoader = new URLClassLoader(new URL[]{testSciJar.toURI().toURL()}, Thread.currentThread().getContextClassLoader());
ClassLoader orig = Thread.currentThread().getContextClassLoader();
try
{
//test 3.1 webapp loads both server and app scis
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
//test 3.1 webapp loads both server and app scis
context.setClassLoader(webAppLoader);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebXml(Resource.newResource(web31true));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
Thread.currentThread().setContextClassLoader(webAppLoader);
List<ServletContainerInitializer> scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
//test a 3.1 webapp with metadata-complete=false loads both server and webapp scis
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(classes);
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
//test 2.5 webapp with configurationDiscovered=false loads only server scis
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(1, scis.size());
assertTrue("org.eclipse.jetty.annotations.ServerServletContainerInitializer".equals(scis.get(0).getClass().getName()));
//test 2.5 webapp with configurationDiscovered=true loads both server and webapp scis
config = new AnnotationConfiguration();
context = new WebAppContext();
context.setConfigurationDiscovered(true);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(2, scis.size());
assertTrue(sciNames.contains(scis.get(0).getClass().getName()));
assertTrue(sciNames.contains(scis.get(1).getClass().getName()));
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("com.acme.webinf.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); // web-inf
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); //web-inf jar no web-fragment
}
finally
{
Thread.currentThread().setContextClassLoader(orig);
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testMetaDataCompleteSCIs() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(webAppLoader);
try
{
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
// test a 3.1 webapp with metadata-complete=false loads both server
// and webapp scis
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web31false));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); // container
// path
assertEquals("com.acme.webinf.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); // web-inf
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); // web-inf
// jar
// no
// web-fragment
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testRelativeOrderingWithSCIs() throws Exception
{
// test a 3.1 webapp with RELATIVE ORDERING loads sci from
// equivalent of WEB-INF/classes first as well as container path
ClassLoader old = Thread.currentThread().getContextClassLoader();
File orderedFragmentJar = new File(jarDir, "test-sci-with-ordering.jar");
assertTrue(orderedFragmentJar.exists());
URLClassLoader orderedLoader = new URLClassLoader(new URL[] { orderedFragmentJar.toURI().toURL(), testSciJar.toURI().toURL(),
targetClasses.getURI().toURL(), webInfClasses.getURI().toURL() },
containerLoader);
Thread.currentThread().setContextClassLoader(orderedLoader);
try
{
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(orderedLoader);
context.getMetaData().setWebXml(Resource.newResource(web31true));
RelativeOrdering ordering = new RelativeOrdering(context.getMetaData());
context.getMetaData().setOrdering(ordering);
context.getMetaData().addWebInfJar(Resource.newResource(orderedFragmentJar.toURI().toURL()));
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().orderFragments();
context.getServletContext().setEffectiveMajorVersion(3);
context.getServletContext().setEffectiveMinorVersion(1);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(4, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("com.acme.webinf.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); // web-inf
assertEquals("com.acme.ordering.AcmeServletContainerInitializer", scis.get(2).getClass().getName()); // first
assertEquals("com.acme.initializer.FooInitializer", scis.get(3).getClass().getName()); //other in ordering
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testDiscoveredFalseWithSCIs() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(webAppLoader);
try
{
//test 2.5 webapp with configurationDiscovered=false loads only server scis
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
for (ServletContainerInitializer s:scis)
{
//should not have any of the web-inf lib scis in here
assertFalse(s.getClass().getName().equals("com.acme.ordering.AcmeServletContainerInitializer"));
assertFalse(s.getClass().getName().equals("com.acme.initializer.FooInitializer"));
//NOTE: should also not have the web-inf classes scis in here either, but due to the
//way the test is set up, the sci we're pretending is in web-inf classes will actually
//NOT be loaded by the webapp's classloader, but rather by the junit classloader, so
//it looks as if it is a container class.
}
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testDiscoveredTrueWithSCIs() throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(webAppLoader);
try
{
//test 2.5 webapp with configurationDiscovered=true loads both server and webapp scis
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List<ServletContainerInitializer> scis;
context.setConfigurationDiscovered(true);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebXml(Resource.newResource(web25));
context.getMetaData().setWebInfClassesDirs(classes);
context.getMetaData().addWebInfJar(Resource.newResource(testSciJar.toURI().toURL()));
context.getServletContext().setEffectiveMajorVersion(2);
context.getServletContext().setEffectiveMinorVersion(5);
scis = config.getNonExcludedInitializers(context);
assertNotNull(scis);
assertEquals(3, scis.size());
assertEquals("com.acme.ServerServletContainerInitializer", scis.get(0).getClass().getName()); //container path
assertEquals("com.acme.webinf.WebInfClassServletContainerInitializer", scis.get(1).getClass().getName()); // web-inf
assertEquals("com.acme.initializer.FooInitializer", scis.get(2).getClass().getName()); //web-inf jar no web-fragment
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@Test
public void testGetFragmentFromJar() throws Exception
{

View File

@ -1 +0,0 @@
org.eclipse.jetty.annotations.ServerServletContainerInitializer

View File

@ -91,7 +91,29 @@ By default, log files are kept for 90 days before being deleted.
The value for `retainDays` (xml) or `setRetainDays` (Java) should be configured as _1 + n_ days.
For example, if you wanted to keep the logs for the current day and the day prior you would set the `retainDays` (or `setRetainDays`) value to 2.
To examine more configuration options, see link:{JDURL}/org/eclipse/jetty/server/NCSARequestLog.html[NCSARequestLog.java].
[[request-log-custom-writer]]
==== Introducing RequestLog.Writer
The concept of a `RequestLog.Writer`, introduced in Jetty 9.4.15, manages the writing to a log the string generated by the `RequestLog`.
This allows the `CustomRequestLog` to match the functionality of other `RequestLogger` implementations by plugging in any `RequestLog.Writer` and a custom format string.
Jetty currently has implementations of `RequestLog.Writer`, `RequestLogWriter`, `AsyncRequestLogWriter`, and `Slf4jRequestLogWriter`.
So, the way to create an asynchronous `RequestLog` using the extended NCSA format has been changed from:
`new AsyncNcsaRequestLog(filename)`
to:
`new CustomRequestLog(new AsyncRequestLogWriter(filename), CustomRequestLog.EXTENDED_NCSA_FORMAT)`
Additionally, there are now two settings for the log timezone to be configured.
There is the configuration for logging the request time, which is set in the `timeZone` parameter in the `%t` format code of the string, given in the format `%{format|timeZone|locale}t`.
The other `timeZone` parameter relates to the generation of the log file name (both at creation and roll over).
This is configured in the `requestlog` module file, or can be used as a setter on `RequestLogWriter` via XML.
Both timezones are set to GMT by default.
[[configuring-separate-request-log-for-web-application]]
==== Configuring a Separate Request Log For a Web Application

View File

@ -18,23 +18,23 @@
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.jndi.*</onlyAnalyze>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>javax.mail.*;resolution:=optional,*</Import-Package>
<Import-Package>javax.mail.*;resolution:=optional,*</Import-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<configuration>
<onlyAnalyze>org.eclipse.jetty.jndi.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -17,6 +17,8 @@ Running single test
You can run single or set of test as well using the command line argument: ```-Dinvoker.test=jetty-run-mojo-it,jetty-run-war*-it,!jetty-run-distro*```
The parameter supports pattern and exclusion with !
NOTE: if you use ```clean``` arg to maven, you will also need to add the test ```it-parent-pom``` first for invoker.test, eg ```-Dinvoker.test=it-parent-pom,jetty-run-mojo-it```.
Running Logs
--------------------
The output of each Maven build will be located in /target/it/${project-name}/build.log

View File

@ -0,0 +1 @@
invoker.goals = test

View File

@ -0,0 +1,109 @@
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.its</groupId>
<artifactId>it-parent-pom</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>javax-annotation-api-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!--maven.compiler.release>11</maven.compiler.release-->
<jetty.port.file>${project.build.directory}/jetty-run-mojo-annotation.txt</jetty.port.file>
<annotation-api.version>1.3.2</annotation-api.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${annotation-api.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.8.0-beta2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.8.0-beta2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<executions>
<execution>
<id>start-jetty</id>
<phase>test-compile</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<systemProperties>
<systemProperty>
<name>jetty.port.file</name>
<value>${jetty.port.file}</value>
</systemProperty>
</systemProperties>
<nonBlocking>true</nonBlocking>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<extensions>
<extension>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>${annotation-api.version}</version>
</extension>
<extension>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</extension>
</extensions>
</build>
</project>

View File

@ -0,0 +1,21 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
File buildLog = new File( basedir, 'build.log' )
assert buildLog.text.contains( 'Started Jetty Server' )
assert buildLog.text.contains( 'all good guys get a good Beer')

View File

@ -0,0 +1,64 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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 test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Properties;
/**
* Hello world!
*
*/
public class App extends SpringBootServletInitializer {
private Logger logger = LoggerFactory.getLogger( getClass() );
@Resource(name="my.properties")
private Properties somePropertyFile;
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder ) {
return builder.sources( App.class );
}
@PostConstruct
public void done(){
logger.info( "all good guys get a good {}", somePropertyFile.get( "drink" ) );
}
@Bean(name = "my.properties")
public Properties getSomeProperties() throws Exception{
Properties properties = new Properties( );
try(InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream( "my.properties" ))
{
properties.load( inputStream );
}
return properties;
}
}

View File

@ -58,32 +58,7 @@ public class AsyncContextState implements AsyncContext
@Override
public void addListener(final AsyncListener listener, final ServletRequest request, final ServletResponse response)
{
AsyncListener wrap = new AsyncListener()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
listener.onTimeout(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
listener.onStartAsync(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onError(AsyncEvent event) throws IOException
{
listener.onError(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
listener.onComplete(new AsyncEvent(event.getAsyncContext(),request,response,event.getThrowable()));
}
};
AsyncListener wrap = new WrappedAsyncListener(listener, request, response);
state().addListener(wrap);
}
@ -188,6 +163,46 @@ public class AsyncContextState implements AsyncContext
return state();
}
public static class WrappedAsyncListener implements AsyncListener
{
private final AsyncListener _listener;
private final ServletRequest _request;
private final ServletResponse _response;
public WrappedAsyncListener(AsyncListener listener, ServletRequest request, ServletResponse response)
{
_listener = listener;
_request = request;
_response = response;
}
public AsyncListener getListener()
{
return _listener;
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
_listener.onTimeout(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
_listener.onStartAsync(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onError(AsyncEvent event) throws IOException
{
_listener.onError(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
_listener.onComplete(new AsyncEvent(event.getAsyncContext(), _request, _response,event.getThrowable()));
}
}
}

View File

@ -149,6 +149,25 @@ public class HttpChannelState
}
}
public boolean hasListener(AsyncListener listener)
{
try(Locker.Lock lock= _locker.lock())
{
if (_asyncListeners==null)
return false;
for (AsyncListener l : _asyncListeners)
{
if (l==listener)
return true;
if (l instanceof AsyncContextState.WrappedAsyncListener && ((AsyncContextState.WrappedAsyncListener)l).getListener()==listener)
return true;
}
return false;
}
}
public void setTimeout(long ms)
{
try(Locker.Lock lock= _locker.lock())

View File

@ -442,7 +442,7 @@ public class LowResourceMonitor extends ContainerLifeCycle
{
}
interface LowResourceCheck
public interface LowResourceCheck
{
boolean isLowOnResources();

View File

@ -72,6 +72,7 @@ import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
@ -465,8 +466,12 @@ public class Request implements HttpServletRequest
if (MimeTypes.Type.FORM_ENCODED.is(contentType) &&
_channel.getHttpConfiguration().isFormEncodedMethod(getMethod()))
{
if (_metaData!=null && getHttpFields().contains(HttpHeader.CONTENT_ENCODING))
throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501,"Unsupported Content-Encoding");
if (_metaData!=null)
{
String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING);
if (contentEncoding!=null && !HttpHeaderValue.IDENTITY.is(contentEncoding))
throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501, "Unsupported Content-Encoding");
}
extractFormParameters(_contentParameters);
}
else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) &&

View File

@ -55,7 +55,7 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
public final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";
protected static final AtomicLong COUNTER = new AtomicLong();

View File

@ -1148,4 +1148,18 @@ public class Session implements SessionHandler.SessionIf
return _resident;
}
@Override
public String toString()
{
try (Lock lock = _lock.lock())
{
return String.format("%s@%x{id=%s,x=%s,req=%d,res=%b}",
getClass().getSimpleName(),
hashCode(),
_sessionData.getId(),
_extendedId,
_requests,
_resident);
}
}
}

View File

@ -162,8 +162,22 @@ public class SessionHandler extends ScopedHandler
@Override
public void onComplete(AsyncEvent event) throws IOException
{
//An async request has completed, so we can complete the session
complete(Request.getBaseRequest(event.getAsyncContext().getRequest()).getSession(false));
// An async request has completed, so we can complete the session,
// but we must locate the session instance for this context
Request request = Request.getBaseRequest(event.getAsyncContext().getRequest());
HttpSession session = request.getSession(false);
String id;
if (session!=null)
id = session.getId();
else
{
id = (String)request.getAttribute(DefaultSessionIdManager.__NEW_SESSION_ID);
if (id==null)
id = request.getRequestedSessionId();
}
if (id!=null)
complete(getSession(id));
}
@Override
@ -407,9 +421,12 @@ public class SessionHandler extends ScopedHandler
*/
public void complete(HttpSession session)
{
if (LOG.isDebugEnabled())
LOG.debug("Complete called with session {}", session);
if (session == null)
return;
Session s = ((SessionIf)session).getSession();
try
@ -422,23 +439,26 @@ public class SessionHandler extends ScopedHandler
LOG.warn(e);
}
}
public void complete (Session session, Request request)
@Deprecated
public void complete(Session session, Request baseRequest)
{
if (request.isAsyncStarted() && request.getDispatcherType() == DispatcherType.REQUEST)
ensureCompletion(baseRequest);
}
private void ensureCompletion(Request baseRequest)
{
if (baseRequest.isAsyncStarted())
{
request.getAsyncContext().addListener(_sessionAsyncListener);
if (LOG.isDebugEnabled())
LOG.debug("Adding AsyncListener for {}", baseRequest);
if (!baseRequest.getHttpChannelState().hasListener(_sessionAsyncListener))
baseRequest.getAsyncContext().addListener(_sessionAsyncListener);
}
else
{
complete(session);
complete(baseRequest.getSession(false));
}
//if dispatcher type is not async and not request, complete immediately (its a forward or an include)
//else if dispatcher type is request and not async, complete immediately
//else register an async callback completion listener that will complete the session
}
@ -455,7 +475,6 @@ public class SessionHandler extends ScopedHandler
_context=ContextHandler.getCurrentContext();
_loader=Thread.currentThread().getContextClassLoader();
synchronized (server)
{
//Get a SessionDataStore and a SessionDataStore, falling back to in-memory sessions only
@ -472,7 +491,6 @@ public class SessionHandler extends ScopedHandler
_sessionCache.setSessionDataStore(sds);
}
if (_sessionIdManager==null)
{
@ -1593,16 +1611,19 @@ public class SessionHandler extends ScopedHandler
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
SessionHandler old_session_manager = null;
SessionHandler old_session_handler = null;
HttpSession old_session = null;
HttpSession existingSession = null;
try
{
old_session_manager = baseRequest.getSessionHandler();
if (LOG.isDebugEnabled())
LOG.debug("SessionHandler.doScope");
old_session_handler = baseRequest.getSessionHandler();
old_session = baseRequest.getSession(false);
if (old_session_manager != this)
if (old_session_handler != this)
{
// new session context
baseRequest.setSessionHandler(this);
@ -1613,7 +1634,7 @@ public class SessionHandler extends ScopedHandler
// access any existing session for this context
existingSession = baseRequest.getSession(false);
if ((existingSession != null) && (old_session_manager != this))
if ((existingSession != null) && (old_session_handler != this))
{
HttpCookie cookie = access(existingSession,request.isSecure());
// Handle changed ID or max-age refresh, but only if this is not a redispatched request
@ -1622,10 +1643,7 @@ public class SessionHandler extends ScopedHandler
}
if (LOG.isDebugEnabled())
{
LOG.debug("sessionHandler=" + this);
LOG.debug("session=" + existingSession);
}
LOG.debug("sessionHandler={} session={}",this, existingSession);
if (_nextScope != null)
_nextScope.doScope(target,baseRequest,request,response);
@ -1637,16 +1655,18 @@ public class SessionHandler extends ScopedHandler
finally
{
//if there is a session that was created during handling this context, then complete it
HttpSession finalSession = baseRequest.getSession(false);
if (LOG.isDebugEnabled()) LOG.debug("FinalSession="+finalSession+" old_session_manager="+old_session_manager+" this="+this);
if ((finalSession != null) && (old_session_manager != this))
if (LOG.isDebugEnabled())
LOG.debug("FinalSession={}, old_session_handler={}, this={}, calling complete={}", baseRequest.getSession(false), old_session_handler, this, (old_session_handler != this));
// If we are leaving the scope of this session handler, ensure the session is completed
if (old_session_handler != this)
ensureCompletion(baseRequest);
// revert the session handler to the previous, unless it was null, in which case remember it as
// the first session handler encountered.
if (old_session_handler != null && old_session_handler != this)
{
complete((Session)finalSession, baseRequest);
}
if (old_session_manager != null && old_session_manager != this)
{
baseRequest.setSessionHandler(old_session_manager);
baseRequest.setSessionHandler(old_session_handler);
baseRequest.setSession(old_session);
}
}

View File

@ -648,18 +648,29 @@ public class RequestTest
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testIdentityParamExtraction() throws Exception
{
_handler._checker = (request, response) -> "bar".equals(request.getParameter("foo"));
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: application/x-www-form-urlencoded; charset=utf-8\n"+
"Content-Length: 7\n"+
"Content-Encoding: identity\n"+
"Connection: close\n"+
"\n"+
"foo=bar\n";
String responses=_connector.getResponse(request);
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testEncodedNotParams() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
return request.getParameter("param")==null;
}
};
_handler._checker = (request, response) -> request.getParameter("param")==null;
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
@ -675,7 +686,6 @@ public class RequestTest
assertThat(responses,startsWith("HTTP/1.1 200"));
}
@Test
public void testInvalidHostHeader() throws Exception
{

View File

@ -116,14 +116,13 @@ public interface LifeCycle
*/
public interface Listener extends EventListener
{
public void lifeCycleStarting(LifeCycle event);
public void lifeCycleStarted(LifeCycle event);
public void lifeCycleFailure(LifeCycle event,Throwable cause);
public void lifeCycleStopping(LifeCycle event);
public void lifeCycleStopped(LifeCycle event);
default void lifeCycleStarting(LifeCycle event) {}
default void lifeCycleStarted(LifeCycle event) {}
default void lifeCycleFailure(LifeCycle event,Throwable cause) {}
default void lifeCycleStopping(LifeCycle event) {}
default void lifeCycleStopped(LifeCycle event) {}
}
/**
* Utility to start an object if it is a LifeCycle and to convert
* any exception thrown to a {@link RuntimeException}

View File

@ -217,7 +217,9 @@ public class JavaxWebSocketFrameHandler implements FrameHandler
if (errorHandle == null)
{
LOG.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " missing onError handler", cause);
LOG.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " : " + cause);
if (LOG.isDebugEnabled())
LOG.debug("unhandled", cause);
return;
}

View File

@ -22,9 +22,15 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.websocket.DeploymentException;
import javax.websocket.EndpointConfig;
import javax.websocket.WebSocketContainer;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
@ -35,8 +41,10 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.DecoratedObjectFactory;
import org.eclipse.jetty.util.annotation.ManagedObject;
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.client.WebSocketCoreClient;
import org.eclipse.jetty.websocket.javax.client.JavaxWebSocketClientContainer;
@ -44,10 +52,12 @@ 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;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
@ManagedObject("JSR356 Server Container")
public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer implements javax.websocket.server.ServerContainer
public class JavaxWebSocketServerContainer
extends JavaxWebSocketClientContainer
implements javax.websocket.server.ServerContainer, LifeCycle.Listener
{
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainer.class);
@ -70,41 +80,86 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
if (!(handler instanceof ServletContextHandler))
return null;
return (javax.websocket.WebSocketContainer)handler.getServletContext().getAttribute("javax.websocket.server.ServerContainer");
return (javax.websocket.WebSocketContainer)handler.getServletContext().getAttribute(ServerContainer.class.getName());
}
private final WebSocketCreatorMapping _webSocketCreatorMapping;
public static JavaxWebSocketServerContainer ensureContainer(ServletContext servletContext) throws ServletException
{
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
JavaxWebSocketServerContainer container = contextHandler.getBean(JavaxWebSocketServerContainer.class);
if (container==null)
{
// Find Pre-Existing (Shared?) HttpClient and/or executor
HttpClient httpClient = (HttpClient)servletContext.getAttribute(JavaxWebSocketServerContainerInitializer.HTTPCLIENT_ATTRIBUTE);
if (httpClient == null)
httpClient = (HttpClient)contextHandler.getServer()
.getAttribute(JavaxWebSocketServerContainerInitializer.HTTPCLIENT_ATTRIBUTE);
Executor executor = httpClient == null?null:httpClient.getExecutor();
if (executor == null)
executor = (Executor)servletContext
.getAttribute("org.eclipse.jetty.server.Executor");
if (executor == null)
executor = contextHandler.getServer().getThreadPool();
if (httpClient != null && httpClient.getExecutor() == null)
httpClient.setExecutor(executor);
// Create the Jetty ServerContainer implementation
container = new JavaxWebSocketServerContainer(
WebSocketMapping.ensureMapping(servletContext), httpClient, executor);
contextHandler.addManaged(container);
contextHandler.addLifeCycleListener(container);
}
// Store a reference to the ServerContainer per - javax.websocket spec 1.0 final - section 6.4: Programmatic Server Deployment
servletContext.setAttribute(ServerContainer.class.getName(), container);
return container;
}
private final WebSocketMapping _webSocketMapping;
private final JavaxWebSocketServerFrameHandlerFactory frameHandlerFactory;
private final Executor executor;
private final FrameHandler.ConfigurationCustomizer customizer = new FrameHandler.ConfigurationCustomizer();
private long asyncSendTimeout = -1;
private List<Class<?>> deferredEndpointClasses;
private List<ServerEndpointConfig> deferredEndpointConfigs;
/**
* Main entry point for {@link JavaxWebSocketServerContainerInitializer}.
* @param webSocketCreatorMapping the {@link WebSocketCreatorMapping} that this container belongs to
* @param webSocketMapping the {@link WebSocketMapping} that this container belongs to
* @param httpClient the {@link HttpClient} instance to use
*/
public JavaxWebSocketServerContainer(WebSocketCreatorMapping webSocketCreatorMapping, HttpClient httpClient, Executor executor)
public JavaxWebSocketServerContainer(WebSocketMapping webSocketMapping, HttpClient httpClient, Executor executor)
{
super(() ->
{
// TODO Can the client share the websocket or container buffer pool
WebSocketCoreClient client = new WebSocketCoreClient(httpClient);
if (executor != null && httpClient == null)
client.getHttpClient().setExecutor(executor);
return client;
});
this._webSocketCreatorMapping = webSocketCreatorMapping;
this._webSocketMapping = webSocketMapping;
this.executor = executor;
this.frameHandlerFactory = new JavaxWebSocketServerFrameHandlerFactory(this);
}
@Override
public void lifeCycleStopping(LifeCycle context)
{
ContextHandler contextHandler = (ContextHandler) context;
JavaxWebSocketServerContainer container = contextHandler.getBean(JavaxWebSocketServerContainer.class);
if (container==null)
{
contextHandler.removeBean(container);
LifeCycle.stop(container);
}
}
@Override
public ByteBufferPool getBufferPool()
{
return this._webSocketCreatorMapping.getBufferPool();
return this._webSocketMapping.getBufferPool();
}
@Override
@ -116,7 +171,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override
public WebSocketExtensionRegistry getExtensionRegistry()
{
return this._webSocketCreatorMapping.getExtensionRegistry();
return this._webSocketMapping.getExtensionRegistry();
}
@Override
@ -128,7 +183,7 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override
public DecoratedObjectFactory getObjectFactory()
{
return this._webSocketCreatorMapping.getObjectFactory();
return this._webSocketMapping.getObjectFactory();
}
@Override
@ -149,14 +204,6 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
return config;
}
/**
* Register a &#064;{@link ServerEndpoint} annotated endpoint class to
* the server
*
* @param endpointClass the annotated endpoint class to add to the server
* @throws DeploymentException if unable to deploy that endpoint class
* @see javax.websocket.server.ServerContainer#addEndpoint(Class)
*/
@Override
public void addEndpoint(Class<?> endpointClass) throws DeploymentException
{
@ -187,20 +234,11 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
else
{
if (deferredEndpointClasses == null)
{
deferredEndpointClasses = new ArrayList<>();
}
deferredEndpointClasses.add(endpointClass);
}
}
/**
* Register a ServerEndpointConfig to the server
*
* @param config the endpoint config to add
* @throws DeploymentException if unable to deploy that endpoint class
* @see javax.websocket.server.ServerContainer#addEndpoint(ServerEndpointConfig)
*/
@Override
public void addEndpoint(ServerEndpointConfig config) throws DeploymentException
{
@ -231,8 +269,10 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
{
frameHandlerFactory.getMetadata(config.getEndpointClass(), config);
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this._webSocketCreatorMapping.getExtensionRegistry());
this._webSocketCreatorMapping.addMapping(new UriTemplatePathSpec(config.getPath()), creator);
JavaxWebSocketCreator creator = new JavaxWebSocketCreator(this, config, this._webSocketMapping
.getExtensionRegistry());
this._webSocketMapping.addMapping(new UriTemplatePathSpec(config.getPath()), creator, frameHandlerFactory, customizer);
}
@Override
@ -271,20 +311,21 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
public int getDefaultMaxBinaryMessageBufferSize()
{
// TODO: warn on long -> int conversion issue
return (int)this._webSocketCreatorMapping.getDefaultMaxBinaryMessageSize();
// TODO: Should this be Filter?
return (int)customizer.getMaxBinaryMessageSize();
}
@Override
public long getDefaultMaxSessionIdleTimeout()
{
return this._webSocketCreatorMapping.getDefaultIdleTimeout().toMillis();
return customizer.getIdleTimeout().toMillis();
}
@Override
public int getDefaultMaxTextMessageBufferSize()
{
// TODO: warn on long -> int conversion issue
return (int)this._webSocketCreatorMapping.getDefaultMaxTextMessageSize();
return (int)customizer.getMaxTextMessageSize();
}
@Override
@ -296,18 +337,18 @@ public class JavaxWebSocketServerContainer extends JavaxWebSocketClientContainer
@Override
public void setDefaultMaxBinaryMessageBufferSize(int max)
{
this._webSocketCreatorMapping.setDefaultMaxBinaryMessageSize(max);
customizer.setMaxBinaryMessageSize(max);
}
@Override
public void setDefaultMaxSessionIdleTimeout(long ms)
{
this._webSocketCreatorMapping.setDefaultIdleTimeout(Duration.ofMillis(ms));
customizer.setIdleTimeout(Duration.ofMillis(ms));
}
@Override
public void setDefaultMaxTextMessageBufferSize(int max)
{
this._webSocketCreatorMapping.setDefaultMaxTextMessageSize(max);
customizer.setMaxTextMessageSize(max);
}
}

View File

@ -35,12 +35,13 @@ import javax.websocket.server.ServerEndpointConfig;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ThreadClassLoaderScope;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter;
@HandlesTypes({ ServerApplicationConfig.class, ServerEndpoint.class, Endpoint.class })
@ -48,36 +49,8 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
{
public static final String ENABLE_KEY = "org.eclipse.jetty.websocket.javax";
public static final String DEPRECATED_ENABLE_KEY = "org.eclipse.jetty.websocket.jsr356";
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainerInitializer.class);
public static final String HTTPCLIENT_ATTRIBUTE = "org.eclipse.jetty.websocket.javax.HttpClient";
/**
* DestroyListener
*/
public static class ContextDestroyListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
//noop
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
//remove any ServerContainer beans
if (sce.getServletContext() instanceof ContextHandler.Context)
{
ContextHandler handler = ((ContextHandler.Context)sce.getServletContext()).getContextHandler();
JavaxWebSocketServerContainer bean = handler.getBean(JavaxWebSocketServerContainer.class);
if (bean != null)
handler.removeBean(bean);
}
//remove reference in attributes
sce.getServletContext().removeAttribute(javax.websocket.server.ServerContainer.class.getName());
}
}
private static final Logger LOG = Log.getLogger(JavaxWebSocketServerContainerInitializer.class);
/**
* Test a ServletContext for {@code init-param} or {@code attribute} at {@code keyName} for
@ -127,58 +100,15 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
return defValue;
}
/**
* 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
*/
public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context) throws ServletException
public static JavaxWebSocketServerContainer configureContext(ServletContextHandler context)
throws ServletException
{
WebSocketUpgradeFilter.configureContext(context);
WebSocketCreatorMapping webSocketCreatorMapping = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName());
// Find Pre-Existing (Shared?) HttpClient and/or executor
HttpClient httpClient = (HttpClient)context.getServletContext().getAttribute(HTTPCLIENT_ATTRIBUTE);
if (httpClient == null)
httpClient = (HttpClient)context.getServer().getAttribute(HTTPCLIENT_ATTRIBUTE);
Executor executor = httpClient == null?null:httpClient.getExecutor();
if (executor == null)
executor = (Executor)context.getAttribute("org.eclipse.jetty.server.Executor");
if (executor == null)
executor = context.getServer().getThreadPool();
if (httpClient!=null && httpClient.getExecutor()==null)
httpClient.setExecutor(executor);
// Create the Jetty ServerContainer implementation
JavaxWebSocketServerContainer jettyContainer = new JavaxWebSocketServerContainer(webSocketCreatorMapping, httpClient, executor);
context.addBean(jettyContainer);
// Add WebSocketServletFrameHandlerFactory to servlet container for this JSR container
webSocketCreatorMapping.addFrameHandlerFactory(jettyContainer.getFrameHandlerFactory());
// 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;
}
/**
* @param context not used
* @param jettyContext the {@link ServletContextHandler} to use
* @return a configured {@link JavaxWebSocketServerContainer} instance
* @throws ServletException if the {@link WebSocketUpgradeFilter} cannot be configured
* @deprecated use {@link #configureContext(ServletContextHandler)} instead
*/
@Deprecated
public static JavaxWebSocketServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException
{
return configureContext(jettyContext);
WebSocketMapping mapping = WebSocketMapping.ensureMapping(context.getServletContext());
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(context.getServletContext());
JavaxWebSocketServerContainer container = JavaxWebSocketServerContainer.ensureContainer(context.getServletContext());
if (LOG.isDebugEnabled())
LOG.debug("configureContext {} {} {}",mapping,upgradeFilter,container);
return container;
}
@Override
@ -196,30 +126,13 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
return;
}
ContextHandler handler = ContextHandler.getContextHandler(context);
if (handler == null)
{
throw new ServletException("Not running on Jetty, Javax Websocket support unavailable");
}
if (!(handler instanceof ServletContextHandler))
{
throw new ServletException("Not running in Jetty ServletContextHandler, Javax Websocket support unavailable");
}
ServletContextHandler jettyContext = (ServletContextHandler)handler;
JavaxWebSocketServerContainer container = configureContext((ServletContextHandler)(ContextHandler.getContextHandler(context)));
try (ThreadClassLoaderScope scope = new ThreadClassLoaderScope(context.getClassLoader()))
{
// Create the Jetty ServerContainer implementation
JavaxWebSocketServerContainer jettyContainer = configureContext(jettyContext);
context.addListener(new ContextDestroyListener()); // make sure context is cleaned up when the context stops
if (LOG.isDebugEnabled())
{
LOG.debug("Found {} classes", c.size());
}
// Now process the incoming classes
Set<Class<? extends Endpoint>> discoveredExtendedEndpoints = new HashSet<>();
@ -243,9 +156,8 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
for (Class<? extends ServerApplicationConfig> clazz : serverAppConfigs)
{
if (LOG.isDebugEnabled())
{
LOG.debug("Found ServerApplicationConfig: {}", clazz);
}
try
{
ServerApplicationConfig config = clazz.getDeclaredConstructor().newInstance();
@ -287,7 +199,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
{
try
{
jettyContainer.addEndpoint(config);
container.addEndpoint(config);
}
catch (DeploymentException e)
{
@ -303,7 +215,7 @@ public class JavaxWebSocketServerContainerInitializer implements ServletContaine
{
try
{
jettyContainer.addEndpoint(annotatedClass);
container.addEndpoint(annotatedClass);
}
catch (DeploymentException e)
{

View File

@ -28,7 +28,7 @@ import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerFactor
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketFrameHandlerMetadata;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFrameHandlerFactory;
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
@ -36,7 +36,7 @@ import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.CompletableFuture;
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements WebSocketServletFrameHandlerFactory
public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory implements FrameHandlerFactory
{
public JavaxWebSocketServerFrameHandlerFactory(JavaxWebSocketContainer container)
{

View File

@ -20,13 +20,13 @@ package org.eclipse.jetty.websocket.javax.server;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
public class DummyServerContainer extends JavaxWebSocketServerContainer
{
public DummyServerContainer()
{
super(new WebSocketCreatorMapping(), new HttpClient(), new QueuedThreadPool());
super(new WebSocketMapping(), new HttpClient(), new QueuedThreadPool());
addBean(getHttpClient(), true);
addBean(getExecutor(), true);
}

View File

@ -38,9 +38,11 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.internal.Parser;
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainerInitializer;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

View File

@ -30,7 +30,7 @@ import org.eclipse.jetty.websocket.javax.client.EmptyClientEndpointConfig;
import org.eclipse.jetty.websocket.javax.common.decoders.AvailableDecoders;
import org.eclipse.jetty.websocket.javax.common.encoders.AvailableEncoders;
import org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServerContainer;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@ -47,7 +47,7 @@ public abstract class AbstractJavaxWebSocketServerFrameHandlerTest
context = new ServletContextHandler();
server.setHandler(context);
WebSocketCreatorMapping factory = new WebSocketCreatorMapping();
WebSocketMapping factory = new WebSocketMapping();
HttpClient httpClient = new HttpClient();
container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool());

View File

@ -38,7 +38,7 @@ import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenCloseRe
import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenIntSocket;
import org.eclipse.jetty.websocket.javax.tests.server.sockets.InvalidOpenSessionIntSocket;
import org.eclipse.jetty.websocket.javax.common.util.InvalidSignatureException;
import org.eclipse.jetty.websocket.servlet.WebSocketCreatorMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
@ -99,7 +99,7 @@ public class DeploymentExceptionTest
{
ServletContextHandler context = new ServletContextHandler();
WebSocketCreatorMapping factory = new WebSocketCreatorMapping();
WebSocketMapping factory = new WebSocketMapping();
HttpClient httpClient = new HttpClient();
JavaxWebSocketServerContainer container = new JavaxWebSocketServerContainer(factory, httpClient, server.getThreadPool());

View File

@ -67,13 +67,11 @@ public class JettyWebSocketFrameHandler implements FrameHandler
*/
private final UpgradeResponse upgradeResponse;
private final CompletableFuture<Session> futureSession;
private final CoreCustomizer customizer;
private final Customizer customizer;
private MessageSink textSink;
private MessageSink binarySink;
private MessageSink activeMessageSink;
private WebSocketSessionImpl session;
private long maxBinaryMessageSize = -1;
private long maxTextMessageSize = -1;
public JettyWebSocketFrameHandler(Executor executor,
Object endpointInstance,
@ -85,7 +83,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler
MethodHandle frameHandle,
MethodHandle pingHandle, MethodHandle pongHandle,
CompletableFuture<Session> futureSession,
CoreCustomizer customizer)
Customizer customizer)
{
this.log = Log.getLogger(endpointInstance.getClass());
@ -130,7 +128,9 @@ public class JettyWebSocketFrameHandler implements FrameHandler
if (errorHandle == null)
{
log.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " missing onError handler", cause);
log.warn("Unhandled Error: Endpoint " + endpointInstance.getClass().getName() + " : " + cause);
if (log.isDebugEnabled())
log.debug("unhandled", cause);
return;
}
@ -228,14 +228,10 @@ public class JettyWebSocketFrameHandler implements FrameHandler
pongHandle = JettyWebSocketFrameHandlerFactory.bindTo(pongHandle, session);
if (textHandle != null)
{
textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, getMaxTextMessageSize());
}
textSink = JettyWebSocketFrameHandlerFactory.createMessageSink(textHandle, textSinkClass, executor, coreSession.getMaxTextMessageSize());
if (binaryHandle != null)
{
binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, getMaxBinaryMessageSize());
}
binarySink = JettyWebSocketFrameHandlerFactory.createMessageSink(binaryHandle, binarySinkClass, executor, coreSession.getMaxBinaryMessageSize());
if (openHandle != null)
{
@ -252,26 +248,6 @@ public class JettyWebSocketFrameHandler implements FrameHandler
futureSession.complete(session);
}
public long getMaxBinaryMessageSize()
{
return maxBinaryMessageSize;
}
public void setMaxBinaryMessageSize(long maxSize)
{
this.maxBinaryMessageSize = maxSize;
}
public long getMaxTextMessageSize()
{
return maxTextMessageSize;
}
public void setMaxTextMessageSize(long maxSize)
{
this.maxTextMessageSize = maxSize;
}
public String toString()
{
return String.format("%s@%x[%s]", this.getClass().getSimpleName(), this.hashCode(), endpointInstance.getClass().getName());

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.common;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
@ -44,8 +45,8 @@ import org.eclipse.jetty.websocket.common.message.ReaderMessageSink;
import org.eclipse.jetty.websocket.common.message.StringMessageSink;
import org.eclipse.jetty.websocket.common.util.ReflectUtils;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.FrameHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Annotation;
@ -56,28 +57,15 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
* Factory for websocket-core {@link FrameHandler} implementations suitable for
* use with jetty-native websocket API.
* <p>
* Will create a {@link FrameHandler} suitable for use with classes/objects that:
* </p>
* <ul>
* <li>Is &#64;{@link org.eclipse.jetty.websocket.api.annotations.WebSocket} annotated</li>
* <li>Extends {@link org.eclipse.jetty.websocket.api.WebSocketAdapter}</li>
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketListener}</li>
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketConnectionListener}</li>
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketPartialListener}</li>
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketPingPongListener}</li>
* <li>Implements {@link org.eclipse.jetty.websocket.api.WebSocketFrameListener}</li>
* </ul>
*/
public class JettyWebSocketFrameHandlerFactory
public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
{
private final Executor executor;
private Map<Class<?>, JettyWebSocketFrameHandlerMetadata> metadataMap = new ConcurrentHashMap<>();
@ -85,6 +73,7 @@ public class JettyWebSocketFrameHandlerFactory
public JettyWebSocketFrameHandlerFactory(Executor executor)
{
this.executor = executor;
addBean(executor);
}
public JettyWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass)
@ -156,13 +145,6 @@ public class JettyWebSocketFrameHandlerFactory
future,
metadata);
// TODO these are not attributes on the CoreSession, so we need another path to route them to the sinks that enforce them:
if (metadata.getMaxBinaryMessageSize() >= -1)
frameHandler.setMaxBinaryMessageSize(metadata.getMaxBinaryMessageSize());
if (metadata.getMaxTextMessageSize() >= -1)
frameHandler.setMaxTextMessageSize(metadata.getMaxTextMessageSize());
return frameHandler;
}
@ -298,10 +280,14 @@ public class JettyWebSocketFrameHandlerFactory
{
JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata();
metadata.setInputBufferSize(anno.inputBufferSize());
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
if (anno.inputBufferSize()>=0)
metadata.setInputBufferSize(anno.inputBufferSize());
if (anno.maxBinaryMessageSize()>=0)
metadata.setMaxBinaryMessageSize(anno.maxBinaryMessageSize());
if (anno.maxTextMessageSize()>=0)
metadata.setMaxTextMessageSize(anno.maxTextMessageSize());
metadata.setIdleTimeout(anno.maxIdleTime());
if (anno.maxIdleTime()>=0)
metadata.setIdleTimeout(Duration.ofMillis(anno.maxIdleTime()));
metadata.setBatchMode(anno.batchMode());
Method onmethod;
@ -486,4 +472,9 @@ public class JettyWebSocketFrameHandlerFactory
throw new InvalidSignatureException(err.toString());
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent, metadataMap);
}
}

View File

@ -25,7 +25,7 @@ import org.eclipse.jetty.websocket.core.FrameHandler;
import java.lang.invoke.MethodHandle;
import java.time.Duration;
public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCustomizer
public class JettyWebSocketFrameHandlerMetadata extends FrameHandler.ConfigurationCustomizer
{
private MethodHandle openHandle;
private MethodHandle closeHandle;
@ -41,12 +41,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
private MethodHandle pingHandle;
private MethodHandle pongHandle;
// Policy Configuration
private int idleTimeout = -1;
private int inputBufferSize = -1;
private int maxBinaryMessageSize = -1;
private int maxTextMessageSize = -1;
// Batch Configuration
// TODO remove?
private BatchMode batchMode = BatchMode.OFF;
@ -111,46 +105,6 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
return frameHandle;
}
public void setIdleTimeout(int idleTimeout)
{
this.idleTimeout = idleTimeout;
}
public int getIdleTimeout()
{
return idleTimeout;
}
public void setInputBufferSize(int inputBufferSize)
{
this.inputBufferSize = inputBufferSize;
}
public int getInputBufferSize()
{
return inputBufferSize;
}
public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
{
this.maxBinaryMessageSize = maxBinaryMessageSize;
}
public int getMaxBinaryMessageSize()
{
return maxBinaryMessageSize;
}
public void setMaxTextMessageSize(int maxTextMessageSize)
{
this.maxTextMessageSize = maxTextMessageSize;
}
public int getMaxTextMessageSize()
{
return maxTextMessageSize;
}
public void setOpenHandler(MethodHandle open, Object origin)
{
assertNotSet(this.openHandle, "OPEN Handler", origin);
@ -226,16 +180,4 @@ public class JettyWebSocketFrameHandlerMetadata implements FrameHandler.CoreCust
return obj.toString();
}
@Override
public void customize(FrameHandler.CoreSession session)
{
// Update passed in (unique) policy for this Frame Handler instance
if (getIdleTimeout() > 0)
session.setIdleTimeout(Duration.ofMillis(getIdleTimeout()));
if (getInputBufferSize() > 0)
session.setInputBufferSize(getInputBufferSize());
}
}

View File

@ -104,13 +104,13 @@ public class WebSocketSessionImpl implements Session
@Override
public long getMaxBinaryMessageSize()
{
return frameHandler.getMaxBinaryMessageSize();
return coreSession.getMaxBinaryMessageSize();
}
@Override
public long getMaxTextMessageSize()
{
return frameHandler.getMaxTextMessageSize();
return coreSession.getMaxTextMessageSize();
}
@Override
@ -134,13 +134,13 @@ public class WebSocketSessionImpl implements Session
@Override
public void setMaxBinaryMessageSize(long size)
{
frameHandler.setMaxBinaryMessageSize(size);
coreSession.setMaxBinaryMessageSize(size);
}
@Override
public void setMaxTextMessageSize(long size)
{
frameHandler.setMaxTextMessageSize(size);
coreSession.setMaxTextMessageSize(size);
}
@Override

View File

@ -28,6 +28,8 @@ module org.eclipse.jetty.websocket.jetty.server
requires javax.servlet.api;
requires org.eclipse.jetty.util;
requires org.eclipse.jetty.http;
requires org.eclipse.jetty.server;
requires org.eclipse.jetty.servlet;
requires org.eclipse.jetty.webapp;
requires org.eclipse.jetty.websocket.jetty.api;

View File

@ -18,20 +18,44 @@
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.server.internal.UpgradeRequestAdapter;
import org.eclipse.jetty.websocket.server.internal.UpgradeResponseAdapter;
import org.eclipse.jetty.websocket.servlet.FrameHandlerFactory;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFrameHandlerFactory;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class JettyWebSocketServletFrameHandlerFactory extends JettyWebSocketFrameHandlerFactory implements WebSocketServletFrameHandlerFactory
import javax.servlet.ServletContext;
public class JettyServerFrameHandlerFactory
extends JettyWebSocketFrameHandlerFactory
implements FrameHandlerFactory, LifeCycle.Listener
{
public JettyWebSocketServletFrameHandlerFactory(Executor executor)
public static JettyServerFrameHandlerFactory ensureFactory(ServletContext servletContext)
{
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
if (factory == null)
{
Executor executor = (Executor)servletContext
.getAttribute("org.eclipse.jetty.server.Executor");
if (executor == null)
executor = contextHandler.getServer().getThreadPool();
factory = new JettyServerFrameHandlerFactory(executor);
contextHandler.addManaged(factory);
contextHandler.addLifeCycleListener(factory);
}
return factory;
}
public JettyServerFrameHandlerFactory(Executor executor)
{
super(executor);
}
@ -42,4 +66,16 @@ public class JettyWebSocketServletFrameHandlerFactory extends JettyWebSocketFram
return super.newJettyFrameHandler(websocketPojo, new UpgradeRequestAdapter(upgradeRequest), new UpgradeResponseAdapter(upgradeResponse),
new CompletableFuture<>());
}
@Override
public void lifeCycleStopping(LifeCycle context)
{
ContextHandler contextHandler = (ContextHandler) context;
JettyServerFrameHandlerFactory factory = contextHandler.getBean(JettyServerFrameHandlerFactory.class);
if (factory != null)
{
contextHandler.removeBean(factory);
LifeCycle.stop(factory);
}
}
}

View File

@ -18,10 +18,16 @@
package org.eclipse.jetty.websocket.server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
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.WebSocketServletFrameHandlerFactory;
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;
@ -37,6 +43,8 @@ import java.util.concurrent.Executor;
*/
public class JettyWebSocketServletContainerInitializer implements ServletContainerInitializer
{
private static final Logger LOG = Log.getLogger(JettyWebSocketServletContainerInitializer.class);
public static class JettyWebSocketEmbeddedStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
{
private ServletContainerInitializer sci;
@ -62,7 +70,7 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain
}
}
public static void configure(ServletContextHandler contextHandler)
public static void configure(ServletContextHandler contextHandler) throws ServletException
{
JettyWebSocketServletContainerInitializer sci = new JettyWebSocketServletContainerInitializer();
JettyWebSocketEmbeddedStarter starter = new JettyWebSocketEmbeddedStarter(sci, contextHandler);
@ -70,33 +78,13 @@ public class JettyWebSocketServletContainerInitializer implements ServletContain
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
public void onStartup(Set<Class<?>> c, ServletContext servletContext) throws ServletException
{
// TODO why doesn't the javax side share this approach?
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx
.getAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS);
if (factories == null)
{
factories = new ArrayList<>();
ctx.setAttribute(WebSocketServletFrameHandlerFactory.ATTR_HANDLERS, factories);
}
WebSocketMapping mapping = WebSocketMapping.ensureMapping(servletContext);
FilterHolder upgradeFilter = WebSocketUpgradeFilter.ensureFilter(servletContext);
JettyServerFrameHandlerFactory factory = JettyServerFrameHandlerFactory.ensureFactory(servletContext);
Executor executor = (Executor)ctx.getAttribute("org.eclipse.jetty.server.Executor");
if (executor == null)
{
try
{
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("Jetty-WebSocketServer");
threadPool.start();
executor = threadPool;
}
catch (Exception e)
{
throw new ServletException("Unable to start internal Executor", e);
}
}
factories.add(new JettyWebSocketServletFrameHandlerFactory(executor));
if (LOG.isDebugEnabled())
LOG.debug("onStartup {} {} {}",mapping, upgradeFilter, factory);
}
}

View File

@ -125,10 +125,53 @@ public interface FrameHandler extends IncomingFrames
return false;
}
interface Configuration
{
/**
* Get the Idle Timeout
*
* @return the idle timeout
*/
Duration getIdleTimeout();
/**
* Set the Idle Timeout.
*
* @param timeout the timeout duration
*/
void setIdleTimeout(Duration timeout);
boolean isAutoFragment();
void setAutoFragment(boolean autoFragment);
long getMaxFrameSize();
void setMaxFrameSize(long maxFrameSize);
int getOutputBufferSize();
void setOutputBufferSize(int outputBufferSize);
int getInputBufferSize();
void setInputBufferSize(int inputBufferSize);
long getMaxBinaryMessageSize();
void setMaxBinaryMessageSize(long maxSize);
long getMaxTextMessageSize();
void setMaxTextMessageSize(long maxSize);
}
/**
* Represents the outgoing Frames.
*/
interface CoreSession extends OutgoingFrames
interface CoreSession extends OutgoingFrames, Configuration
{
/**
* The negotiated WebSocket Sub-Protocol for this channel.
@ -229,20 +272,6 @@ public interface FrameHandler extends IncomingFrames
*/
boolean isOpen();
/**
* Get the Idle Timeout
*
* @return the idle timeout
*/
Duration getIdleTimeout();
/**
* Set the Idle Timeout.
*
* @param timeout the timeout duration
*/
void setIdleTimeout(Duration timeout);
/**
* If using BatchMode.ON or BatchMode.AUTO, trigger a flush of enqueued / batched frames.
*
@ -277,22 +306,6 @@ public interface FrameHandler extends IncomingFrames
*/
void demand(long n);
boolean isAutoFragment();
void setAutoFragment(boolean autoFragment);
long getMaxFrameSize();
void setMaxFrameSize(long maxFrameSize);
int getOutputBufferSize();
void setOutputBufferSize(int outputBufferSize);
int getInputBufferSize();
void setInputBufferSize(int inputBufferSize);
class Empty implements CoreSession
{
@Override
@ -375,31 +388,26 @@ public interface FrameHandler extends IncomingFrames
@Override
public void setIdleTimeout(Duration timeout)
{
}
@Override
public void flush(Callback callback)
{
}
@Override
public void close(Callback callback)
{
}
@Override
public void close(int statusCode, String reason, Callback callback)
{
}
@Override
public void demand(long n)
{
}
@Override
@ -411,7 +419,6 @@ public interface FrameHandler extends IncomingFrames
@Override
public void setAutoFragment(boolean autoFragment)
{
}
@Override
@ -423,7 +430,6 @@ public interface FrameHandler extends IncomingFrames
@Override
public void setMaxFrameSize(long maxFrameSize)
{
}
@Override
@ -435,7 +441,6 @@ public interface FrameHandler extends IncomingFrames
@Override
public void setOutputBufferSize(int outputBufferSize)
{
}
@Override
@ -447,19 +452,154 @@ public interface FrameHandler extends IncomingFrames
@Override
public void setInputBufferSize(int inputBufferSize)
{
}
@Override
public void sendFrame(Frame frame, Callback callback, boolean batch)
{
}
@Override
public long getMaxBinaryMessageSize()
{
return 0;
}
@Override
public void setMaxBinaryMessageSize(long maxSize)
{
}
@Override
public long getMaxTextMessageSize()
{
return 0;
}
@Override
public void setMaxTextMessageSize(long maxSize)
{
}
}
}
interface CoreCustomizer
interface Customizer
{
void customize(CoreSession session);
}
class ConfigurationCustomizer implements Customizer, Configuration
{
private Duration timeout;
private Boolean autoFragment;
private Long maxFrameSize;
private Integer outputBufferSize;
private Integer inputBufferSize;
private Long maxBinaryMessageSize;
private Long maxTextMessageSize;
@Override
public Duration getIdleTimeout()
{
return timeout;
}
@Override
public void setIdleTimeout(Duration timeout)
{
this.timeout = timeout;
}
@Override
public boolean isAutoFragment()
{
return autoFragment==null?WebSocketConstants.DEFAULT_AUTO_FRAGMENT:autoFragment;
}
@Override
public void setAutoFragment(boolean autoFragment)
{
this.autoFragment = autoFragment;
}
@Override
public long getMaxFrameSize()
{
return maxFrameSize==null?WebSocketConstants.DEFAULT_MAX_FRAME_SIZE:maxFrameSize;
}
@Override
public void setMaxFrameSize(long maxFrameSize)
{
this.maxFrameSize = maxFrameSize;
}
@Override
public int getOutputBufferSize()
{
return outputBufferSize==null?WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE:outputBufferSize;
}
@Override
public void setOutputBufferSize(int outputBufferSize)
{
this.outputBufferSize = outputBufferSize;
}
@Override
public int getInputBufferSize()
{
return inputBufferSize==null?WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE:inputBufferSize;
}
@Override
public void setInputBufferSize(int inputBufferSize)
{
this.inputBufferSize = inputBufferSize;
}
@Override
public long getMaxBinaryMessageSize()
{
return maxBinaryMessageSize==null?WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE:maxBinaryMessageSize;
}
@Override
public void setMaxBinaryMessageSize(long maxBinaryMessageSize)
{
this.maxBinaryMessageSize = maxBinaryMessageSize;
}
@Override
public long getMaxTextMessageSize()
{
return maxTextMessageSize==null?WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE:maxTextMessageSize;
}
@Override
public void setMaxTextMessageSize(long maxTextMessageSize)
{
this.maxTextMessageSize = maxTextMessageSize;
}
@Override
public void customize(CoreSession session)
{
if (timeout!=null)
session.setIdleTimeout(timeout);
if (autoFragment!=null)
session.setAutoFragment(autoFragment);
if (maxFrameSize!=null)
session.setMaxFrameSize(maxFrameSize);
if (inputBufferSize!=null)
session.setInputBufferSize(inputBufferSize);
if (outputBufferSize!=null)
session.setOutputBufferSize(outputBufferSize);
if (maxBinaryMessageSize!=null)
session.setMaxBinaryMessageSize(maxBinaryMessageSize);
if (maxTextMessageSize!=null)
session.setMaxTextMessageSize(maxTextMessageSize);
}
}
}

View File

@ -33,14 +33,14 @@ import org.eclipse.jetty.websocket.core.ExtensionConfig;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.CoreCustomizer
public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHandler.Customizer
{
private static final Logger LOG = Log.getLogger(WebSocketCoreClient.class);
private final HttpClient httpClient;
private WebSocketExtensionRegistry extensionRegistry;
private DecoratedObjectFactory objectFactory;
private final FrameHandler.CoreCustomizer customizer;
private final FrameHandler.Customizer customizer;
// TODO: Things to consider for inclusion in this class (or removal if they can be set elsewhere, like HttpClient)
// - AsyncWrite Idle Timeout
@ -59,7 +59,7 @@ public class WebSocketCoreClient extends ContainerLifeCycle implements FrameHand
this(httpClient, null);
}
public WebSocketCoreClient(HttpClient httpClient, FrameHandler.CoreCustomizer customizer)
public WebSocketCoreClient(HttpClient httpClient, FrameHandler.Customizer customizer)
{
if (httpClient==null)
{

View File

@ -70,6 +70,8 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
private long maxFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE;
private int inputBufferSize = WebSocketConstants.DEFAULT_INPUT_BUFFER_SIZE;
private int outputBufferSize = WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE;
private long maxBinaryMessageSize = WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE;
private long maxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE;
public WebSocketChannel(FrameHandler handler,
Behavior behavior,
@ -576,6 +578,30 @@ public class WebSocketChannel implements IncomingFrames, FrameHandler.CoreSessio
this.inputBufferSize = inputBufferSize;
}
@Override
public long getMaxBinaryMessageSize()
{
return maxBinaryMessageSize;
}
@Override
public void setMaxBinaryMessageSize(long maxSize)
{
maxBinaryMessageSize = maxSize;
}
@Override
public long getMaxTextMessageSize()
{
return maxTextMessageSize;
}
@Override
public void setMaxTextMessageSize(long maxSize)
{
maxTextMessageSize = maxSize;
}
private class IncomingState extends FrameSequence implements IncomingFrames
{
@Override

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.websocket.core.server;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.core.server.internal.RFC6455Handshaker;
import javax.servlet.http.HttpServletRequest;
@ -31,5 +32,10 @@ public interface Handshaker
return new RFC6455Handshaker();
}
boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response) throws IOException;
boolean upgradeRequest(
WebSocketNegotiator negotiator,
HttpServletRequest request,
HttpServletResponse response,
FrameHandler.Customizer defaultCustomizer)
throws IOException;
}

View File

@ -26,7 +26,7 @@ import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import java.io.IOException;
import java.util.function.Function;
public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
public interface WebSocketNegotiator extends FrameHandler.Customizer
{
FrameHandler negotiate(Negotiation negotiation) throws IOException;
@ -48,7 +48,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
};
}
static WebSocketNegotiator from(Function<Negotiation, FrameHandler> negotiate, FrameHandler.CoreCustomizer customizer)
static WebSocketNegotiator from(Function<Negotiation, FrameHandler> negotiate, FrameHandler.Customizer customizer)
{
return new AbstractNegotiator(null, null, null, customizer)
{
@ -65,7 +65,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
WebSocketExtensionRegistry extensionRegistry,
DecoratedObjectFactory objectFactory,
ByteBufferPool bufferPool,
FrameHandler.CoreCustomizer customizer)
FrameHandler.Customizer customizer)
{
return new AbstractNegotiator(extensionRegistry, objectFactory, bufferPool, customizer)
{
@ -82,7 +82,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
final WebSocketExtensionRegistry extensionRegistry;
final DecoratedObjectFactory objectFactory;
final ByteBufferPool bufferPool;
final FrameHandler.CoreCustomizer customizer;
final FrameHandler.Customizer customizer;
public AbstractNegotiator()
{
@ -93,7 +93,7 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
WebSocketExtensionRegistry extensionRegistry,
DecoratedObjectFactory objectFactory,
ByteBufferPool bufferPool,
FrameHandler.CoreCustomizer customizer)
FrameHandler.Customizer customizer)
{
this.extensionRegistry = extensionRegistry == null?new WebSocketExtensionRegistry():extensionRegistry;
this.objectFactory = objectFactory == null?new DecoratedObjectFactory():objectFactory;
@ -125,5 +125,10 @@ public interface WebSocketNegotiator extends FrameHandler.CoreCustomizer
{
return bufferPool;
}
public FrameHandler.Customizer getCustomizer()
{
return customizer;
}
}
}

View File

@ -74,7 +74,7 @@ public class WebSocketUpgradeHandler extends HandlerWrapper
return;
}
if (handshaker.upgradeRequest(negotiator, request, response))
if (handshaker.upgradeRequest(negotiator, request, response, null))
return;
if (!baseRequest.isHandled())

View File

@ -58,7 +58,9 @@ public final class RFC6455Handshaker implements Handshaker
private static final HttpField CONNECTION_UPGRADE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeader.UPGRADE.asString());
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
public boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request, HttpServletResponse response) throws IOException
public boolean upgradeRequest(WebSocketNegotiator negotiator, HttpServletRequest request,
HttpServletResponse response,
FrameHandler.Customizer defaultCustomizer) throws IOException
{
Request baseRequest = Request.getBaseRequest(request);
HttpChannel httpChannel = baseRequest.getHttpChannel();
@ -192,7 +194,8 @@ public final class RFC6455Handshaker implements Handshaker
}
channel.setWebSocketConnection(connection);
if (defaultCustomizer!=null)
defaultCustomizer.customize(channel);
negotiator.customize(channel);
// send upgrade response

View File

@ -23,6 +23,7 @@ module org.eclipse.jetty.websocket.servlet
requires javax.servlet.api;
requires org.eclipse.jetty.util;
requires org.eclipse.jetty.http;
requires org.eclipse.jetty.server;
requires org.eclipse.jetty.io;
requires org.eclipse.jetty.servlet;
requires org.eclipse.jetty.websocket.core;

View File

@ -20,24 +20,18 @@ package org.eclipse.jetty.websocket.servlet;
import org.eclipse.jetty.websocket.core.FrameHandler;
/**
* WebSocket Core API - Factory for Servlet based API's to use for creating API specific FrameHandler instances that
* websocket-core will eventually utilize.
* <p>
* This is used by Servlet based APIs only.
* </p>
*/
public interface WebSocketServletFrameHandlerFactory
{
String ATTR_HANDLERS = "org.eclipse.jetty.websocket.servlet.FrameHandlerFactories";
public interface FrameHandlerFactory
{
/**
* Attempt to create a FrameHandler from the provided websocketPojo.
* Create a FrameHandler from the provided websocketPojo.
*
* @param websocketPojo the websocket pojo to work with
* @param upgradeRequest the Upgrade Handshake Request used to create the FrameHandler
* @param upgradeResponse the Upgrade Handshake Response used to create the FrameHandler
* @return the API specific FrameHandler, or null if this implementation is unable to create the FrameHandler (allowing another {@link WebSocketServletFrameHandlerFactory} to try)
* @return the API specific FrameHandler, or null if this implementation is unable to create
* the FrameHandler (allowing another {@link FrameHandlerFactory} to try)
*/
FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest, ServletUpgradeResponse upgradeResponse);
FrameHandler newFrameHandler(Object websocketPojo, ServletUpgradeRequest upgradeRequest,
ServletUpgradeResponse upgradeResponse);
}

View File

@ -130,14 +130,6 @@ public class ServletUpgradeRequest
return requestURI.getHost();
}
/**
* Return the underlying HttpServletRequest that existed at Upgrade time.
* <p>
* Note: many features of the HttpServletRequest are invalid when upgraded,
* especially ones that deal with body content, streams, readers, and responses.
*
* @return a limited version of the underlying HttpServletRequest
*/
public HttpServletRequest getHttpServletRequest()
{
return request;
@ -148,33 +140,16 @@ public class ServletUpgradeRequest
return request.getProtocol();
}
/**
* Equivalent to {@link HttpServletRequest#getLocale()}
*
* @return the preferred <code>Locale</code> for the client
*/
public Locale getLocale()
{
return request.getLocale();
}
/**
* Equivalent to {@link HttpServletRequest#getLocales()}
*
* @return an Enumeration of preferred Locale objects
*/
public Enumeration<Locale> getLocales()
{
return request.getLocales();
}
/**
* Return a {@link java.net.SocketAddress} for the local socket.
* <p>
* Warning: this can cause a DNS lookup
*
* @return the local socket address
*/
public SocketAddress getLocalSocketAddress()
{
// TODO: fix when HttpServletRequest can use Unix Socket stuff
@ -221,13 +196,6 @@ public class ServletUpgradeRequest
return this.queryString;
}
/**
* Return a {@link SocketAddress} for the remote socket.
* <p>
* Warning: this can cause a DNS lookup
*
* @return the remote socket address
*/
public SocketAddress getRemoteSocketAddress()
{
return new InetSocketAddress(request.getRemoteAddr(), request.getRemotePort());
@ -263,12 +231,6 @@ public class ServletUpgradeRequest
return getParameterMap();
}
/**
* Return the HttpSession if it exists.
* <p>
* Note: this is equivalent to {@link HttpServletRequest#getSession(boolean)}
* and will not create a new HttpSession.
*/
public HttpSession getSession()
{
return request.getSession(false);
@ -279,9 +241,6 @@ public class ServletUpgradeRequest
return negotiation.getOfferedSubprotocols();
}
/**
* Equivalent to {@link HttpServletRequest#getUserPrincipal()}
*/
public Principal getUserPrincipal()
{
return request.getUserPrincipal();

View File

@ -23,7 +23,7 @@ package org.eclipse.jetty.websocket.servlet;
* <p>
* Should you desire filtering of the WebSocket object creation due to criteria such as origin or sub-protocol, then you will be required to implement a custom
* WebSocketCreator implementation.
* <p>
* </p>
*/
public interface WebSocketCreator
{

View File

@ -1,369 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.servlet;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.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.util.DecoratedObjectFactory;
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.WebSocketConstants;
import org.eclipse.jetty.websocket.core.WebSocketExtensionRegistry;
import org.eclipse.jetty.websocket.core.server.Negotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
/**
*/
public class WebSocketCreatorMapping implements Dumpable, FrameHandler.CoreCustomizer, WebSocketServletFactory
{
private static final Logger LOG = Log.getLogger(WebSocketCreatorMapping.class);
private final PathMappings<CreatorNegotiator> mappings = new PathMappings<>();
private final Set<WebSocketServletFrameHandlerFactory> frameHandlerFactories = new HashSet<>();
private Duration defaultIdleTimeout;
private int defaultInputBufferSize;
private long defaultMaxBinaryMessageSize = WebSocketConstants.DEFAULT_MAX_BINARY_MESSAGE_SIZE;
private long defaultMaxTextMessageSize = WebSocketConstants.DEFAULT_MAX_TEXT_MESSAGE_SIZE;
private long defaultMaxAllowedFrameSize = WebSocketConstants.DEFAULT_MAX_FRAME_SIZE;
private int defaultOutputBufferSize = WebSocketConstants.DEFAULT_OUTPUT_BUFFER_SIZE;
private boolean defaultAutoFragment = WebSocketConstants.DEFAULT_AUTO_FRAGMENT;
private DecoratedObjectFactory objectFactory;
private ClassLoader contextClassLoader;
private WebSocketExtensionRegistry extensionRegistry;
private ByteBufferPool bufferPool;
public WebSocketCreatorMapping()
{
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
}
public WebSocketCreatorMapping(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
{
this.extensionRegistry = extensionRegistry;
this.objectFactory = objectFactory;
this.bufferPool = bufferPool;
}
/**
* Manually add a WebSocket mapping.
* <p>
* If mapping is added before this configuration is started, then it is persisted through
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
* this configuration is stopped.
* </p>
*
* @param pathSpec the pathspec to respond on
* @param creator the websocket creator to activate on the provided mapping.
*/
public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
{
// Handling for response forbidden (and similar paths)
// no creation, sorry
// No factory worked!
mappings.put(pathSpec, new CreatorNegotiator(creator));
}
@Override
public WebSocketCreator getMapping(PathSpec pathSpec)
{
CreatorNegotiator cn = mappings.get(pathSpec);
return cn == null?null:cn.getWebSocketCreator();
}
@Override
public WebSocketCreator getMatch(String target)
{
MappedResource<CreatorNegotiator> resource = mappings.getMatch(target);
return resource == null?null:resource.getResource().getWebSocketCreator();
}
@Override
public boolean removeMapping(PathSpec pathSpec)
{
return mappings.remove(pathSpec);
}
@Override
public String dump()
{
return Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
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 Duration getDefaultIdleTimeout()
{
return defaultIdleTimeout;
}
public WebSocketExtensionRegistry getExtensionRegistry()
{
return this.extensionRegistry;
}
public DecoratedObjectFactory getObjectFactory()
{
return this.objectFactory;
}
public void addFrameHandlerFactory(WebSocketServletFrameHandlerFactory webSocketServletFrameHandlerFactory)
{
// TODO should this be done by a ServiceLoader?
this.frameHandlerFactories.add(webSocketServletFrameHandlerFactory);
}
public void setDefaultIdleTimeout(Duration duration)
{
this.defaultIdleTimeout = duration;
}
public int getDefaultInputBufferSize()
{
return defaultInputBufferSize;
}
public void setDefaultInputBufferSize(int bufferSize)
{
this.defaultInputBufferSize = bufferSize;
}
public long getDefaultMaxAllowedFrameSize()
{
return this.defaultMaxAllowedFrameSize;
}
public void setDefaultMaxAllowedFrameSize(long maxFrameSize)
{
this.defaultMaxAllowedFrameSize = maxFrameSize;
}
public long getDefaultMaxBinaryMessageSize()
{
return defaultMaxBinaryMessageSize;
}
public void setDefaultMaxBinaryMessageSize(long bufferSize)
{
this.defaultMaxBinaryMessageSize = bufferSize;
}
public long getDefaultMaxTextMessageSize()
{
return defaultMaxTextMessageSize;
}
public void setDefaultMaxTextMessageSize(long bufferSize)
{
this.defaultMaxTextMessageSize = bufferSize;
}
public int getDefaultOutputBufferSize()
{
return this.defaultOutputBufferSize;
}
public void setDefaultOutputBufferSize(int bufferSize)
{
this.defaultOutputBufferSize = bufferSize;
}
public boolean isAutoFragment()
{
return this.defaultAutoFragment;
}
public void setAutoFragment(boolean autoFragment)
{
this.defaultAutoFragment = autoFragment;
}
/**
* Get the matching {@link MappedResource} for the provided target.
*
* @param target the target path
* @return the matching resource, or null if no match.
*/
public WebSocketNegotiator getMatchedNegotiator(String target, Consumer<PathSpec> pathSpecConsumer)
{
MappedResource<CreatorNegotiator> mapping = this.mappings.getMatch(target);
if (mapping == null)
return null;
pathSpecConsumer.accept(mapping.getPathSpec());
return mapping.getResource();
}
/**
* Parse a PathSpec string into a PathSpec instance.
* <p>
* Recognized Path Spec syntaxes:
* </p>
* <dl>
* <dt><code>/path/to</code> or <code>/</code> or <code>*.ext</code> or <code>servlet|{spec}</code></dt>
* <dd>Servlet Syntax</dd>
* <dt><code>^{spec}</code> or <code>regex|{spec}</code></dt>
* <dd>Regex Syntax</dd>
* <dt><code>uri-template|{spec}</code></dt>
* <dd>URI Template (see JSR356 and RFC6570 level 1)</dd>
* </dl>
*
* @param rawSpec the raw path spec as String to parse.
* @return the {@link PathSpec} implementation for the rawSpec
*/
public PathSpec parsePathSpec(String rawSpec)
{
// Determine what kind of path spec we are working with
if (rawSpec.charAt(0) == '/' || rawSpec.startsWith("*.") || rawSpec.startsWith("servlet|"))
{
return new ServletPathSpec(rawSpec);
}
else if (rawSpec.charAt(0) == '^' || rawSpec.startsWith("regex|"))
{
return new RegexPathSpec(rawSpec);
}
else if (rawSpec.startsWith("uri-template|"))
{
return new UriTemplatePathSpec(rawSpec.substring("uri-template|".length()));
}
// TODO: add ability to load arbitrary jetty-http PathSpec implementation
// TODO: perhaps via "fully.qualified.class.name|spec" style syntax
throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]");
}
@Override
public void customize(FrameHandler.CoreSession session)
{
session.setIdleTimeout(getDefaultIdleTimeout());
session.setAutoFragment(isAutoFragment());
session.setInputBufferSize(getDefaultInputBufferSize());
session.setOutputBufferSize(getDefaultOutputBufferSize());
session.setMaxFrameSize(getDefaultMaxAllowedFrameSize());
}
private class CreatorNegotiator extends WebSocketNegotiator.AbstractNegotiator
{
private final WebSocketCreator creator;
public CreatorNegotiator(WebSocketCreator creator)
{
super(WebSocketCreatorMapping.this.getExtensionRegistry(), WebSocketCreatorMapping.this.getObjectFactory(),
WebSocketCreatorMapping.this.getBufferPool(),
WebSocketCreatorMapping.this);
this.creator = creator;
}
public WebSocketCreator getWebSocketCreator()
{
return creator;
}
@Override
public FrameHandler negotiate(Negotiation negotiation1)
{
return ((Function<Negotiation, FrameHandler>)negotiation ->
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(getContextClassloader());
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
Object websocketPojo = creator.createWebSocket(upgradeRequest, upgradeResponse);
// Handling for response forbidden (and similar paths)
if (upgradeResponse.isCommitted())
return null;
if (websocketPojo == null)
{
// no creation, sorry
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
return null;
}
for (WebSocketServletFrameHandlerFactory factory : frameHandlerFactories)
{
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
if (frameHandler != null)
return frameHandler;
}
if (frameHandlerFactories.isEmpty())
LOG.warn("There are no {} instances registered", WebSocketServletFrameHandlerFactory.class);
// No factory worked!
return null;
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
catch (URISyntaxException e)
{
throw new RuntimeIOException("Unable to negotiate websocket due to mangled request URI", e);
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}).apply(negotiation1);
}
}
}

View File

@ -0,0 +1,362 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.servlet;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.RegexPathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.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.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
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.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.Negotiation;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Mapping of pathSpec to a tupple of {@link WebSocketCreator}, {@link FrameHandlerFactory} and
* {@link org.eclipse.jetty.websocket.core.FrameHandler.Customizer}.
* <p>
* When the {@link #upgrade(HttpServletRequest, HttpServletResponse, FrameHandler.Customizer)}
* method is called, a match for the pathSpec is looked for. If one is found then the
* creator is used to create a POJO for the WebSocket endpoint, the factory is used to
* wrap that POJO with a {@link FrameHandler} and the customizer is used to configure the resulting
* {@link FrameHandler.CoreSession}.</p>
*/
public class WebSocketMapping implements Dumpable, LifeCycle.Listener
{
private static final Logger LOG = Log.getLogger(WebSocketMapping.class);
public static WebSocketMapping ensureMapping(ServletContext servletContext) throws ServletException
{
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
// Ensure a mapping exists
WebSocketMapping mapping = contextHandler.getBean(WebSocketMapping.class);
if (mapping == null)
{
mapping = new WebSocketMapping();
mapping.setContextClassLoader(servletContext.getClassLoader());
contextHandler.addBean(mapping);
contextHandler.addLifeCycleListener(mapping);
}
return mapping;
}
private final PathMappings<Negotiator> mappings = new PathMappings<>();
private final Set<FrameHandlerFactory> frameHandlerFactories = new HashSet<>();
private final Handshaker handshaker = Handshaker.newInstance();
private DecoratedObjectFactory objectFactory;
private ClassLoader contextClassLoader;
private WebSocketExtensionRegistry extensionRegistry;
private ByteBufferPool bufferPool;
public WebSocketMapping()
{
this(new WebSocketExtensionRegistry(), new DecoratedObjectFactory(), new MappedByteBufferPool());
}
public WebSocketMapping(WebSocketExtensionRegistry extensionRegistry, DecoratedObjectFactory objectFactory, ByteBufferPool bufferPool)
{
this.extensionRegistry = extensionRegistry;
this.objectFactory = objectFactory;
this.bufferPool = bufferPool;
}
@Override
public void lifeCycleStopping(LifeCycle context)
{
ContextHandler contextHandler = (ContextHandler) context;
WebSocketMapping mapping = contextHandler.getBean(WebSocketMapping.class);
if (mapping == null)
contextHandler.removeBean(mapping);
}
/**
* Manually add a WebSocket mapping.
* <p>
* If mapping is added before this configuration is started, then it is persisted through
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
* this configuration is stopped.
* </p>
*
* @param pathSpec the pathspec to respond on
* @param creator the websocket creator to activate on the provided mapping.
* @param factory the factory to use to create a FrameHandler for the websocket
* @param customizer the customizer to use to customize the WebSocket session.
*/
public void addMapping(PathSpec pathSpec, WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
{
// Handling for response forbidden (and similar paths)
// no creation, sorry
// No factory worked!
mappings.put(pathSpec, new Negotiator(creator, factory, customizer));
}
public WebSocketCreator getMapping(PathSpec pathSpec)
{
Negotiator cn = mappings.get(pathSpec);
return cn == null?null:cn.getWebSocketCreator();
}
public boolean removeMapping(PathSpec pathSpec)
{
return mappings.remove(pathSpec);
}
@Override
public String dump()
{
return Dumpable.dump(this);
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
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;
}
public void addFrameHandlerFactory(FrameHandlerFactory webSocketServletFrameHandlerFactory)
{
// TODO should this be done by a ServiceLoader?
this.frameHandlerFactories.add(webSocketServletFrameHandlerFactory);
}
/**
* Get the matching {@link MappedResource} for the provided target.
*
* @param target the target path
* @return the matching resource, or null if no match.
*/
public WebSocketNegotiator getMatchedNegotiator(String target, Consumer<PathSpec> pathSpecConsumer)
{
MappedResource<Negotiator> mapping = this.mappings.getMatch(target);
if (mapping == null)
return null;
pathSpecConsumer.accept(mapping.getPathSpec());
return mapping.getResource();
}
/**
* Parse a PathSpec string into a PathSpec instance.
* <p>
* Recognized Path Spec syntaxes:
* </p>
* <dl>
* <dt><code>/path/to</code> or <code>/</code> or <code>*.ext</code> or <code>servlet|{spec}</code></dt>
* <dd>Servlet Syntax</dd>
* <dt><code>^{spec}</code> or <code>regex|{spec}</code></dt>
* <dd>Regex Syntax</dd>
* <dt><code>uri-template|{spec}</code></dt>
* <dd>URI Template (see JSR356 and RFC6570 level 1)</dd>
* </dl>
*
* @param rawSpec the raw path spec as String to parse.
* @return the {@link PathSpec} implementation for the rawSpec
*/
public static PathSpec parsePathSpec(String rawSpec)
{
// Determine what kind of path spec we are working with
if (rawSpec.charAt(0) == '/' || rawSpec.startsWith("*.") || rawSpec.startsWith("servlet|"))
{
return new ServletPathSpec(rawSpec);
}
else if (rawSpec.charAt(0) == '^' || rawSpec.startsWith("regex|"))
{
return new RegexPathSpec(rawSpec);
}
else if (rawSpec.startsWith("uri-template|"))
{
return new UriTemplatePathSpec(rawSpec.substring("uri-template|".length()));
}
// TODO: add ability to load arbitrary jetty-http PathSpec implementation
// TODO: perhaps via "fully.qualified.class.name|spec" style syntax
throw new IllegalArgumentException("Unrecognized path spec syntax [" + rawSpec + "]");
}
public boolean upgrade(HttpServletRequest request, HttpServletResponse response, FrameHandler.Customizer defaultCustomizer)
{
try
{
// Since this may be a filter, we need to be smart about determining the target path.
// We should rely on the Container for stripping path parameters and its ilk before
// attempting to match a specific mapped websocket creator.
String target = request.getServletPath();
if (request.getPathInfo() != null)
target = target + request.getPathInfo();
WebSocketNegotiator negotiator = getMatchedNegotiator(target, pathSpec ->
{
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
// implementors to use later if they wish
request.setAttribute(PathSpec.class.getName(), pathSpec);
});
if (negotiator == null)
return false;
if (LOG.isDebugEnabled())
LOG.debug("WebSocket Negotiated detected on {} for endpoint {}", target, negotiator);
// We have an upgrade request
return handshaker.upgradeRequest(negotiator, request, response, defaultCustomizer);
}
catch (Exception e)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to upgrade: "+e);
LOG.ignore(e);
}
return false;
}
private class Negotiator extends WebSocketNegotiator.AbstractNegotiator
{
private final WebSocketCreator creator;
private final FrameHandlerFactory factory;
public Negotiator(WebSocketCreator creator, FrameHandlerFactory factory, FrameHandler.Customizer customizer)
{
super(WebSocketMapping.this.getExtensionRegistry(), WebSocketMapping.this.getObjectFactory(),
WebSocketMapping.this.getBufferPool(),
customizer);
this.creator = creator;
this.factory = factory;
}
public WebSocketCreator getWebSocketCreator()
{
return creator;
}
@Override
public FrameHandler negotiate(Negotiation negotiation)
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(getContextClassloader());
ServletUpgradeRequest upgradeRequest = new ServletUpgradeRequest(negotiation);
ServletUpgradeResponse upgradeResponse = new ServletUpgradeResponse(negotiation);
Object websocketPojo = creator.createWebSocket(upgradeRequest, upgradeResponse);
// Handling for response forbidden (and similar paths)
if (upgradeResponse.isCommitted())
return null;
if (websocketPojo == null)
{
// no creation, sorry
upgradeResponse.sendError(SC_SERVICE_UNAVAILABLE, "WebSocket Endpoint Creation Refused");
return null;
}
FrameHandler frameHandler = factory.newFrameHandler(websocketPojo, upgradeRequest, upgradeResponse);
if (frameHandler != null)
return frameHandler;
return null;
}
catch (IOException e)
{
throw new RuntimeIOException(e);
}
catch (URISyntaxException e)
{
throw new RuntimeIOException("Unable to negotiate websocket due to mangled request URI", e);
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
}
@Override
public String toString()
{
return String.format("%s@%x{%s,%s,%s}",getClass().getSimpleName(), hashCode(), creator, factory, getCustomizer());
}
}
}

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.servlet;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -29,19 +28,18 @@ 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.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.server.Handshaker;
import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
import org.eclipse.jetty.websocket.core.FrameHandler;
/**
* Abstract Servlet used to bridge the Servlet API to the WebSocket API.
* <p>
* To use this servlet, you will be required to register your websockets with the {@link WebSocketCreatorMapping} so that it can create your websockets under the
* To use this servlet, you will be required to register your websockets with the {@link WebSocketMapping} so that it can create your websockets under the
* appropriate conditions.
* <p>
* The most basic implementation would be as follows.
* <p>
* </p>
* <p>The most basic implementation would be as follows:</p>
* <pre>
* package my.example;
*
@ -53,106 +51,97 @@ import org.eclipse.jetty.websocket.core.server.WebSocketNegotiator;
* &#064;Override
* public void configure(WebSocketServletFactory factory)
* {
* // set a 10 second idle timeout
* factory.getPolicy().setIdleTimeout(10000);
* // register my socket
* factory.register(MyEchoSocket.class);
* factory.setDefaultMaxFrameSize(4096);
* factory.addMapping(factory.parsePathSpec("/"), (req,res)-&gt;new EchoSocket());
* }
* }
* </pre>
* <p>
* Note: that only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketCreatorMapping} handling of creating
* WebSockets.<br>
* All other requests are treated as normal servlet requests.
* <p>
* <p>
* <b>Configuration / Init-Parameters:</b><br>
* Only request that conforms to a "WebSocket: Upgrade" handshake request will trigger the {@link WebSocketMapping} handling of creating
* WebSockets. All other requests are treated as normal servlet requests. The configuration defined by this servlet init parameters will
* be used as the customizer for any mappings created by {@link WebSocketServletFactory#addMapping(PathSpec, WebSocketCreator)} during
* {@link #configure(WebSocketServletFactory)} calls. The request upgrade may be peformed by this servlet, or is may be performed by a
* {@link WebSocketUpgradeFilter} instance that will share the same {@link WebSocketMapping} instance. If the filter is used, then the
* filter configuraton is used as the default configuration prior to this servlets configuration being applied.
* </p>
* <p>
* <b>Configuration / Init-Parameters:</b>
* </p>
* <dl>
* <dt>maxIdleTime</dt>
* <dd>set the time in ms that a websocket may be idle before closing<br>
* <p>
* <dt>maxTextMessageSize</dt>
* <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
* <p>
* <dt>maxBinaryMessageSize</dt>
* <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
* <p>
* <dt>inputBufferSize</dt>
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br> * <dt>outputBufferSize</dt>
* <dd>set the size in bytes of the buffer used to write bytes to the network layer<br>
* <dt>maxFrameSize</dt>
* <dd>The maximum frame size sent or received.<br>
* <dt>autoFragment</dt>
* <dd>If true, frames are automatically fragmented to respect the maximum frame size.<br>
* </dl>
*/
@SuppressWarnings("serial")
public abstract class WebSocketServlet extends HttpServlet
{
private static final Logger LOG = Log.getLogger(WebSocketServlet.class);
private WebSocketCreatorMapping factory;
private final Handshaker handshaker = Handshaker.newInstance();
// TODO This servlet should be split into an API neutral version and a Jetty API specific one.
public abstract void configure(WebSocketServletFactory factory);
private static final Logger LOG = Log.getLogger(WebSocketServlet.class);
private final CustomizedWebSocketServletFactory customizer = new CustomizedWebSocketServletFactory();
private WebSocketMapping mapping;
/**
* @see javax.servlet.GenericServlet#init()
* Configure the WebSocketServletFactory for this servlet instance by setting default
* configuration (which may be overriden by annotations) and mapping {@link WebSocketCreator}s.
* This method assumes a single {@link FrameHandlerFactory} will be available as a bean on the
* {@link ContextHandler}, which in practise will mostly the the Jetty WebSocket API factory.
* @param factory the WebSocketServletFactory
*/
public abstract void configure(WebSocketServletFactory factory);
@Override
public void init() throws ServletException
{
try
{
ServletContext ctx = getServletContext();
ServletContext servletContext = getServletContext();
mapping = WebSocketMapping.ensureMapping(servletContext);
factory = new WebSocketCreatorMapping();
factory.setContextClassLoader(ctx.getClassLoader());
String max = getInitParameter("maxIdleTime");
if (max != null)
{
factory.setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
}
customizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
max = getInitParameter("maxTextMessageSize");
if (max != null)
{
factory.setDefaultMaxTextMessageSize(Long.parseLong(max));
}
customizer.setMaxTextMessageSize(Long.parseLong(max));
max = getInitParameter("maxBinaryMessageSize");
if (max != null)
{
factory.setDefaultMaxBinaryMessageSize(Long.parseLong(max));
}
customizer.setMaxBinaryMessageSize(Long.parseLong(max));
max = getInitParameter("inputBufferSize");
if (max != null)
{
factory.setDefaultInputBufferSize(Integer.parseInt(max));
}
customizer.setInputBufferSize(Integer.parseInt(max));
max = getInitParameter("outputBufferSize");
if (max != null)
{
factory.setDefaultOutputBufferSize(Integer.parseInt(max));
}
customizer.setOutputBufferSize(Integer.parseInt(max));
max = getInitParameter("maxAllowedFrameSize");
max = getInitParameter("maxFrameSize");
if (max==null)
max = getInitParameter("maxAllowedFrameSize");
if (max != null)
{
factory.setDefaultMaxAllowedFrameSize(Long.parseLong(max));
}
customizer.setMaxFrameSize(Long.parseLong(max));
String autoFragment = getInitParameter("autoFragment");
if (autoFragment != null)
{
factory.setAutoFragment(Boolean.parseBoolean(autoFragment));
}
customizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
List<WebSocketServletFrameHandlerFactory> factories = (List<WebSocketServletFrameHandlerFactory>)ctx.getAttribute(
WebSocketServletFrameHandlerFactory.ATTR_HANDLERS);
if (factories != null)
factories.forEach(factory::addFrameHandlerFactory);
configure(factory); // Let user modify factory
ctx.setAttribute(WebSocketCreatorMapping.class.getName(), factory);
configure(customizer); // Let user modify customizer prior after init params
}
catch (Throwable x)
{
@ -160,60 +149,144 @@ public abstract class WebSocketServlet extends HttpServlet
}
}
/**
* @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// Since this is a filter, we need to be smart about determining the target path.
// We should rely on the Container for stripping path parameters and its ilk before
// attempting to match a specific mapped websocket creator.
String target = request.getServletPath();
if (request.getPathInfo() != null)
// Typically this servlet is used together with the WebSocketUpgradeFilter,
// so upgrade requests will normally be upgraded by the filter. But we
// can do it here as well if for some reason the filter did not match.
if (mapping.upgrade(req, resp, null))
return;
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (resp.isCommitted())
return;
// Handle normally
super.service(req, resp);
}
private class CustomizedWebSocketServletFactory extends FrameHandler.ConfigurationCustomizer implements WebSocketServletFactory
{
@Override
public Duration getDefaultIdleTimeout()
{
target = target + request.getPathInfo();
return getIdleTimeout();
}
WebSocketNegotiator negotiator = factory.getMatchedNegotiator(target, pathSpec ->
@Override
public void setDefaultIdleTimeout(Duration duration)
{
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
// implementors to use later if they wish
request.setAttribute(PathSpec.class.getName(), pathSpec);
});
if (negotiator != null)
{
if (LOG.isDebugEnabled())
{
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}", target, negotiator);
}
// Attempt to upgrade
if (handshaker.upgradeRequest(negotiator, request, response))
{
// Upgrade was a success, nothing else to do.
return;
}
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (response.isCommitted())
{
// not much we can do at this point.
return;
}
}
else
{
if (LOG.isDebugEnabled())
{
LOG.debug("No match for WebSocket Upgrade at target: {}", target);
}
setIdleTimeout(duration);
}
// All other processing
super.service(request, response);
@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);
}
@Override
public void addMapping(String pathSpec, WebSocketCreator creator)
{
addMapping(WebSocketMapping.parsePathSpec(pathSpec), creator);
}
@Override
public void addMapping(PathSpec pathSpec, WebSocketCreator creator)
{
// TODO a bit fragile. This code knows that only the JettyFHF is added directly as a been
ServletContext servletContext = getServletContext();
ContextHandler contextHandler = ContextHandler.getContextHandler(servletContext);
FrameHandlerFactory frameHandlerFactory = contextHandler.getBean(FrameHandlerFactory.class);
if (frameHandlerFactory==null)
throw new IllegalStateException("No known FrameHandlerFactory");
mapping.addMapping(pathSpec, creator, frameHandlerFactory, this);
}
@Override
public WebSocketCreator getMapping(PathSpec pathSpec)
{
return mapping.getMapping(pathSpec);
}
@Override
public WebSocketCreator getMatch(String target)
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeMapping(PathSpec pathSpec)
{
return mapping.removeMapping(pathSpec);
}
@Override
public PathSpec parsePathSpec(String pathSpec)
{
return WebSocketMapping.parsePathSpec(pathSpec);
}
}
}

View File

@ -24,22 +24,6 @@ import java.time.Duration;
public interface WebSocketServletFactory
{
void addFrameHandlerFactory(WebSocketServletFrameHandlerFactory frameHandlerFactory);
/**
* add a WebSocket mapping to a provided {@link WebSocketCreator}.
* <p>
* If mapping is added before this configuration is started, then it is persisted through
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
* this configuration is stopped.
* </p>
*
* @param pathSpec the pathspec to respond on
* @param creator the WebSocketCreator to use
* @since 10.0
*/
void addMapping(PathSpec pathSpec, WebSocketCreator creator);
Duration getDefaultIdleTimeout();
void setDefaultIdleTimeout(Duration duration);
@ -64,6 +48,26 @@ public interface WebSocketServletFactory
void setDefaultOutputBufferSize(int bufferSize);
boolean isAutoFragment();
void setAutoFragment(boolean autoFragment);
void addMapping(String pathSpec, WebSocketCreator creator);
/**
* add a WebSocket mapping to a provided {@link WebSocketCreator}.
* <p>
* If mapping is added before this configuration is started, then it is persisted through
* stop/start of this configuration's lifecycle. Otherwise it will be removed when
* this configuration is stopped.
* </p>
*
* @param pathSpec the pathspec to respond on
* @param creator the WebSocketCreator to use
* @since 10.0
*/
void addMapping(PathSpec pathSpec, WebSocketCreator creator);
/**
* Returns the creator for the given path spec.
*
@ -80,9 +84,6 @@ public interface WebSocketServletFactory
*/
WebSocketCreator getMatch(String target);
boolean isAutoFragment();
void setAutoFragment(boolean autoFragment);
/**
* Parse a PathSpec string into a PathSpec instance.

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 java.util.Map;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
@ -35,169 +34,98 @@ 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.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
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;
/**
* Inline Servlet Filter to capture WebSocket upgrade requests.
* <p>
* The configuration applied to this filter via init params will be used as the the default
* configuration of any websocket upgraded by this filter, prior to the configuration of the
* websocket applied by the {@link WebSocketMapping}.
* </p>
* <p>
* <b>Configuration / Init-Parameters:</b>
* </p>
* <dl>
* <dt>maxIdleTime</dt>
* <dd>set the time in ms that a websocket may be idle before closing<br>
* <dt>maxTextMessageSize</dt>
* <dd>set the size in UTF-8 bytes that a websocket may be accept as a Text Message before closing<br>
* <dt>maxBinaryMessageSize</dt>
* <dd>set the size in bytes that a websocket may be accept as a Binary Message before closing<br>
* <dt>inputBufferSize</dt>
* <dd>set the size in bytes of the buffer used to read raw bytes from the network layer<br>
* <dt>outputBufferSize</dt>
* <dd>set the size in bytes of the buffer used to write bytes to the network layer<br>
* <dt>maxFrameSize</dt>
* <dd>The maximum frame size sent or received.<br>
* <dt>autoFragment</dt>
* <dd>If true, frames are automatically fragmented to respect the maximum frame size.<br>
* </dl>
*/
@ManagedObject("WebSocket Upgrade Filter")
public class WebSocketUpgradeFilter implements Filter, Dumpable
{
private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
private final Handshaker handshaker = Handshaker.newInstance();
/**
* Initialize the default WebSocketUpgradeFilter that the various WebSocket APIs use.
*
* @param context the {@link ServletContextHandler} to use
* @throws ServletException if the filer cannot be configured
*/
public static void configureContext(ServletContextHandler context) throws ServletException
public static FilterHolder ensureFilter(ServletContext servletContext) throws ServletException
{
WebSocketCreatorMapping factory = (WebSocketCreatorMapping)context.getAttribute(WebSocketCreatorMapping.class.getName());
if (factory == null)
ServletHandler servletHandler = ContextHandler.getContextHandler(servletContext).getChildHandlerByClass(ServletHandler.class);
for (FilterHolder holder : servletHandler.getFilters())
{
factory = new WebSocketCreatorMapping();
context.setAttribute(WebSocketCreatorMapping.class.getName(), factory);
if (holder.getClassName().equals(WebSocketUpgradeFilter.class.getName()))
return holder;
if (holder.getHeldClass()!=null && WebSocketUpgradeFilter.class.isAssignableFrom(holder.getHeldClass()))
return holder;
}
for (FilterHolder filterHolder : context.getServletHandler().getFilters())
{
// TODO does not handle extended filter classes
if (WebSocketUpgradeFilter.class.getName().equals(filterHolder.getClassName()))
{
Map<String, String> initParams = filterHolder.getInitParameters();
String key = initParams.get(CONTEXT_ATTRIBUTE_KEY);
if (key == null || WebSocketUpgradeFilter.class.getName().equals(key))
{
if (LOG.isDebugEnabled())
LOG.debug("Filter already created: {}", filterHolder);
return;
}
}
}
// Dynamically add filter
WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(factory);
filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
String name = "Jetty_WebSocketUpgradeFilter";
String name = "WebSocketUpgradeFilter";
String pathSpec = "/*";
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
FilterHolder fholder = new FilterHolder(filter);
fholder.setName(name);
fholder.setAsyncSupported(true);
fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY, WebSocketUpgradeFilter.class.getName());
context.addFilter(fholder, pathSpec, dispatcherTypes);
FilterHolder holder = new FilterHolder(new WebSocketUpgradeFilter());
holder.setName(name);
holder.setAsyncSupported(true);
servletHandler.addFilterWithMapping(holder, pathSpec, dispatcherTypes);
if (LOG.isDebugEnabled())
{
LOG.debug("Adding [{}] {} mapped to {} to {}", name, filter, pathSpec, context);
}
LOG.debug("Adding {} mapped to {} in {}", holder, pathSpec, servletContext);
return holder;
}
private WebSocketCreatorMapping factory;
private String instanceKey;
private boolean alreadySetToAttribute = false;
private final FrameHandler.ConfigurationCustomizer defaultCustomizer = new FrameHandler.ConfigurationCustomizer();
private WebSocketMapping mapping;
@SuppressWarnings("unused")
public WebSocketUpgradeFilter()
{
this(null);
}
public WebSocketUpgradeFilter(WebSocketCreatorMapping factory)
{
this.factory = factory;
}
@Override
public void destroy()
{
try
{
alreadySetToAttribute = false;
}
catch (Exception e)
{
LOG.ignore(e);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
try
{
HttpServletRequest httpreq = (HttpServletRequest)request;
HttpServletResponse httpresp = (HttpServletResponse)response;
HttpServletRequest httpreq = (HttpServletRequest)request;
HttpServletResponse httpresp = (HttpServletResponse)response;
// Since this is a filter, we need to be smart about determining the target path.
// We should rely on the Container for stripping path parameters and its ilk before
// attempting to match a specific mapped websocket creator.
String target = httpreq.getServletPath();
if (httpreq.getPathInfo() != null)
{
target = target + httpreq.getPathInfo();
}
if (mapping.upgrade(httpreq, httpresp, defaultCustomizer))
return;
WebSocketNegotiator negotiator = factory.getMatchedNegotiator(target, pathSpec ->
{
// Store PathSpec resource mapping as request attribute, for WebSocketCreator
// implementors to use later if they wish
httpreq.setAttribute(PathSpec.class.getName(), pathSpec);
});
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (response.isCommitted())
return;
if (negotiator == null)
{
// no match.
chain.doFilter(request, response);
return;
}
if (LOG.isDebugEnabled())
{
LOG.debug("WebSocket Upgrade detected on {} for endpoint {}", target, negotiator);
}
// We have an upgrade request
if (handshaker.upgradeRequest(negotiator, httpreq, httpresp))
{
// We have a socket instance created
return;
}
// If we reach this point, it means we had an incoming request to upgrade
// but it was either not a proper websocket upgrade, or it was possibly rejected
// due to incoming request constraints (controlled by WebSocketCreator)
if (response.isCommitted())
{
// not much we can do at this point.
return;
}
}
catch (ClassCastException e)
{
// We are in some kind of funky non-http environment.
if (LOG.isDebugEnabled())
{
LOG.debug("Not a HttpServletRequest, skipping WebSocketUpgradeFilter");
}
}
// This means we got a request that looked like an upgrade request, but
// didn't actually upgrade, or produce an error, so process normally in the servlet chain.
// Handle normally
chain.doFilter(request, response);
}
@ -210,115 +138,49 @@ public class WebSocketUpgradeFilter implements Filter, Dumpable
@Override
public void dump(Appendable out, String indent) throws IOException
{
Dumpable.dumpObjects(out, indent, this, factory);
Dumpable.dumpObjects(out, indent, this, mapping);
}
@ManagedAttribute(value = "factory", readonly = true)
public WebSocketCreatorMapping getFactory()
public WebSocketMapping getMapping()
{
return factory;
return mapping;
}
@Override
public void init(FilterConfig config) throws ServletException
{
if (factory == null)
{
factory = (WebSocketCreatorMapping)config.getServletContext().getAttribute(WebSocketCreatorMapping.class.getName());
final ServletContext context = config.getServletContext();
mapping = WebSocketMapping.ensureMapping(context);
if (factory == null)
factory = new WebSocketCreatorMapping();
}
String max = config.getInitParameter("maxIdleTime");
if (max != null)
defaultCustomizer.setIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
try
{
final ServletContext context = config.getServletContext();
max = config.getInitParameter("maxTextMessageSize");
if (max != null)
defaultCustomizer.setMaxTextMessageSize(Integer.parseInt(max));
factory.setContextClassLoader(context.getClassLoader());
max = config.getInitParameter("maxBinaryMessageSize");
if (max != null)
defaultCustomizer.setMaxBinaryMessageSize(Integer.parseInt(max));
String max = config.getInitParameter("maxIdleTime");
if (max != null)
{
getFactory().setDefaultIdleTimeout(Duration.ofMillis(Long.parseLong(max)));
}
max = config.getInitParameter("inputBufferSize");
if (max != null)
defaultCustomizer.setInputBufferSize(Integer.parseInt(max));
max = config.getInitParameter("maxTextMessageSize");
if (max != null)
{
getFactory().setDefaultMaxTextMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("maxBinaryMessageSize");
if (max != null)
{
getFactory().setDefaultMaxBinaryMessageSize(Integer.parseInt(max));
}
max = config.getInitParameter("inputBufferSize");
if (max != null)
{
getFactory().setDefaultInputBufferSize(Integer.parseInt(max));
}
max = config.getInitParameter("outputBufferSize");
if (max != null)
{
getFactory().setDefaultOutputBufferSize(Integer.parseInt(max));
}
max = config.getInitParameter("outputBufferSize");
if (max != null)
defaultCustomizer.setOutputBufferSize(Integer.parseInt(max));
max = config.getInitParameter("maxFrameSize");
if (max == null)
max = config.getInitParameter("maxAllowedFrameSize");
if (max != null)
{
getFactory().setDefaultMaxAllowedFrameSize(Long.parseLong(max));
}
if (max != null)
defaultCustomizer.setMaxFrameSize(Long.parseLong(max));
String autoFragment = config.getInitParameter("autoFragment");
if (autoFragment != null)
{
getFactory().setAutoFragment(Boolean.parseBoolean(autoFragment));
}
instanceKey = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
if (instanceKey == null)
{
// assume default
instanceKey = WebSocketUpgradeFilter.class.getName();
}
// Set instance of this filter to context attribute
setToAttribute(config.getServletContext(), instanceKey);
}
catch (ServletException e)
{
throw e;
}
catch (Throwable t)
{
throw new ServletException(t);
}
}
private void setToAttribute(ServletContextHandler context, String key) throws ServletException
{
setToAttribute(context.getServletContext(), key);
}
public void setToAttribute(ServletContext context, String key) throws ServletException
{
if (alreadySetToAttribute)
{
return;
}
if (context.getAttribute(key) != null)
{
throw new ServletException(WebSocketUpgradeFilter.class.getName() +
" is defined twice for the same context attribute key '" + key
+ "'. Make sure you have different init-param '" +
CONTEXT_ATTRIBUTE_KEY + "' values set");
}
context.setAttribute(key, this);
alreadySetToAttribute = true;
String autoFragment = config.getInitParameter("autoFragment");
if (autoFragment != null)
defaultCustomizer.setAutoFragment(Boolean.parseBoolean(autoFragment));
}
}

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.33</jsp.version>
<jsp.version>8.5.35.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>
@ -66,11 +66,10 @@
<unix.socket.tmp></unix.socket.tmp>
<!-- enable or not TestTracker junit5 extension i.e log message when test method is starting -->
<jetty.testtracker.log>false</jetty.testtracker.log>
<jetty.surefire.argLine>-Dfile.encoding=UTF-8 -Duser.language=en -Duser.region=US -showversion -Xmx1g -Xms1g -Xlog:gc:stderr:time,level,tags</jetty.surefire.argLine>
<!-- some maven plugins versions -->
<maven.surefire.version>2.22.1</maven.surefire.version>
<maven.surefire.version>3.0.0-M2</maven.surefire.version>
<maven.compiler.plugin.version>3.8.0</maven.compiler.plugin.version>
<maven.dependency.plugin.version>3.1.1</maven.dependency.plugin.version>
<maven.resources.plugin.version>3.1.0</maven.resources.plugin.version>
@ -490,7 +489,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<version>3.1.1</version>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>

View File

@ -117,7 +117,7 @@ public class AttributeNameTest
//Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path=");
//Make a request to the 2nd server which will do a refresh, use TestFooServlet to ensure that the
//Make a request to the 2nd server which will do a refresh, use TestServlet to ensure that the
//session attribute with dotted name is not removed
Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get");
request2.header("Cookie", sessionCookie);

View File

@ -1,59 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.server.session;
import java.io.IOException;
import java.lang.reflect.Proxy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestFooServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("create".equals(action))
{
HttpSession session = request.getSession(true);
TestFoo testFoo = new TestFoo();
testFoo.setInt(33);
FooInvocationHandler handler = new FooInvocationHandler(testFoo);
Foo foo = (Foo)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {Foo.class}, handler);
session.setAttribute("foo", foo);
}
else if ("test".equals(action))
{
HttpSession session = request.getSession(false);
if (session == null)
response.sendError(500, "Session not activated");
Foo foo = (Foo)session.getAttribute("foo");
if (foo == null || foo.getInt() != 33)
response.sendError(500, "Foo not deserialized");
}
}
}

View File

@ -0,0 +1,358 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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.server.session;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.lang.reflect.Proxy;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
/**
* AsyncTest
*
* Tests async handling wrt sessions.
*/
public class AsyncTest
{
public static class LatchServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getWriter().println("Latched");
}
}
@Test
public void testSessionWithAsyncDispatch() throws Exception
{
// Test async dispatch back to same context, which then creates a session.
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
String contextPath = "";
String mapping = "/server";
ServletContextHandler contextHandler = server.addContext(contextPath);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
contextHandler.addServlet(holder, mapping);
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextHandler.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + contextPath + mapping+"?action=async";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + contextPath + "/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextHandler.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithAsyncComplete() throws Exception
{
// Test async write, which creates a session and completes outside of a dispatch
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
String contextPath = "";
String mapping = "/server";
ServletContextHandler contextHandler = server.addContext(contextPath);
TestServlet servlet = new TestServlet();
ServletHolder holder = new ServletHolder(servlet);
contextHandler.addServlet(holder, mapping);
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextHandler.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + contextPath + mapping+"?action=asyncComplete";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + contextPath + "/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextHandler.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextHandler.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithCrossContextAsync() throws Exception
{
// Test async dispatch from context A to context B then
// async dispatch back to context B, which then creates a session (in context B).
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
ServletContextHandler contextA = server.addContext("/ctxA");
CrossContextServlet ccServlet = new CrossContextServlet();
ServletHolder ccHolder = new ServletHolder(ccServlet);
contextA.addServlet(ccHolder, "/*");
ServletContextHandler contextB = server.addContext("/ctxB");
TestServlet testServlet = new TestServlet();
ServletHolder testHolder = new ServletHolder(testServlet);
contextB.addServlet(testHolder, "/*");
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextB.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + "/ctxA/test?action=async";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + "/ctxB/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextB.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextB.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
@Test
public void testSessionWithCrossContextAsyncComplete() throws Exception
{
// Test async dispatch from context A to context B, which then does an
// async write, which creates a session (in context A) and completes outside of a
// dispatch
DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory();
cacheFactory.setEvictionPolicy(SessionCache.EVICT_ON_SESSION_EXIT);
SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory();
TestServer server = new TestServer(0, -1, -1, cacheFactory, storeFactory);
ServletContextHandler contextA = server.addContext("/ctxA");
CrossContextServlet ccServlet = new CrossContextServlet();
ServletHolder ccHolder = new ServletHolder(ccServlet);
contextA.addServlet(ccHolder, "/*");
ServletContextHandler contextB = server.addContext("/ctxB");
TestServlet testServlet = new TestServlet();
ServletHolder testHolder = new ServletHolder(testServlet);
contextB.addServlet(testHolder, "/*");
LatchServlet latchServlet = new LatchServlet();
ServletHolder latchHolder = new ServletHolder(latchServlet);
contextB.addServlet(latchHolder, "/latch");
server.start();
int port = server.getPort();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
HttpClient client = new HttpClient();
client.start();
String url = "http://localhost:" + port + "/ctxA/test?action=asyncComplete";
//make a request to set up a session on the server
ContentResponse response = client.GET(url);
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
//make another request, when this is handled, the first request is definitely finished being handled
response = client.GET("http://localhost:" + port + "/ctxB/latch");
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
//session should now be evicted from the cache A after request exited
String id = TestServer.extractSessionId(sessionCookie);
assertFalse(contextA.getSessionHandler().getSessionCache().contains(id));
assertTrue(contextA.getSessionHandler().getSessionCache().getSessionDataStore().exists(id));
}
finally
{
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("create".equals(action))
{
HttpSession session = request.getSession(true);
TestFoo testFoo = new TestFoo();
testFoo.setInt(33);
FooInvocationHandler handler = new FooInvocationHandler(testFoo);
Foo foo = (Foo)Proxy
.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] {Foo.class}, handler);
session.setAttribute("foo", foo);
}
else if ("test".equals(action))
{
HttpSession session = request.getSession(false);
if (session == null)
response.sendError(500, "Session not activated");
Foo foo = (Foo)session.getAttribute("foo");
if (foo == null || foo.getInt() != 33)
response.sendError(500, "Foo not deserialized");
}
else if ("async".equals(action))
{
if (request.getAttribute("async-test") == null)
{
request.setAttribute("async-test", Boolean.TRUE);
AsyncContext acontext = request.startAsync();
acontext.dispatch();
return;
}
else
{
HttpSession session = request.getSession(true);
response.getWriter().println("OK");
}
}
else if ("asyncComplete".equals(action))
{
AsyncContext acontext = request.startAsync();
ServletOutputStream out = response.getOutputStream();
out.setWriteListener(new WriteListener()
{
@Override
public void onWritePossible() throws IOException
{
if (out.isReady())
{
request.getSession(true);
out.print("OK\n");
acontext.complete();
}
}
@Override
public void onError(Throwable t)
{
}
});
}
}
}
public static class CrossContextServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext acontext = request.startAsync();
acontext.dispatch(request.getServletContext().getContext("/ctxB"),"/test");
}
}
}

View File

@ -128,12 +128,7 @@ public class DeleteUnloadableSessionTest
}
}
/**
* TestFooServlet
*
*
*/
public static class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;

View File

@ -136,16 +136,9 @@ public class SessionEvictionFailureTest
}
/**
* TestFooServlet
*
*
*/
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException
{

View File

@ -192,6 +192,7 @@
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
@ -203,11 +204,13 @@
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>jetty-websocket-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-servlet</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>

View File

@ -32,9 +32,11 @@ import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.FrameHandler;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketMapping;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;