Merge remote-tracking branch 'origin/jetty-10.0.x' into jetty-10.0.x-4919-WebSocketContainerStop

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2020-07-24 13:39:07 +10:00
commit e13d26ab45
46 changed files with 1255 additions and 244 deletions

View File

@ -22,9 +22,11 @@ import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
@ -46,18 +48,33 @@ import org.eclipse.jetty.util.TypeUtil;
*/
public class DigestAuthentication extends AbstractAuthentication
{
private final Random random;
private final String user;
private final String password;
/**
/** Construct a DigestAuthentication with a {@link SecureRandom} nonce.
* @param uri the URI to match for the authentication
* @param realm the realm to match for the authentication
* @param user the user that wants to authenticate
* @param password the password of the user
*/
public DigestAuthentication(URI uri, String realm, String user, String password)
{
this(uri, realm, user, password, new SecureRandom());
}
/**
* @param uri the URI to match for the authentication
* @param realm the realm to match for the authentication
* @param user the user that wants to authenticate
* @param password the password of the user
* @param random the Random generator to use for nonces.
*/
public DigestAuthentication(URI uri, String realm, String user, String password, Random random)
{
super(uri, realm);
Objects.requireNonNull(random);
this.random = random;
this.user = user;
this.password = password;
}
@ -216,7 +233,6 @@ public class DigestAuthentication extends AbstractAuthentication
private String newClientNonce()
{
Random random = new Random();
byte[] bytes = new byte[8];
random.nextBytes(bytes);
return toHexString(bytes);

View File

@ -27,7 +27,6 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.AsyncContentProvider;
@ -102,15 +101,10 @@ public class MultiPartContentProvider extends AbstractTypedContentProvider imple
private static String makeBoundary()
{
Random random = new Random();
StringBuilder builder = new StringBuilder("JettyHttpClientBoundary");
int length = builder.length();
while (builder.length() < length + 16)
{
long rnd = random.nextLong();
builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36));
}
builder.setLength(length + 16);
builder.append(Long.toString(System.identityHashCode(builder), 36));
builder.append(Long.toString(System.identityHashCode(Thread.currentThread()), 36));
builder.append(Long.toString(System.nanoTime(), 36));
return builder.toString();
}

View File

@ -102,8 +102,8 @@ As well as opening the connectors as `root`, you can also have Jetty start the S
____
. A native code library is required to perform user switching.
This code is hosted as part of the Jetty ToolChain project and is released independently from Jetty itself.
You can find the source code https://github.com/eclipsejetty.toolchain[here] in the https://github.com/eclipse/jetty.toolchain/jetty-setuid[jetty-setuid] project.
This code is hosted as part of the https://github.com/eclipse/jetty.toolchain[Jetty ToolChain] project and is released independently from Jetty itself.
You can find the source code in the https://github.com/eclipse/jetty.toolchain/tree/master/jetty-setuid[eclipse/jetty.toolchain/jetty-setuid] project.
Build it locally, which will produce a native library appropriate for the operating system:
+
[source, screen, subs="{sub-order}"]

View File

@ -278,7 +278,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
}
// Attempt to login with the provided authCode
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request), _configuration);
OpenIdCredentials credentials = new OpenIdCredentials(authCode, getRedirectUri(request));
UserIdentity user = login(null, credentials, request);
if (user != null)
{

View File

@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.FormRequestContent;
@ -38,7 +37,7 @@ import org.slf4j.LoggerFactory;
*
* <p>
* This is constructed with an authorization code from the authentication request. This authorization code
* is then exchanged using {@link #redeemAuthCode(HttpClient)} for a response containing the ID Token and Access Token.
* is then exchanged using {@link #redeemAuthCode(OpenIdConfiguration)} for a response containing the ID Token and Access Token.
* The response is then validated against the {@link OpenIdConfiguration}.
* </p>
*/
@ -48,16 +47,14 @@ public class OpenIdCredentials implements Serializable
private static final long serialVersionUID = 4766053233370044796L;
private final String redirectUri;
private final OpenIdConfiguration configuration;
private String authCode;
private Map<String, Object> response;
private Map<String, Object> claims;
public OpenIdCredentials(String authCode, String redirectUri, OpenIdConfiguration configuration)
public OpenIdCredentials(String authCode, String redirectUri)
{
this.authCode = authCode;
this.redirectUri = redirectUri;
this.configuration = configuration;
}
public String getUserId()
@ -75,7 +72,25 @@ public class OpenIdCredentials implements Serializable
return response;
}
public void redeemAuthCode(HttpClient httpClient) throws Exception
public boolean isExpired()
{
if (authCode != null || claims == null)
return true;
// Check expiry
long expiry = (Long)claims.get("exp");
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
if (currentTimeSeconds > expiry)
{
if (LOG.isDebugEnabled())
LOG.debug("OpenId Credentials expired {}", this);
return true;
}
return false;
}
public void redeemAuthCode(OpenIdConfiguration configuration) throws Exception
{
if (LOG.isDebugEnabled())
LOG.debug("redeemAuthCode() {}", this);
@ -84,7 +99,7 @@ public class OpenIdCredentials implements Serializable
{
try
{
response = claimAuthCode(httpClient, authCode);
response = claimAuthCode(configuration);
if (LOG.isDebugEnabled())
LOG.debug("response: {}", response);
@ -103,7 +118,7 @@ public class OpenIdCredentials implements Serializable
claims = JwtDecoder.decode(idToken);
if (LOG.isDebugEnabled())
LOG.debug("claims {}", claims);
validateClaims();
validateClaims(configuration);
}
finally
{
@ -113,14 +128,14 @@ public class OpenIdCredentials implements Serializable
}
}
private void validateClaims()
private void validateClaims(OpenIdConfiguration configuration)
{
// Issuer Identifier for the OpenID Provider MUST exactly match the value of the iss (issuer) Claim.
if (!configuration.getIssuer().equals(claims.get("iss")))
throw new IllegalArgumentException("Issuer Identifier MUST exactly match the iss Claim");
// The aud (audience) Claim MUST contain the client_id value.
validateAudience();
validateAudience(configuration);
// If an azp (authorized party) Claim is present, verify that its client_id is the Claim Value.
Object azp = claims.get("azp");
@ -128,7 +143,7 @@ public class OpenIdCredentials implements Serializable
throw new IllegalArgumentException("Authorized party claim value should be the client_id");
}
private void validateAudience()
private void validateAudience(OpenIdConfiguration configuration)
{
Object aud = claims.get("aud");
String clientId = configuration.getClientId();
@ -150,25 +165,8 @@ public class OpenIdCredentials implements Serializable
throw new IllegalArgumentException("Audience claim was not valid");
}
public boolean isExpired()
{
if (authCode != null || claims == null)
return true;
// Check expiry
long expiry = (Long)claims.get("exp");
long currentTimeSeconds = (long)(System.currentTimeMillis() / 1000F);
if (currentTimeSeconds > expiry)
{
if (LOG.isDebugEnabled())
LOG.debug("OpenId Credentials expired {}", this);
return true;
}
return false;
}
private Map<String, Object> claimAuthCode(HttpClient httpClient, String authCode) throws Exception
@SuppressWarnings("unchecked")
private Map<String, Object> claimAuthCode(OpenIdConfiguration configuration) throws Exception
{
Fields fields = new Fields();
fields.add("code", authCode);
@ -177,7 +175,7 @@ public class OpenIdCredentials implements Serializable
fields.add("redirect_uri", redirectUri);
fields.add("grant_type", "authorization_code");
FormRequestContent formContent = new FormRequestContent(fields);
Request request = httpClient.POST(configuration.getTokenEndpoint())
Request request = configuration.getHttpClient().POST(configuration.getTokenEndpoint())
.body(formContent)
.timeout(10, TimeUnit.SECONDS);
ContentResponse response = request.send();

View File

@ -22,7 +22,6 @@ import java.security.Principal;
import javax.security.auth.Subject;
import javax.servlet.ServletRequest;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.UserIdentity;
@ -43,7 +42,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
private final OpenIdConfiguration configuration;
private final LoginService loginService;
private final HttpClient httpClient;
private IdentityService identityService;
private boolean authenticateNewUsers;
@ -63,7 +61,6 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
{
this.configuration = configuration;
this.loginService = loginService;
this.httpClient = configuration.getHttpClient();
addBean(this.configuration);
addBean(this.loginService);
}
@ -88,7 +85,7 @@ public class OpenIdLoginService extends ContainerLifeCycle implements LoginServi
OpenIdCredentials openIdCredentials = (OpenIdCredentials)credentials;
try
{
openIdCredentials.redeemAuthCode(httpClient);
openIdCredentials.redeemAuthCode(configuration);
if (openIdCredentials.isExpired())
return null;
}

View File

@ -129,9 +129,11 @@ public class OSGiWebappClassLoader extends WebAppClassLoader implements BundleRe
public Enumeration<URL> getResources(String name) throws IOException
{
Enumeration<URL> osgiUrls = _osgiBundleClassLoader.getResources(name);
if (osgiUrls != null && osgiUrls.hasMoreElements())
return osgiUrls;
Enumeration<URL> urls = super.getResources(name);
List<URL> resources = toList(osgiUrls, urls);
return Collections.enumeration(resources);
return urls;
}
@Override

View File

@ -24,6 +24,7 @@
<module>jetty-osgi-boot-warurl</module>
<module>jetty-osgi-httpservice</module>
<module>test-jetty-osgi-webapp</module>
<module>test-jetty-osgi-webapp-resources</module>
<module>test-jetty-osgi-context</module>
<module>test-jetty-osgi-fragment</module>
<module>test-jetty-osgi-server</module>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>jetty-osgi-project</artifactId>
<version>10.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>test-jetty-osgi-webapp-resources</artifactId>
<name>OSGi Test :: Webapp With Resources</name>
<url>http://www.eclipse.org/jetty</url>
<packaging>war</packaging>
<properties>
<bundle-symbolic-name>${project.groupId}.webapp.resources</bundle-symbolic-name>
</properties>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<!-- No point building javadoc on testing projects -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<supportedProjectTypes>
<supportedProjectType>war</supportedProjectType>
</supportedProjectTypes>
<instructions>
<Export-Package>!com.acme*</Export-Package>
<Web-ContextPath>/</Web-ContextPath>
</instructions>
</configuration>
</plugin>
<!-- also make this webapp an osgi bundle -->
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- must deploy: required for jetty-distribution -->
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,67 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package com.acme;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Dump Servlet Request.
*/
@SuppressWarnings("serial")
public class HelloWorld extends HttpServlet
{
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doGet(request, response);
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.setContentType("text/html");
ServletOutputStream out = response.getOutputStream();
out.println("<html>");
out.println("<h1>Hello World</h1>");
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("fake.properties");
while (resources.hasMoreElements())
out.println(resources.nextElement().toString());
out.println("</html>");
out.flush();
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
version="3.1">
<display-name>WebApp With Resources</display-name>
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>com.acme.HelloWorld</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Hello</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -38,6 +38,12 @@
<artifactId>pax-exam-container-forked</artifactId>
<version>${pax.exam.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bndlib</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
@ -69,17 +75,6 @@
<version>${pax.url.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.tinybundles</groupId>
<artifactId>tinybundles</artifactId>
<version>3.0.0</version>
<exclusions>
<exclusion>
<groupId>biz.aQute.bnd</groupId>
<artifactId>bnd</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-wrap</artifactId>
@ -393,6 +388,13 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>test-jetty-osgi-webapp-resources</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
<artifactId>test-jetty-osgi-fragment</artifactId>
@ -550,6 +552,23 @@
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
<configuration>
<includeArtifactIds>test-jetty-osgi-webapp-resources</includeArtifactIds>
<outputDirectory>target</outputDirectory>
<stripVersion>true</stripVersion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.servicemix.tooling</groupId>
<artifactId>depends-maven-plugin</artifactId>

View File

@ -0,0 +1,43 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<!-- ============================================================= --><!-- Configure the Jetty Server instance with an ID "Server" --><!-- by adding an HTTP connector. --><!-- This configuration must be used in conjunction with jetty.xml --><!-- ============================================================= -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- Add an HTTP Connector. -->
<!-- Configure an o.e.j.server.ServerConnector with a single -->
<!-- HttpConnectionFactory instance using the common httpConfig -->
<!-- instance defined in jetty.xml -->
<!-- -->
<!-- Consult the javadoc of o.e.j.server.ServerConnector and -->
<!-- o.e.j.server.HttpConnectionFactory for all configuration -->
<!-- that may be set here. -->
<!-- =========================================================== -->
<Call name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server"><Ref refid="Server" /></Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="httpConfig" /></Arg>
</New>
</Item>
</Array>
</Arg>
<Call name="addEventListener">
<Arg>
<New class="org.eclipse.jetty.osgi.boot.utils.ServerConnectorListener">
<Set name="sysPropertyName">boot.resources.port</Set>
</New>
</Arg>
</Call>
<Set name="host"><Property name="jetty.http.host" /></Set>
<Set name="port"><Property name="jetty.http.port" default="80" /></Set>
<Set name="idleTimeout"><Property name="jetty.http.idleTimeout" default="30000"/></Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -92,7 +92,6 @@ public class TestJettyOSGiBootWithJavaxWebSocket
{
List<Option> res = new ArrayList<>();
res.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("biz.aQute.bndlib").versionAsInProject().start());
res.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").versionAsInProject().start());
return res;
}

View File

@ -0,0 +1,152 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.osgi.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import javax.inject.Inject;
import aQute.bnd.osgi.Constants;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Configuration;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.PaxExam;
import org.ops4j.pax.tinybundles.core.TinyBundle;
import org.ops4j.pax.tinybundles.core.TinyBundles;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
/**
* TestJettyOSGiClasspathResources
*
*/
@RunWith(PaxExam.class)
public class TestJettyOSGiClasspathResources
{
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<>();
options.addAll(TestOSGiUtil.configurePaxExamLogging());
options.add(CoreOptions.junitBundles());
options.addAll(TestOSGiUtil.configureJettyHomeAndPort(false, "jetty-http-boot-with-resources.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*", "org.w3c.*", "javax.xml.*", "javax.activation.*"));
options.add(CoreOptions.systemPackages("com.sun.org.apache.xalan.internal.res", "com.sun.org.apache.xml.internal.utils",
"com.sun.org.apache.xml.internal.utils", "com.sun.org.apache.xpath.internal",
"com.sun.org.apache.xpath.internal.jaxp", "com.sun.org.apache.xpath.internal.objects"));
options.addAll(TestOSGiUtil.coreJettyDependencies());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-java-client").versionAsInProject().start());
options.add(mavenBundle().groupId("org.eclipse.jetty").artifactId("jetty-alpn-client").versionAsInProject().start());
//Note: we have to back down the version of bnd used here because tinybundles expects only this version
options.add(mavenBundle().groupId("biz.aQute.bnd").artifactId("bndlib").version("2.4.0").start());
options.add(mavenBundle().groupId("org.ops4j.pax.tinybundles").artifactId("tinybundles").version("2.1.1").start());
options.add(mavenBundle().groupId("org.eclipse.jetty.osgi").artifactId("test-jetty-osgi-webapp-resources").type("war").versionAsInProject());
options.add(CoreOptions.cleanCaches(true));
return options.toArray(new Option[options.size()]);
}
@Test
public void testWebInfResourceNotOnBundleClasspath() throws Exception
{
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
TestOSGiUtil.diagnoseBundles(bundleContext);
//Test the test-jetty-osgi-webapp-resource bundle with a
//Bundle-Classpath that does NOT include WEB-INF/classes
HttpClient client = new HttpClient();
try
{
client.start();
String port = System.getProperty("boot.resources.port");
assertNotNull(port);
ContentResponse response = client.GET("http://127.0.0.1:" + port + "/hello/a");
assertEquals(HttpStatus.OK_200, response.getStatus());
String content = response.getContentAsString();
//check that fake.properties is only listed once from the classpath
assertEquals(content.indexOf("fake.properties"), content.lastIndexOf("fake.properties"));
}
finally
{
client.stop();
}
}
@Test
public void testWebInfResourceOnBundleClasspath() throws Exception
{
if (Boolean.getBoolean(TestOSGiUtil.BUNDLE_DEBUG))
TestOSGiUtil.diagnoseBundles(bundleContext);
Bundle webappBundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.webapp.resources");
//Make a new bundle based on the test-jetty-osgi-webapp-resources war bundle, but
//change the Bundle-Classpath so that WEB-INF/classes IS on the bundle classpath
File warFile = new File("target/test-jetty-osgi-webapp-resources.war");
TinyBundle tiny = TinyBundles.bundle();
tiny.read(new FileInputStream(warFile));
tiny.set(Constants.BUNDLE_CLASSPATH, "., WEB-INF/classes/");
tiny.set(Constants.BUNDLE_SYMBOLICNAME, "org.eclipse.jetty.osgi.webapp.resources.alt");
InputStream is = tiny.build(TinyBundles.withBnd());
bundleContext.installBundle("dummyAltLocation", is);
webappBundle.stop();
Bundle bundle = TestOSGiUtil.getBundle(bundleContext, "org.eclipse.jetty.osgi.webapp.resources.alt");
bundle.start();
HttpClient client = new HttpClient();
try
{
client.start();
String port = System.getProperty("boot.resources.port");
assertNotNull(port);
ContentResponse response = client.GET("http://127.0.0.1:" + port + "/hello/a");
String content = response.getContentAsString();
assertEquals(HttpStatus.OK_200, response.getStatus());
//check that fake.properties is only listed once from the classpath
assertEquals(content.indexOf("fake.properties"), content.lastIndexOf("fake.properties"));
}
finally
{
client.stop();
}
}
}

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.plus.webapp;
import java.util.Random;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
@ -110,8 +109,7 @@ public class PlusConfiguration extends AbstractConfiguration
Thread.currentThread().setContextClassLoader(wac.getClassLoader());
try
{
Random random = new Random();
_key = random.nextInt();
_key = (int)(this.hashCode() ^ System.nanoTime());
Context context = new InitialContext();
Context compCtx = (Context)context.lookup("java:comp");
compCtx.addToEnvironment(NamingContext.LOCK_PROPERTY, _key);

View File

@ -578,6 +578,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return _contextPathEncoded;
}
/**
* Get the context path in a form suitable to be returned from {@link HttpServletRequest#getContextPath()}
* or {@link ServletContext#getContextPath()}.
* @return Returns the encoded contextPath, or empty string for root context
*/
public String getRequestContextPath()
{
String contextPathEncoded = getContextPathEncoded();
return "/".equals(contextPathEncoded) ? "" : contextPathEncoded;
}
/*
* @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
*/
@ -2329,10 +2340,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
@Override
public String getContextPath()
{
if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
return "";
return _contextPath;
return getRequestContextPath();
}
@Override

View File

@ -33,7 +33,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* FilterHolderTest
@ -67,13 +67,13 @@ public class FilterHolderTest
Filter filter = new Filter()
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
public void init(FilterConfig filterConfig)
{
counter.incrementAndGet();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
{
}
@ -89,14 +89,9 @@ public class FilterHolderTest
fh.setName("xx");
fh.setFilter(filter);
try (StacklessLogging stackless = new StacklessLogging(FilterHolder.class))
try (StacklessLogging ignored = new StacklessLogging(FilterHolder.class))
{
fh.initialize();
fail("Not started");
}
catch (Exception e)
{
//expected
assertThrows(IllegalStateException.class, fh::initialize);
}
fh.start();

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -25,8 +26,12 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -34,8 +39,10 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.IO;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@ -71,6 +78,45 @@ public class IncludedServletTest
}
}
public static class IncludedAttrServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getDispatcherType() == DispatcherType.INCLUDE)
{
if (req.getAttribute("included") == null)
{
req.setAttribute("included", Boolean.TRUE);
dumpAttrs("BEFORE1", req, resp.getOutputStream());
req.getRequestDispatcher("two").include(req, resp);
dumpAttrs("AFTER1", req, resp.getOutputStream());
}
else
{
dumpAttrs("DURING", req, resp.getOutputStream());
}
}
else
{
resp.setContentType("text/plain");
dumpAttrs("BEFORE0", req, resp.getOutputStream());
req.getRequestDispatcher("one").include(req, resp);
dumpAttrs("AFTER0", req, resp.getOutputStream());
}
}
private void dumpAttrs(String tag, HttpServletRequest req, ServletOutputStream out) throws IOException
{
out.println(String.format("%s: %s='%s'", tag, RequestDispatcher.INCLUDE_CONTEXT_PATH,
req.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH)));
out.println(String.format("%s: %s='%s'", tag, RequestDispatcher.INCLUDE_SERVLET_PATH,
req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH)));
out.println(String.format("%s: %s='%s'", tag, RequestDispatcher.INCLUDE_PATH_INFO,
req.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO)));
}
}
private Server server;
private URI baseUri;
@ -87,6 +133,7 @@ public class IncludedServletTest
context.setContextPath("/");
context.addServlet(TopServlet.class, "/top");
context.addServlet(IncludedServlet.class, "/included");
context.addServlet(IncludedAttrServlet.class, "/attr/*");
server.setHandler(context);
@ -173,4 +220,53 @@ public class IncludedServletTest
IO.close(in);
}
}
@Disabled // TODO: complete merge of PR #5058.
@Test
public void testIncludeAttributes() throws IOException
{
URI uri = baseUri.resolve("/attr/one");
InputStream in = null;
BufferedReader reader = null;
HttpURLConnection connection = null;
try
{
connection = (HttpURLConnection)uri.toURL().openConnection();
connection.connect();
assertThat(connection.getResponseCode(), is(HttpURLConnection.HTTP_OK));
in = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(in));
List<String> result = new ArrayList<>();
String line = reader.readLine();
while (line != null)
{
result.add(line);
line = reader.readLine();
}
assertThat(result, Matchers.contains(
"BEFORE0: javax.servlet.include.context_path='null'",
"BEFORE0: javax.servlet.include.servlet_path='null'",
"BEFORE0: javax.servlet.include.path_info='null'",
"BEFORE1: javax.servlet.include.context_path=''",
"BEFORE1: javax.servlet.include.servlet_path='/attr'",
"BEFORE1: javax.servlet.include.path_info='/one'",
"DURING: javax.servlet.include.context_path=''",
"DURING: javax.servlet.include.servlet_path='/attr'",
"DURING: javax.servlet.include.path_info='/two'",
"AFTER1: javax.servlet.include.context_path=''",
"AFTER1: javax.servlet.include.servlet_path='/attr'",
"AFTER1: javax.servlet.include.path_info='/one'",
"AFTER0: javax.servlet.include.context_path='null'",
"AFTER0: javax.servlet.include.servlet_path='null'",
"AFTER0: javax.servlet.include.path_info='null'"
));
}
finally
{
IO.close(reader);
IO.close(in);
}
}
}

View File

@ -91,6 +91,7 @@ import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -111,7 +112,7 @@ public class ServletContextHandlerTest
private static int __initIndex = 0;
private static int __destroyIndex = 0;
public class StopTestFilter implements Filter
public static class StopTestFilter implements Filter
{
int _initIndex;
int _destroyIndex;
@ -135,7 +136,7 @@ public class ServletContextHandlerTest
}
}
public class StopTestServlet extends GenericServlet
public static class StopTestServlet extends GenericServlet
{
int _initIndex;
int _destroyIndex;
@ -160,7 +161,7 @@ public class ServletContextHandlerTest
}
}
public class StopTestListener implements ServletContextListener
public static class StopTestListener implements ServletContextListener
{
int _initIndex;
int _destroyIndex;
@ -195,7 +196,7 @@ public class ServletContextHandlerTest
}
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
public void onStartup(Set<Class<?>> c, ServletContext ctx)
{
//add a programmatic listener
if (ctx.getAttribute("MySCI.startup") != null)
@ -251,7 +252,7 @@ public class ServletContextHandlerTest
public static class MySCIStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller
{
ServletContainerInitializer _sci = null;
ServletContainerInitializer _sci;
ContextHandler.Context _ctx;
MySCIStarter(ContextHandler.Context ctx, ServletContainerInitializer sci)
@ -593,7 +594,7 @@ public class ServletContextHandlerTest
}
}
public class InitialListener implements ServletContextListener
public static class InitialListener implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent sce)
@ -623,18 +624,18 @@ public class ServletContextHandlerTest
{
fail(e);
}
//And also test you can't add a ServletContextListener from a ServletContextListener
// And also test you can't add a ServletContextListener from a ServletContextListener
try
{
MyContextListener contextListener = sce.getServletContext().createListener(MyContextListener.class);
sce.getServletContext().addListener(contextListener);
fail("Adding SCL from an SCL!");
assertThrows(IllegalArgumentException.class, () -> sce.getServletContext().addListener(contextListener), "Adding SCI from an SCI!");
}
catch (IllegalArgumentException e)
{
//expected
}
catch (Exception x)
catch (ServletException x)
{
fail(x);
}
@ -750,7 +751,7 @@ public class ServletContextHandlerTest
}
@Test
public void testAddSessionListener() throws Exception
public void testAddSessionListener()
{
ContextHandlerCollection contexts = new ContextHandlerCollection();
_server.setHandler(contexts);
@ -831,7 +832,7 @@ public class ServletContextHandlerTest
ListenerHolder initialListener = new ListenerHolder();
initialListener.setListener(new InitialListener());
root.getServletHandler().addListener(initialListener);
ServletHolder holder0 = root.addServlet(TestServlet.class, "/test");
root.addServlet(TestServlet.class, "/test");
_server.start();
ListenerHolder[] listenerHolders = root.getServletHandler().getListeners();
@ -879,7 +880,7 @@ public class ServletContextHandlerTest
StringBuffer request = new StringBuffer();
request.append("GET /test?session=replace HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("Cookie: " + sessionid + "\n");
request.append("Cookie: ").append(sessionid).append("\n");
request.append("\n");
response = _connector.getResponse(request.toString());
assertThat(response, Matchers.containsString("200 OK"));
@ -890,7 +891,7 @@ public class ServletContextHandlerTest
request = new StringBuffer();
request.append("GET /test?session=remove HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("Cookie: " + sessionid + "\n");
request.append("Cookie: ").append(sessionid).append("\n");
request.append("\n");
response = _connector.getResponse(request.toString());
assertThat(response, Matchers.containsString("200 OK"));
@ -903,7 +904,7 @@ public class ServletContextHandlerTest
request = new StringBuffer();
request.append("GET /test?session=change HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("Cookie: " + sessionid + "\n");
request.append("Cookie: ").append(sessionid).append("\n");
request.append("\n");
response = _connector.getResponse(request.toString());
assertThat(response, Matchers.containsString("200 OK"));
@ -914,7 +915,7 @@ public class ServletContextHandlerTest
request = new StringBuffer();
request.append("GET /test?session=delete HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("Cookie: " + sessionid + "\n");
request.append("Cookie: ").append(sessionid).append("\n");
request.append("\n");
response = _connector.getResponse(request.toString());
assertThat(response, Matchers.containsString("200 OK"));
@ -994,12 +995,12 @@ public class ServletContextHandlerTest
}
@Test
public void testAddServletFromServlet() throws Exception
public void testAddServletFromServlet()
{
//A servlet cannot be added by another servlet
Logger logger = LoggerFactory.getLogger(ContextHandler.class.getName() + "ROOT");
try (StacklessLogging stackless = new StacklessLogging(logger))
try (StacklessLogging ignored = new StacklessLogging(logger))
{
ServletContextHandler context = new ServletContextHandler();
context.setLogger(logger);
@ -1008,17 +1009,8 @@ public class ServletContextHandlerTest
holder.setInitOrder(0);
context.setContextPath("/");
_server.setHandler(context);
_server.start();
fail("Servlet can only be added from SCI or SCL");
}
catch (Exception e)
{
if (e instanceof ServletException)
{
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
ServletException se = assertThrows(ServletException.class, _server::start);
assertThat("Servlet can only be added from SCI or SCL", se.getCause(), instanceOf(IllegalStateException.class));
}
}
@ -1093,12 +1085,12 @@ public class ServletContextHandlerTest
}
@Test
public void testAddFilterFromServlet() throws Exception
public void testAddFilterFromServlet()
{
//A filter cannot be added from a servlet
Logger logger = LoggerFactory.getLogger(ContextHandler.class.getName() + "ROOT");
try (StacklessLogging stackless = new StacklessLogging(logger))
try (StacklessLogging ignored = new StacklessLogging(logger))
{
ServletContextHandler context = new ServletContextHandler();
context.setLogger(logger);
@ -1107,34 +1099,25 @@ public class ServletContextHandlerTest
holder.setInitOrder(0);
context.setContextPath("/");
_server.setHandler(context);
_server.start();
fail("Filter can only be added from SCI or SCL");
}
catch (Exception e)
{
if (e instanceof ServletException)
{
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
ServletException se = assertThrows(ServletException.class, _server::start);
assertThat("Filter can only be added from SCI or SCL", se.getCause(), instanceOf(IllegalStateException.class));
}
}
@Test
public void testAddServletByClassFromFilter() throws Exception
public void testAddServletByClassFromFilter()
{
//A servlet cannot be added from a Filter
Logger logger = LoggerFactory.getLogger(ContextHandler.class.getName() + "ROOT");
try (StacklessLogging stackless = new StacklessLogging(logger))
try (StacklessLogging ignored = new StacklessLogging(logger))
{
ServletContextHandler context = new ServletContextHandler();
context.setLogger(logger);
FilterHolder holder = new FilterHolder(new Filter()
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
public void init(FilterConfig filterConfig)
{
ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", HelloServlet.class);
rego.addMapping("/hello/*");
@ -1142,7 +1125,6 @@ public class ServletContextHandlerTest
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
}
@ -1155,37 +1137,24 @@ public class ServletContextHandlerTest
context.getServletHandler().setStartWithUnavailable(false);
context.setContextPath("/");
_server.setHandler(context);
_server.start();
fail("Servlet can only be added from SCI or SCL");
}
catch (Exception e)
{
if (!(e instanceof IllegalStateException))
{
if (e instanceof ServletException)
{
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
}
assertThrows(IllegalStateException.class, _server::start, "Servlet can only be added from SCI or SCL");
}
}
@Test
public void testAddServletByInstanceFromFilter() throws Exception
public void testAddServletByInstanceFromFilter()
{
//A servlet cannot be added from a Filter
Logger logger = LoggerFactory.getLogger(ContextHandler.class.getName() + "ROOT");
try (StacklessLogging stackless = new StacklessLogging(logger))
try (StacklessLogging ignored = new StacklessLogging(logger))
{
ServletContextHandler context = new ServletContextHandler();
context.setLogger(logger);
FilterHolder holder = new FilterHolder(new Filter()
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
public void init(FilterConfig filterConfig)
{
ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", new HelloServlet());
rego.addMapping("/hello/*");
@ -1193,7 +1162,6 @@ public class ServletContextHandlerTest
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
}
@ -1206,37 +1174,24 @@ public class ServletContextHandlerTest
context.getServletHandler().setStartWithUnavailable(false);
context.setContextPath("/");
_server.setHandler(context);
_server.start();
fail("Servlet can only be added from SCI or SCL");
}
catch (Exception e)
{
if (!(e instanceof IllegalStateException))
{
if (e instanceof ServletException)
{
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
}
assertThrows(IllegalStateException.class, _server::start, "Servlet can only be added from SCI or SCL");
}
}
@Test
public void testAddServletByClassNameFromFilter() throws Exception
public void testAddServletByClassNameFromFilter()
{
//A servlet cannot be added from a Filter
Logger logger = LoggerFactory.getLogger(ContextHandler.class.getName() + "ROOT");
try (StacklessLogging stackless = new StacklessLogging(logger))
try (StacklessLogging ignored = new StacklessLogging(logger))
{
ServletContextHandler context = new ServletContextHandler();
context.setLogger(logger);
FilterHolder holder = new FilterHolder(new Filter()
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
public void init(FilterConfig filterConfig)
{
ServletRegistration rego = filterConfig.getServletContext().addServlet("hello", HelloServlet.class.getName());
rego.addMapping("/hello/*");
@ -1244,7 +1199,6 @@ public class ServletContextHandlerTest
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
}
@ -1257,20 +1211,7 @@ public class ServletContextHandlerTest
context.getServletHandler().setStartWithUnavailable(false);
context.setContextPath("/");
_server.setHandler(context);
_server.start();
fail("Servlet can only be added from SCI or SCL");
}
catch (Exception e)
{
if (!(e instanceof IllegalStateException))
{
if (e instanceof ServletException)
{
assertTrue(e.getCause() instanceof IllegalStateException);
}
else
fail(e);
}
assertThrows(IllegalStateException.class, _server::start, "Servlet can only be added from SCI or SCL");
}
}
@ -1299,7 +1240,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /hello HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1319,7 +1260,7 @@ public class ServletContextHandlerTest
class ServletAddingSCI implements ServletContainerInitializer
{
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException
public void onStartup(Set<Class<?>> c, ServletContext ctx)
{
ServletRegistration rego = ctx.addServlet("hello", HelloServlet.class);
rego.addMapping("/hello/*");
@ -1329,7 +1270,7 @@ public class ServletContextHandlerTest
root.addBean(new MySCIStarter(root.getServletContext(), new ServletAddingSCI()), true);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /hello HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1491,7 +1432,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1511,7 +1452,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1537,7 +1478,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1564,7 +1505,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1590,7 +1531,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1613,7 +1554,7 @@ public class ServletContextHandlerTest
_server.setHandler(context);
_server.start();
StringBuffer request = new StringBuffer();
StringBuilder request = new StringBuilder();
request.append("GET /test HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("\n");
@ -1657,7 +1598,7 @@ public class ServletContextHandlerTest
}
@Test
public void testSetSecurityHandler() throws Exception
public void testSetSecurityHandler()
{
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS | ServletContextHandler.SECURITY);
assertNotNull(context.getSessionHandler());
@ -1683,7 +1624,7 @@ public class ServletContextHandlerTest
@Override
protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response,
RoleInfo constraintInfo) throws IOException
RoleInfo constraintInfo)
{
return false;
}
@ -1697,7 +1638,6 @@ public class ServletContextHandlerTest
@Override
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response,
Object constraintInfo, UserIdentity userIdentity)
throws IOException
{
return false;
}
@ -1803,7 +1743,7 @@ public class ServletContextHandlerTest
list.addHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.sendError(404, "Fell Through");
}
@ -1896,6 +1836,7 @@ public class ServletContextHandlerTest
}
}
@SuppressWarnings("deprecation")
public static class DecoratedObjectFactoryServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@ -2014,7 +1955,7 @@ public class ServletContextHandlerTest
}
else if ("change".equalsIgnoreCase(action))
{
HttpSession session = req.getSession(true);
req.getSession(true);
req.changeSessionId();
}
else if ("replace".equalsIgnoreCase(action))

View File

@ -104,6 +104,16 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
reload();
}
@ManagedOperation(value = "Scan for changes in the SSL Keystore", impact = "ACTION")
public void scan()
{
if (LOG.isDebugEnabled())
LOG.debug("scanning");
_scanner.scan();
_scanner.scan();
}
@ManagedOperation(value = "Reload the SSL Keystore", impact = "ACTION")
public void reload()
{

View File

@ -111,6 +111,11 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon
this.wsClient = webSocketClient;
this.futureCoreSession = new CompletableFuture<>();
this.futureCoreSession.whenComplete((session, throwable) ->
{
if (throwable != null)
abort(throwable);
});
}
public void setConfiguration(Configuration.ConfigurationCustomizer config)
@ -439,7 +444,16 @@ public abstract class ClientUpgradeRequest extends HttpRequest implements Respon
try
{
endPoint.upgrade(wsConnection);
futureCoreSession.complete(coreSession);
// Try to complete the future but if we could't we should abort the CoreSession
if (!futureCoreSession.complete(coreSession))
{
futureCoreSession.exceptionally(t ->
{
coreSession.processConnectionError(t, Callback.NOOP);
return null;
});
}
}
catch (Throwable t)
{

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.core.internal;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Executor;
@ -83,6 +84,29 @@ public class WebSocketConnection extends AbstractConnection implements Connectio
Scheduler scheduler,
ByteBufferPool bufferPool,
WebSocketCoreSession coreSession)
{
this(endp, executor, scheduler, bufferPool, coreSession, null);
}
/**
* Create a WSConnection.
* <p>
* It is assumed that the WebSocket Upgrade Handshake has already
* completed successfully before creating this connection.
* </p>
* @param endp The endpoint ever which Websockot is sent/received
* @param executor A thread executor to use for WS callbacks.
* @param scheduler A scheduler to use for timeouts
* @param bufferPool A pool of buffers to use.
* @param coreSession The WC core session to which frames are delivered.
* @param randomMask A Random used to mask frames. If null then SecureRandom will be created if needed.
*/
public WebSocketConnection(EndPoint endp,
Executor executor,
Scheduler scheduler,
ByteBufferPool bufferPool,
WebSocketCoreSession coreSession,
Random randomMask)
{
super(endp, executor);
@ -92,15 +116,15 @@ public class WebSocketConnection extends AbstractConnection implements Connectio
Objects.requireNonNull(bufferPool, "ByteBufferPool");
this.bufferPool = bufferPool;
this.coreSession = coreSession;
this.generator = new Generator();
this.parser = new Parser(bufferPool, coreSession);
this.flusher = new Flusher(scheduler, coreSession.getOutputBufferSize(), generator, endp);
this.setInputBufferSize(coreSession.getInputBufferSize());
this.random = this.coreSession.getBehavior() == Behavior.CLIENT ? new Random(endp.hashCode()) : null;
if (this.coreSession.getBehavior() == Behavior.CLIENT && randomMask == null)
randomMask = new SecureRandom();
this.random = randomMask;
}
@Override

View File

@ -19,7 +19,6 @@
package org.eclipse.jetty.websocket.javax.client.internal;
import javax.websocket.ClientEndpoint;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketContainer;
@ -49,7 +48,7 @@ public class JavaxWebSocketClientFrameHandlerFactory extends JavaxWebSocketFrame
public JavaxWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
{
if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass))
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
return createEndpointMetadata(endpointConfig);
if (endpointClass.getAnnotation(ClientEndpoint.class) == null)
return null;

View File

@ -250,20 +250,20 @@ public abstract class JavaxWebSocketFrameHandlerFactory
}
}
protected JavaxWebSocketFrameHandlerMetadata createEndpointMetadata(Class<? extends Endpoint> endpointClass, EndpointConfig endpointConfig)
protected JavaxWebSocketFrameHandlerMetadata createEndpointMetadata(EndpointConfig endpointConfig)
{
JavaxWebSocketFrameHandlerMetadata metadata = new JavaxWebSocketFrameHandlerMetadata(endpointConfig);
MethodHandles.Lookup lookup = getApplicationMethodHandleLookup(endpointClass);
MethodHandles.Lookup lookup = getServerMethodHandleLookup();
Method openMethod = ReflectUtils.findMethod(endpointClass, "onOpen", Session.class, EndpointConfig.class);
Method openMethod = ReflectUtils.findMethod(Endpoint.class, "onOpen", Session.class, EndpointConfig.class);
MethodHandle open = toMethodHandle(lookup, openMethod);
metadata.setOpenHandler(open, openMethod);
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onClose", Session.class, CloseReason.class);
Method closeMethod = ReflectUtils.findMethod(Endpoint.class, "onClose", Session.class, CloseReason.class);
MethodHandle close = toMethodHandle(lookup, closeMethod);
metadata.setCloseHandler(close, closeMethod);
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onError", Session.class, Throwable.class);
Method errorMethod = ReflectUtils.findMethod(Endpoint.class, "onError", Session.class, Throwable.class);
MethodHandle error = toMethodHandle(lookup, errorMethod);
metadata.setErrorHandler(error, errorMethod);

View File

@ -93,6 +93,7 @@ public class JavaxWebSocketSession implements javax.websocket.Session
}
this.userProperties = endpointConfig.getUserProperties();
container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionCreated(this));
}
public CoreSession getCoreSession()

View File

@ -20,7 +20,15 @@ package org.eclipse.jetty.websocket.javax.common;
public interface JavaxWebSocketSessionListener
{
void onJavaxWebSocketSessionOpened(JavaxWebSocketSession session);
default void onJavaxWebSocketSessionCreated(JavaxWebSocketSession session)
{
}
void onJavaxWebSocketSessionClosed(JavaxWebSocketSession session);
default void onJavaxWebSocketSessionOpened(JavaxWebSocketSession session)
{
}
default void onJavaxWebSocketSessionClosed(JavaxWebSocketSession session)
{
}
}

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.websocket.javax.common;
import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import org.eclipse.jetty.websocket.util.InvokerUtils;
@ -43,7 +42,7 @@ public class DummyFrameHandlerFactory extends JavaxWebSocketFrameHandlerFactory
{
if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass))
{
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
return createEndpointMetadata(endpointConfig);
}
if (endpointClass.getAnnotation(ClientEndpoint.class) == null)

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.websocket.javax.server.internal;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.server.ServerEndpoint;
@ -42,7 +41,7 @@ public class JavaxWebSocketServerFrameHandlerFactory extends JavaxWebSocketClien
public JavaxWebSocketFrameHandlerMetadata getMetadata(Class<?> endpointClass, EndpointConfig endpointConfig)
{
if (javax.websocket.Endpoint.class.isAssignableFrom(endpointClass))
return createEndpointMetadata((Class<? extends Endpoint>)endpointClass, endpointConfig);
return createEndpointMetadata(endpointConfig);
ServerEndpoint anno = endpointClass.getAnnotation(ServerEndpoint.class);
if (anno == null)

View File

@ -50,11 +50,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class JavaxOnCloseTest
{
private static BlockingArrayQueue<OnCloseEndpoint> serverEndpoints = new BlockingArrayQueue<>();
private static final BlockingArrayQueue<OnCloseEndpoint> serverEndpoints = new BlockingArrayQueue<>();
private Server server;
private ServerConnector connector;
private JavaxWebSocketClientContainer client = new JavaxWebSocketClientContainer();
private final JavaxWebSocketClientContainer client = new JavaxWebSocketClientContainer();
@ServerEndpoint("/")
public static class OnCloseEndpoint extends EventSocket
@ -84,7 +84,7 @@ public class JavaxOnCloseTest
@ClientEndpoint
public static class BlockingClientEndpoint extends EventSocket
{
private CountDownLatch blockInClose = new CountDownLatch(1);
private final CountDownLatch blockInClose = new CountDownLatch(1);
public void unBlockClose()
{

View File

@ -18,13 +18,18 @@
package org.eclipse.jetty.websocket.javax.tests.client;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.ContainerProvider;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.websocket.javax.common.JavaxWebSocketSession;
import org.eclipse.jetty.websocket.javax.tests.LocalServer;
import org.eclipse.jetty.websocket.javax.tests.WSEndpointTracker;
@ -35,6 +40,7 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class EndpointEchoTest
{
@ -105,4 +111,45 @@ public class EndpointEchoTest
session.close();
endpoint.awaitCloseEvent("Client");
}
@Test
public void testEchoAnonymousInstance() throws Exception
{
CountDownLatch openLatch = new CountDownLatch(1);
CountDownLatch closeLatch = new CountDownLatch(1);
BlockingQueue<String> textMessages = new BlockingArrayQueue<>();
Endpoint clientEndpoint = new Endpoint()
{
@Override
public void onOpen(Session session, EndpointConfig config)
{
// Cannot replace this with a lambda or it breaks ReflectUtils.findGenericClassFor().
session.addMessageHandler(new MessageHandler.Whole<String>()
{
@Override
public void onMessage(String message)
{
textMessages.add(message);
}
});
openLatch.countDown();
}
@Override
public void onClose(Session session, CloseReason closeReason)
{
closeLatch.countDown();
}
};
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session session = container.connectToServer(clientEndpoint, null, server.getWsUri().resolve("/echo/text"));
assertTrue(openLatch.await(5, TimeUnit.SECONDS));
session.getBasicRemote().sendText("Echo");
String resp = textMessages.poll(1, TimeUnit.SECONDS);
assertThat("Response echo", resp, is("Echo"));
session.close();
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -23,6 +23,10 @@ package org.eclipse.jetty.websocket.api;
*/
public interface WebSocketSessionListener
{
default void onWebSocketSessionCreated(Session session)
{
}
default void onWebSocketSessionOpened(Session session)
{
}

View File

@ -52,6 +52,7 @@ import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandler;
import org.eclipse.jetty.websocket.common.JettyWebSocketFrameHandlerFactory;
import org.eclipse.jetty.websocket.common.SessionTracker;
import org.eclipse.jetty.websocket.core.Configuration;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.eclipse.jetty.websocket.core.WebSocketComponents;
import org.eclipse.jetty.websocket.core.client.UpgradeListener;
import org.eclipse.jetty.websocket.core.client.WebSocketCoreClient;
@ -154,7 +155,8 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli
}
CompletableFuture<Session> futureSession = new CompletableFuture<>();
coreClient.connect(upgradeRequest).whenComplete((coreSession, error) ->
CompletableFuture<CoreSession> coreConnect = coreClient.connect(upgradeRequest);
coreConnect.whenComplete((coreSession, error) ->
{
if (error != null)
{
@ -166,6 +168,12 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketPoli
futureSession.complete(frameHandler.getSession());
});
// If the returned future is cancelled we want to try to cancel the core future if possible.
futureSession.whenComplete((session, throwable) ->
{
if (throwable != null)
coreConnect.completeExceptionally(throwable);
});
return futureSession;
}

View File

@ -153,7 +153,7 @@ public class JettyWebSocketFrameHandler implements FrameHandler
try
{
customizer.customize(coreSession);
session = new WebSocketSession(coreSession, this);
session = new WebSocketSession(container, coreSession, this);
frameHandle = InvokerUtils.bindTo(frameHandle, session);
openHandle = InvokerUtils.bindTo(openHandle, session);

View File

@ -195,28 +195,31 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
private JettyWebSocketFrameHandlerMetadata createListenerMetadata(Class<?> endpointClass)
{
JettyWebSocketFrameHandlerMetadata metadata = new JettyWebSocketFrameHandlerMetadata();
MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getApplicationMethodHandleLookup(endpointClass);
MethodHandles.Lookup lookup = JettyWebSocketFrameHandlerFactory.getServerMethodHandleLookup();
Method openMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketConnect", Session.class);
if (!WebSocketConnectionListener.class.isAssignableFrom(endpointClass))
throw new IllegalArgumentException("Class " + endpointClass + " does not implement " + WebSocketConnectionListener.class);
Method openMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketConnect", Session.class);
MethodHandle open = toMethodHandle(lookup, openMethod);
metadata.setOpenHandler(open, openMethod);
Method closeMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketClose", int.class, String.class);
Method closeMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketClose", int.class, String.class);
MethodHandle close = toMethodHandle(lookup, closeMethod);
metadata.setCloseHandler(close, closeMethod);
Method errorMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketError", Throwable.class);
Method errorMethod = ReflectUtils.findMethod(WebSocketConnectionListener.class, "onWebSocketError", Throwable.class);
MethodHandle error = toMethodHandle(lookup, errorMethod);
metadata.setErrorHandler(error, errorMethod);
// Simple Data Listener
if (WebSocketListener.class.isAssignableFrom(endpointClass))
{
Method textMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketText", String.class);
Method textMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketText", String.class);
MethodHandle text = toMethodHandle(lookup, textMethod);
metadata.setTextHandler(StringMessageSink.class, text, textMethod);
Method binaryMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketBinary", byte[].class, int.class, int.class);
Method binaryMethod = ReflectUtils.findMethod(WebSocketListener.class, "onWebSocketBinary", byte[].class, int.class, int.class);
MethodHandle binary = toMethodHandle(lookup, binaryMethod);
metadata.setBinaryHandle(ByteArrayMessageSink.class, binary, binaryMethod);
}
@ -224,11 +227,11 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
// Ping/Pong Listener
if (WebSocketPingPongListener.class.isAssignableFrom(endpointClass))
{
Method pongMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPong", ByteBuffer.class);
Method pongMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPong", ByteBuffer.class);
MethodHandle pong = toMethodHandle(lookup, pongMethod);
metadata.setPongHandle(pong, pongMethod);
Method pingMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPing", ByteBuffer.class);
Method pingMethod = ReflectUtils.findMethod(WebSocketPingPongListener.class, "onWebSocketPing", ByteBuffer.class);
MethodHandle ping = toMethodHandle(lookup, pingMethod);
metadata.setPingHandle(ping, pingMethod);
}
@ -236,11 +239,11 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
// Partial Data / Message Listener
if (WebSocketPartialListener.class.isAssignableFrom(endpointClass))
{
Method textMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPartialText", String.class, boolean.class);
Method textMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialText", String.class, boolean.class);
MethodHandle text = toMethodHandle(lookup, textMethod);
metadata.setTextHandler(PartialStringMessageSink.class, text, textMethod);
Method binaryMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class);
Method binaryMethod = ReflectUtils.findMethod(WebSocketPartialListener.class, "onWebSocketPartialBinary", ByteBuffer.class, boolean.class);
MethodHandle binary = toMethodHandle(lookup, binaryMethod);
metadata.setBinaryHandle(PartialByteBufferMessageSink.class, binary, binaryMethod);
}
@ -248,7 +251,7 @@ public class JettyWebSocketFrameHandlerFactory extends ContainerLifeCycle
// Frame Listener
if (WebSocketFrameListener.class.isAssignableFrom(endpointClass))
{
Method frameMethod = ReflectUtils.findMethod(endpointClass, "onWebSocketFrame", Frame.class);
Method frameMethod = ReflectUtils.findMethod(WebSocketFrameListener.class, "onWebSocketFrame", Frame.class);
MethodHandle frame = toMethodHandle(lookup, frameMethod);
metadata.setFrameHandler(frame, frameMethod);
}

View File

@ -26,10 +26,12 @@ import java.util.Objects;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.SuspendToken;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
import org.eclipse.jetty.websocket.api.WebSocketContainer;
import org.eclipse.jetty.websocket.core.CoreSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,19 +45,20 @@ public class WebSocketSession implements Session, SuspendToken, Dumpable
private final UpgradeRequest upgradeRequest;
private final UpgradeResponse upgradeResponse;
public WebSocketSession(CoreSession coreSession, JettyWebSocketFrameHandler frameHandler)
public WebSocketSession(WebSocketContainer container, CoreSession coreSession, JettyWebSocketFrameHandler frameHandler)
{
this.frameHandler = Objects.requireNonNull(frameHandler);
this.coreSession = Objects.requireNonNull(coreSession);
this.upgradeRequest = frameHandler.getUpgradeRequest();
this.upgradeResponse = frameHandler.getUpgradeResponse();
this.remoteEndpoint = new JettyWebSocketRemoteEndpoint(coreSession, frameHandler.getBatchMode());
container.notifySessionListeners((listener) -> listener.onWebSocketSessionCreated(this));
}
@Override
public void close()
{
remoteEndpoint.close();
remoteEndpoint.close(StatusCode.NORMAL, null);
}
@Override

View File

@ -44,6 +44,7 @@ import org.eclipse.jetty.websocket.core.exception.WebSocketException;
import org.eclipse.jetty.websocket.core.server.WebSocketServerComponents;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.server.internal.JettyServerFrameHandlerFactory;
import org.eclipse.jetty.websocket.util.ReflectUtils;
import org.eclipse.jetty.websocket.util.ShutdownUtil;
import org.eclipse.jetty.websocket.util.server.internal.FrameHandlerFactory;
import org.eclipse.jetty.websocket.util.server.internal.WebSocketMapping;
@ -134,6 +135,24 @@ public class JettyWebSocketServerContainer extends ContainerLifeCycle implements
frameHandlerFactory, customizer);
}
public void addMapping(String pathSpec, final Class<?> endpointClass)
{
if (!ReflectUtils.isDefaultConstructable(endpointClass))
throw new IllegalArgumentException("Cannot access default constructor for the class: " + endpointClass.getName());
addMapping(pathSpec, (req, resp) ->
{
try
{
return endpointClass.getDeclaredConstructor().newInstance();
}
catch (Exception e)
{
throw new org.eclipse.jetty.websocket.api.WebSocketException("Unable to create instance of " + endpointClass.getName(), e);
}
});
}
@Override
public Executor getExecutor()
{

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.core.CloseStatus;
import org.eclipse.jetty.websocket.core.Frame;
import org.eclipse.jetty.websocket.core.OpCode;
import org.eclipse.jetty.websocket.core.internal.Generator;
@ -150,7 +151,7 @@ public class WebSocketStatsTest
assertThat(statistics.getReceivedMessages(), is(numMessages + 2L));
Frame textFrame = new Frame(OpCode.TEXT, msgText);
Frame closeFrame = new Frame(OpCode.CLOSE);
Frame closeFrame = CloseStatus.NORMAL_STATUS.toFrame();
final long textFrameSize = getFrameByteSize(textFrame);
final long closeFrameSize = getFrameByteSize(closeFrame);

View File

@ -159,7 +159,7 @@ public class ClientConfigTest
assertNull(clientEndpoint.error);
assertTrue(serverSocket.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(serverSocket.closeCode, is(StatusCode.NO_CODE));
assertThat(serverSocket.closeCode, is(StatusCode.NORMAL));
}
@ParameterizedTest

View File

@ -0,0 +1,381 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.websocket.tests.client;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.UpgradeException;
import org.eclipse.jetty.websocket.api.WebSocketException;
import org.eclipse.jetty.websocket.api.WebSocketSessionListener;
import org.eclipse.jetty.websocket.api.util.WSURI;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.JettyUpgradeListener;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.tests.CloseTrackingEndpoint;
import org.eclipse.jetty.websocket.tests.EchoSocket;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class ConnectFutureTest
{
private Server server;
private WebSocketClient client;
public void start(Consumer<JettyWebSocketServerContainer> configuration) throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler contextHandler = new ServletContextHandler();
contextHandler.setContextPath("/");
server.setHandler(contextHandler);
JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
configuration.accept(container));
server.start();
client = new WebSocketClient();
client.start();
}
@AfterEach
public void stop() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
}
@Test
public void testAbortDuringCreator() throws Exception
{
CountDownLatch enteredCreator = new CountDownLatch(1);
CountDownLatch exitCreator = new CountDownLatch(1);
start(c ->
{
c.addMapping("/", (req, res) ->
{
try
{
enteredCreator.countDown();
exitCreator.await();
return new EchoSocket();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
});
});
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
// Cancel the future once we have entered the servers WebSocketCreator (after upgrade request is sent).
assertTrue(enteredCreator.await(5, TimeUnit.SECONDS));
assertTrue(connect.cancel(true));
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
exitCreator.countDown();
assertFalse(clientSocket.openLatch.await(1, TimeUnit.SECONDS));
Throwable error = clientSocket.error.get();
assertThat(error, instanceOf(UpgradeException.class));
Throwable cause = error.getCause();
assertThat(cause, instanceOf(org.eclipse.jetty.websocket.core.exception.UpgradeException.class));
assertThat(cause.getCause(), instanceOf(CancellationException.class));
}
@Test
public void testAbortSessionOnCreated() throws Exception
{
start(c -> c.addMapping("/", EchoSocket.class));
CountDownLatch enteredListener = new CountDownLatch(1);
CountDownLatch exitListener = new CountDownLatch(1);
client.addSessionListener(new WebSocketSessionListener()
{
@Override
public void onWebSocketSessionCreated(Session session)
{
try
{
enteredListener.countDown();
exitListener.await();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
}
});
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
// Abort when session is created, this is during the connection upgrade.
assertTrue(enteredListener.await(5, TimeUnit.SECONDS));
assertTrue(connect.cancel(true));
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
exitListener.countDown();
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
assertThat(clientSocket.error.get(), instanceOf(CancellationException.class));
}
@Test
public void testAbortInHandshakeResponse() throws Exception
{
start(c -> c.addMapping("/", EchoSocket.class));
CountDownLatch enteredListener = new CountDownLatch(1);
CountDownLatch exitListener = new CountDownLatch(1);
JettyUpgradeListener upgradeListener = new JettyUpgradeListener()
{
@Override
public void onHandshakeResponse(HttpRequest request, HttpResponse response)
{
try
{
enteredListener.countDown();
exitListener.await();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
}
};
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
ClientUpgradeRequest upgradeRequest = new ClientUpgradeRequest();
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()), upgradeRequest, upgradeListener);
// Abort after after handshake response, this is during the connection upgrade.
assertTrue(enteredListener.await(5, TimeUnit.SECONDS));
assertTrue(connect.cancel(true));
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
exitListener.countDown();
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
assertThat(clientSocket.error.get(), instanceOf(CancellationException.class));
}
@Test
public void testAbortOnOpened() throws Exception
{
start(c -> c.addMapping("/", EchoSocket.class));
CountDownLatch exitOnOpen = new CountDownLatch(1);
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint()
{
@Override
public void onWebSocketConnect(Session session)
{
try
{
super.onWebSocketConnect(session);
exitOnOpen.await();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
}
};
// Abort during the call to onOpened. This is after the connection upgrade, but before future completion.
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
assertTrue(connect.cancel(true));
exitOnOpen.countDown();
// We got an error on the WebSocket endpoint and an error from the future.
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
assertThrows(CancellationException.class, () -> connect.get(5, TimeUnit.SECONDS));
}
@Test
public void testAbortAfterCompletion() throws Exception
{
start(c -> c.addMapping("/", EchoSocket.class));
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
Session session = connect.get(5, TimeUnit.SECONDS);
// If we can send and receive messages the future has been completed.
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
clientSocket.getSession().getRemote().sendString("hello");
assertThat(clientSocket.messageQueue.poll(5, TimeUnit.SECONDS), Matchers.is("hello"));
// After it has been completed we should not get any errors from cancelling it.
assertFalse(connect.cancel(true));
assertThat(connect.get(5, TimeUnit.SECONDS), instanceOf(Session.class));
assertFalse(clientSocket.closeLatch.await(1, TimeUnit.SECONDS));
assertNull(clientSocket.error.get());
// Close the session properly.
session.close();
assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(clientSocket.closeCode, is(StatusCode.NORMAL));
}
@Test
public void testFutureTimeout() throws Exception
{
CountDownLatch exitCreator = new CountDownLatch(1);
start(c ->
{
c.addMapping("/", (req, res) ->
{
try
{
exitCreator.await();
return new EchoSocket();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
});
});
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
Future<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
assertThrows(TimeoutException.class, () -> connect.get(1, TimeUnit.SECONDS));
exitCreator.countDown();
Session session = connect.get(5, TimeUnit.SECONDS);
// Close the session properly.
session.close();
assertTrue(clientSocket.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(clientSocket.closeCode, is(StatusCode.NORMAL));
}
@Test
public void testAbortWithExceptionBeforeUpgrade() throws Exception
{
CountDownLatch exitCreator = new CountDownLatch(1);
start(c ->
{
c.addMapping("/", (req, res) ->
{
try
{
exitCreator.await();
return new EchoSocket();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
});
});
// Complete the CompletableFuture with an exception the during the call to onOpened.
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint();
CompletableFuture<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
assertTrue(connect.completeExceptionally(new WebSocketException("custom exception")));
exitCreator.countDown();
// Exception from the future is correct.
ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS));
Throwable futureCause = futureError.getCause();
assertThat(futureCause, instanceOf(WebSocketException.class));
assertThat(futureCause.getMessage(), is("custom exception"));
// Exception from the endpoint is correct.
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
Throwable upgradeException = clientSocket.error.get();
assertThat(upgradeException, instanceOf(UpgradeException.class));
Throwable coreUpgradeException = upgradeException.getCause();
assertThat(coreUpgradeException, instanceOf(org.eclipse.jetty.websocket.core.exception.UpgradeException.class));
Throwable cause = coreUpgradeException.getCause();
assertThat(cause, instanceOf(WebSocketException.class));
assertThat(cause.getMessage(), is("custom exception"));
}
@Test
public void testAbortWithExceptionAfterUpgrade() throws Exception
{
start(c -> c.addMapping("/", EchoSocket.class));
CountDownLatch exitOnOpen = new CountDownLatch(1);
CloseTrackingEndpoint clientSocket = new CloseTrackingEndpoint()
{
@Override
public void onWebSocketConnect(Session session)
{
try
{
super.onWebSocketConnect(session);
exitOnOpen.await();
}
catch (InterruptedException e)
{
throw new IllegalStateException(e);
}
}
};
// Complete the CompletableFuture with an exception the during the call to onOpened.
CompletableFuture<Session> connect = client.connect(clientSocket, WSURI.toWebsocket(server.getURI()));
assertTrue(clientSocket.openLatch.await(5, TimeUnit.SECONDS));
assertTrue(connect.completeExceptionally(new WebSocketException("custom exception")));
exitOnOpen.countDown();
// Exception from the future is correct.
ExecutionException futureError = assertThrows(ExecutionException.class, () -> connect.get(5, TimeUnit.SECONDS));
Throwable cause = futureError.getCause();
assertThat(cause, instanceOf(WebSocketException.class));
assertThat(cause.getMessage(), is("custom exception"));
// Exception from the endpoint is correct.
assertTrue(clientSocket.errorLatch.await(5, TimeUnit.SECONDS));
Throwable endpointError = clientSocket.error.get();
assertThat(endpointError, instanceOf(WebSocketException.class));
assertThat(endpointError.getMessage(), is("custom exception"));
}
}

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.websocket.tests.listeners;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -28,13 +30,18 @@ import java.util.stream.Stream;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.websocket.tests.EchoSocket;
import org.eclipse.jetty.websocket.tests.EventSocket;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -60,6 +67,8 @@ public class WebSocketListenerTest
contextHandler.setContextPath("/");
JettyWebSocketServletContainerInitializer.configure(contextHandler, (context, container) ->
{
container.addMapping("/echo", (req, res) -> new EchoSocket());
for (Class<?> c : getClassListFromArguments(TextListeners.getTextListeners()))
{
container.addMapping("/text/" + c.getSimpleName(), (req, res) -> construct(c));
@ -125,6 +134,47 @@ public class WebSocketListenerTest
assertThat(clientEndpoint.closeReason, is("standard close"));
}
@Test
public void testAnonymousListener() throws Exception
{
CountDownLatch openLatch = new CountDownLatch(1);
CountDownLatch closeLatch = new CountDownLatch(1);
BlockingQueue<String> textMessages = new BlockingArrayQueue<>();
WebSocketListener clientEndpoint = new WebSocketListener()
{
@Override
public void onWebSocketConnect(Session session)
{
openLatch.countDown();
}
@Override
public void onWebSocketText(String message)
{
textMessages.add(message);
}
@Override
public void onWebSocketClose(int statusCode, String reason)
{
closeLatch.countDown();
}
};
Session session = client.connect(clientEndpoint, serverUri.resolve("/echo")).get(5, TimeUnit.SECONDS);
assertTrue(openLatch.await(5, TimeUnit.SECONDS));
// Send and receive echo on client.
String payload = "hello world";
session.getRemote().sendString(payload);
String echoMessage = textMessages.poll(5, TimeUnit.SECONDS);
assertThat(echoMessage, is(payload));
// Close normally.
session.close(StatusCode.NORMAL, "standard close");
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
private List<Class<?>> getClassListFromArguments(Stream<Arguments> stream)
{
return stream.map(arguments -> (Class<?>)arguments.get()[0]).collect(Collectors.toList());

View File

@ -230,7 +230,7 @@ public class ServerConfigTest
assertNull(serverEndpoint.error);
assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(clientEndpoint.closeCode, is(StatusCode.NO_CODE));
assertThat(clientEndpoint.closeCode, is(StatusCode.NORMAL));
listener.assertClosed();
}

View File

@ -467,7 +467,7 @@ public class DistributionTests extends AbstractDistributionTest
assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message"));
session.close();
assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE));
assertThat(webSocketListener.closeCode, is(StatusCode.NORMAL));
// Verify that /test2 and /test3 could not be started.
ContentResponse response = client.GET(serverUri.resolve("/test2/badonopen/a"));
@ -482,7 +482,7 @@ public class DistributionTests extends AbstractDistributionTest
assertThat(webSocketListener.textMessages.poll(5, TimeUnit.SECONDS), is("echo message"));
session.close();
assertTrue(webSocketListener.closeLatch.await(5, TimeUnit.SECONDS));
assertThat(webSocketListener.closeCode, is(StatusCode.NO_CODE));
assertThat(webSocketListener.closeCode, is(StatusCode.NORMAL));
}
}
}

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.opentest4j.TestAbortedException;
import static org.eclipse.jetty.http.client.Transport.UNIX_SOCKET;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@ -507,7 +508,7 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
// connect to them will hang the connection attempt, which is
// what we want to simulate in this test.
socket.connect(new InetSocketAddress(host, port), connectTimeout);
// Abort the test if we can connect.
// Fail the test if we can connect.
fail("Error: Should not have been able to connect to " + host + ":" + port);
}
catch (SocketTimeoutException ignored)
@ -517,7 +518,7 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
catch (Throwable x)
{
// Abort if any other exception happens.
fail(x);
throw new TestAbortedException("Not able to validate connect timeout conditions", x);
}
}

View File

@ -24,7 +24,6 @@ import java.nio.file.Path;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Calendar;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
@ -57,10 +56,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
@ExtendWith(WorkDirExtension.class)
public class KeyStoreScannerTest
{
private static final int scanInterval = 1;
public WorkDir testdir;
private Server server;
private Path keystoreDir;
private KeyStoreScanner keystoreScanner;
@BeforeEach
public void before()
@ -99,8 +98,8 @@ public class KeyStoreScannerTest
server.addConnector(connector);
// Configure Keystore Reload.
KeyStoreScanner keystoreScanner = new KeyStoreScanner(sslContextFactory);
keystoreScanner.setScanInterval(scanInterval);
keystoreScanner = new KeyStoreScanner(sslContextFactory);
keystoreScanner.setScanInterval(0);
server.addBean(keystoreScanner);
server.start();
@ -123,7 +122,7 @@ public class KeyStoreScannerTest
// Switch to use newKeystore which has a later expiry date.
useKeystore("newKeystore");
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
// The scanner should have detected the updated keystore, expiry should be renewed.
X509Certificate cert2 = getCertificateFromServer();
@ -143,11 +142,11 @@ public class KeyStoreScannerTest
try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class))
{
useKeystore("badKeystore");
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
}
// The good keystore is removed, now the bad keystore now causes an exception.
assertThrows(Throwable.class, () -> getCertificateFromServer());
assertThrows(Throwable.class, this::getCertificateFromServer);
}
@Test
@ -163,15 +162,15 @@ public class KeyStoreScannerTest
try (StacklessLogging ignored = new StacklessLogging(KeyStoreScanner.class))
{
useKeystore(null);
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
}
// The good keystore is removed, having no keystore causes an exception.
assertThrows(Throwable.class, () -> getCertificateFromServer());
assertThrows(Throwable.class, this::getCertificateFromServer);
// Switch to use keystore2 which has a later expiry date.
useKeystore("newKeystore");
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
X509Certificate cert2 = getCertificateFromServer();
assertThat(getExpiryYear(cert2), is(2020));
}
@ -195,7 +194,7 @@ public class KeyStoreScannerTest
// Change the symlink to point to the newKeystore file location which has a later expiry date.
Files.delete(keystorePath);
Files.createSymbolicLink(keystorePath, useKeystore("newKeystore"));
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
// The scanner should have detected the updated keystore, expiry should be renewed.
X509Certificate cert2 = getCertificateFromServer();
@ -220,7 +219,7 @@ public class KeyStoreScannerTest
// Change the target file of the symlink to the newKeystore which has a later expiry date.
useKeystore("newKeystore");
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis());
keystoreScanner.scan();
// The scanner should have detected the updated keystore, expiry should be renewed.
X509Certificate cert2 = getCertificateFromServer();