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:
parent
3779b0384d
commit
8c7c5a5d01
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
org.eclipse.jetty.annotations.ServerServletContainerInitializer
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
invoker.goals = test
|
|
@ -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>
|
|
@ -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')
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
drink=Beer
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -442,7 +442,7 @@ public class LowResourceMonitor extends ContainerLifeCycle
|
|||
{
|
||||
}
|
||||
|
||||
interface LowResourceCheck
|
||||
public interface LowResourceCheck
|
||||
{
|
||||
boolean isLowOnResources();
|
||||
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @{@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 @{@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|||
* @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)->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -128,12 +128,7 @@ public class DeleteUnloadableSessionTest
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TestFooServlet
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
public static class TestServlet extends HttpServlet
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue