diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java
index 0502fb2f65c..bf18c00f92e 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/Http2Server.java
@@ -46,7 +46,6 @@ import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
-import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
@@ -56,7 +55,6 @@ import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.PushCacheFilter;
-import org.eclipse.jetty.servlets.PushSessionCacheFilter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -109,7 +107,6 @@ public class Http2Server
// HTTP/2 Connection Factory
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(https_config);
- NegotiatingServerConnectionFactory.checkProtocolNegotiationAvailable();
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
alpn.setDefaultProtocol(http.getDefaultProtocol());
diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
index a83012d9327..68922a42dbd 100644
--- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
+++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
@@ -19,8 +19,10 @@
package org.eclipse.jetty.alpn.client;
import java.io.IOException;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.ServiceLoader;
import java.util.concurrent.Executor;
import javax.net.ssl.SSLEngine;
@@ -29,27 +31,58 @@ import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
+import org.eclipse.jetty.io.ssl.SslHandshakeListener;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
-public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory
+public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory implements SslHandshakeListener
{
+ private final SslHandshakeListener alpnListener = new ALPNListener();
private final Executor executor;
private final List protocols;
+ private final ALPNProcessor.Client alpnProcessor;
public ALPNClientConnectionFactory(Executor executor, ClientConnectionFactory connectionFactory, List protocols)
{
super(connectionFactory);
- this.executor = executor;
- this.protocols = protocols;
if (protocols.isEmpty())
throw new IllegalArgumentException("ALPN protocol list cannot be empty");
+ this.executor = executor;
+ this.protocols = protocols;
+ Iterator processors = ServiceLoader.load(ALPNProcessor.Client.class).iterator();
+ alpnProcessor = processors.hasNext() ? processors.next() : ALPNProcessor.Client.NOOP;
+ }
+
+ public ALPNProcessor.Client getALPNProcessor()
+ {
+ return alpnProcessor;
}
@Override
public Connection newConnection(EndPoint endPoint, Map context) throws IOException
{
+ SSLEngine sslEngine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY);
+ getALPNProcessor().configure(sslEngine, protocols);
+ ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
+ // Method addBean() has set semantic, so the listener is added only once.
+ connector.addBean(alpnListener);
ALPNClientConnection connection = new ALPNClientConnection(endPoint, executor, getClientConnectionFactory(),
- (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY), context, protocols);
+ sslEngine, context, protocols);
return customize(connection, context);
}
+
+ private class ALPNListener implements SslHandshakeListener
+ {
+ @Override
+ public void handshakeSucceeded(Event event)
+ {
+ getALPNProcessor().process(event.getSSLEngine());
+ }
+
+ @Override
+ public void handshakeFailed(Event event, Throwable failure)
+ {
+ }
+ }
}
diff --git a/jetty-alpn/jetty-alpn-java-client/pom.xml b/jetty-alpn/jetty-alpn-java-client/pom.xml
new file mode 100644
index 00000000000..9bfff3a2193
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-client/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+
+ org.eclipse.jetty
+ jetty-alpn-parent
+ 9.4.1-SNAPSHOT
+
+
+ 4.0.0
+ jetty-alpn-java-client
+ Jetty :: ALPN :: JDK9 Client Implementation
+
+
+ ${project.groupId}.alpn.java.client
+
+
+
+
+
+ maven-compiler-plugin
+
+
+ 1.9
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${project.version}
+
+
+ org.eclipse.jetty.alpn
+ alpn-api
+ ${alpn.api.version}
+
+
+
+ org.eclipse.jetty.http2
+ http2-client
+ ${project.version}
+ test
+
+
+
+
+
diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java
new file mode 100644
index 00000000000..b06fc76b275
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java
@@ -0,0 +1,55 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.alpn.java.client;
+
+import java.io.UncheckedIOException;
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLParameters;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
+
+public class JDK9ClientALPNProcessor implements ALPNProcessor.Client
+{
+ @Override
+ public void configure(SSLEngine sslEngine, List protocols)
+ {
+ SSLParameters sslParameters = sslEngine.getSSLParameters();
+ sslParameters.setApplicationProtocols(protocols.toArray(new String[0]));
+ sslEngine.setSSLParameters(sslParameters);
+ }
+
+ @Override
+ public void process(SSLEngine sslEngine)
+ {
+ try
+ {
+ ALPN.ClientProvider provider = (ALPN.ClientProvider)ALPN.get(sslEngine);
+ if (provider != null)
+ provider.selected(sslEngine.getApplicationProtocol());
+ }
+ catch (SSLException x)
+ {
+ throw new UncheckedIOException(x);
+ }
+ }
+}
diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client b/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client
new file mode 100644
index 00000000000..29d8e8b29be
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-client/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Client
@@ -0,0 +1 @@
+org.eclipse.jetty.alpn.java.client.JDK9ClientALPNProcessor
diff --git a/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java
new file mode 100644
index 00000000000..d8a85871bc8
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2Client.java
@@ -0,0 +1,85 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.alpn.java.client;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http2.api.Session;
+import org.eclipse.jetty.http2.api.Stream;
+import org.eclipse.jetty.http2.client.HTTP2Client;
+import org.eclipse.jetty.http2.frames.DataFrame;
+import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.FuturePromise;
+import org.eclipse.jetty.util.Jetty;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+public class JDK9HTTP2Client
+{
+ public static void main(String[] args) throws Exception
+ {
+ HTTP2Client client = new HTTP2Client();
+ SslContextFactory sslContextFactory = new SslContextFactory(true);
+ client.addBean(sslContextFactory);
+ client.start();
+
+ String host = "localhost";
+ int port = 8443;
+
+ FuturePromise sessionPromise = new FuturePromise<>();
+ client.connect(sslContextFactory, new InetSocketAddress(host, port), new Session.Listener.Adapter(), sessionPromise);
+ Session session = sessionPromise.get(555, TimeUnit.SECONDS);
+
+ HttpFields requestFields = new HttpFields();
+ requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
+ MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://" + host + ":" + port + "/"), HttpVersion.HTTP_2, requestFields);
+ HeadersFrame headersFrame = new HeadersFrame(metaData, null, true);
+ CountDownLatch latch = new CountDownLatch(1);
+ session.newStream(headersFrame, new Promise.Adapter<>(), new Stream.Listener.Adapter()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ System.err.println(frame);
+ if (frame.isEndStream())
+ latch.countDown();
+ }
+
+ @Override
+ public void onData(Stream stream, DataFrame frame, Callback callback)
+ {
+ System.err.println(frame);
+ callback.succeeded();
+ if (frame.isEndStream())
+ latch.countDown();
+ }
+ });
+
+ latch.await(5, TimeUnit.SECONDS);
+
+ client.stop();
+ }
+}
diff --git a/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties
new file mode 100644
index 00000000000..d96a696f82e
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-client/src/test/resources/jetty-logging.properties
@@ -0,0 +1,2 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
diff --git a/jetty-alpn/jetty-alpn-java-server/pom.xml b/jetty-alpn/jetty-alpn-java-server/pom.xml
new file mode 100644
index 00000000000..a1c1be69d60
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+ org.eclipse.jetty
+ jetty-alpn-parent
+ 9.4.1-SNAPSHOT
+
+
+ 4.0.0
+ jetty-alpn-java-server
+ Jetty :: ALPN :: JDK9 Server Implementation
+
+
+ ${project.groupId}.alpn.java.server
+
+
+
+
+
+ maven-compiler-plugin
+
+
+ 1.9
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${project.version}
+
+
+ org.eclipse.jetty.alpn
+ alpn-api
+ ${alpn.api.version}
+
+
+
+ org.eclipse.jetty
+ jetty-server
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.http2
+ http2-server
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty
+ jetty-alpn-server
+ ${project.version}
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.hamcrest
+ hamcrest-library
+ test
+
+
+
+
diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java
new file mode 100644
index 00000000000..34c9424e9ae
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java
@@ -0,0 +1,73 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.alpn.java.server;
+
+import java.util.List;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+
+import org.eclipse.jetty.alpn.ALPN;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
+import org.eclipse.jetty.io.ssl.SslHandshakeListener;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandshakeListener
+{
+ private static final Logger LOG = Log.getLogger(JDK9ServerALPNProcessor.class);
+
+ @Override
+ public void configure(SSLEngine sslEngine)
+ {
+ sslEngine.setHandshakeApplicationProtocolSelector(this::process);
+ }
+
+ private String process(SSLEngine sslEngine, List protocols)
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("ALPN selecting among client{}", protocols);
+ ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(sslEngine);
+ return provider == null ? "" : provider.select(protocols);
+ }
+ catch (SSLException x)
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public void handshakeSucceeded(Event event)
+ {
+ ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(event.getSSLEngine());
+ if (provider != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("ALPN unsupported by client");
+ provider.unsupported();
+ }
+ }
+
+ @Override
+ public void handshakeFailed(Event event, Throwable failure)
+ {
+ }
+}
diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server b/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server
new file mode 100644
index 00000000000..51e198cb82c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/src/main/resources/META-INF/services/org.eclipse.jetty.io.ssl.ALPNProcessor$Server
@@ -0,0 +1 @@
+org.eclipse.jetty.alpn.java.server.JDK9ServerALPNProcessor
diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java
new file mode 100644
index 00000000000..ca08cdec6e0
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java
@@ -0,0 +1,166 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.alpn.java.server;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JDK9ALPNTest
+{
+ private Server server;
+ private ServerConnector connector;
+
+ public void startServer(Handler handler) throws Exception
+ {
+ server = new Server();
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration);
+ HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfiguration);
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(h1.getProtocol());
+ connector = new ServerConnector(server, newSslContextFactory(), alpn, h1, h2);
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ }
+
+ private SslContextFactory newSslContextFactory()
+ {
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+ sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+ sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+ sslContextFactory.setIncludeProtocols("TLSv1.2");
+ // The mandatory HTTP/2 cipher.
+ sslContextFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
+ return sslContextFactory;
+ }
+
+ @Test
+ public void testClientNotSupportingALPNServerSpeaksDefaultProtocol() throws Exception
+ {
+ startServer(new AbstractHandler.ErrorDispatchHandler()
+ {
+ @Override
+ protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ }
+ });
+
+ SslContextFactory sslContextFactory = new SslContextFactory(true);
+ sslContextFactory.start();
+ SSLContext sslContext = sslContextFactory.getSslContext();
+ try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", connector.getLocalPort()))
+ {
+ client.setUseClientMode(true);
+ client.setSoTimeout(5000);
+ client.startHandshake();
+
+ OutputStream output = client.getOutputStream();
+ output.write(("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n" +
+ "").getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ InputStream input = client.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ String line = reader.readLine();
+ Assert.assertTrue(line.contains(" 200 "));
+ while (true)
+ {
+ if (reader.readLine() == null)
+ break;
+ }
+ }
+ }
+
+ @Test
+ public void testClientSupportingALPNServerSpeaksNegotiatedProtocol() throws Exception
+ {
+ startServer(new AbstractHandler.ErrorDispatchHandler()
+ {
+ @Override
+ protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ }
+ });
+
+ SslContextFactory sslContextFactory = new SslContextFactory(true);
+ sslContextFactory.start();
+ SSLContext sslContext = sslContextFactory.getSslContext();
+ try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", connector.getLocalPort()))
+ {
+ client.setUseClientMode(true);
+ SSLParameters sslParameters = client.getSSLParameters();
+ sslParameters.setApplicationProtocols(new String[]{"unknown/1.0", "http/1.1"});
+ client.setSSLParameters(sslParameters);
+ client.setSoTimeout(5000);
+ client.startHandshake();
+
+ OutputStream output = client.getOutputStream();
+ output.write(("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n" +
+ "").getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ InputStream input = client.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ String line = reader.readLine();
+ Assert.assertTrue(line.contains(" 200 "));
+ while (true)
+ {
+ if (reader.readLine() == null)
+ break;
+ }
+ }
+
+ }
+}
diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9HTTP2Server.java b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9HTTP2Server.java
new file mode 100644
index 00000000000..524785d2c5c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9HTTP2Server.java
@@ -0,0 +1,66 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.alpn.java.server;
+
+import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http2.HTTP2Cipher;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/**
+ * Test server that verifies that the JDK 9 ALPN mechanism works.
+ */
+public class JDK9HTTP2Server
+{
+ public static void main(String... args) throws Exception
+ {
+ Server server = new Server();
+
+ HttpConfiguration httpsConfig = new HttpConfiguration();
+ httpsConfig.setSecureScheme("https");
+ httpsConfig.setSecurePort(8443);
+ httpsConfig.setSendXPoweredBy(true);
+ httpsConfig.setSendServerVersion(true);
+ httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+ sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
+ sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
+ sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
+
+ HttpConnectionFactory http = new HttpConnectionFactory(httpsConfig);
+ HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
+ ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
+ alpn.setDefaultProtocol(http.getProtocol());
+ SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
+
+ ServerConnector http2Connector = new ServerConnector(server, ssl, alpn, h2, http);
+ http2Connector.setPort(8443);
+ server.addConnector(http2Connector);
+
+ server.start();
+ }
+}
diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties
new file mode 100644
index 00000000000..c391f84e35b
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties
@@ -0,0 +1,3 @@
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.alpn.LEVEL=DEBUG
diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.jks b/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.jks
new file mode 100644
index 00000000000..d6592f95ee9
Binary files /dev/null and b/jetty-alpn/jetty-alpn-java-server/src/test/resources/keystore.jks differ
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod
new file mode 100644
index 00000000000..a591d419f89
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-8.mod
@@ -0,0 +1,28 @@
+[description]
+Provides ALPN support for JDK 8, modifying the sun.security.ssl
+classes and adding them to the JVM boot classpath.
+This modification has a tight dependency on specific recent updates of
+Java 1.7 and Java 1.8 (Java versions prior to 1.7u40 are not supported).
+This module will use an appropriate alpn-boot jar for your
+specific version of Java.
+# IMPORTANT: Versions of Java that exist after this module was created are
+# not guaranteed to work with existing alpn-boot jars, and might
+# need a new alpn-boot to be created / tested / deployed by the
+# Jetty project in order to provide support for these future
+# Java versions.
+#
+# All versions of the alpn-boot jar can be found at
+# http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/
+
+[depend]
+alpn-impl/alpn-${java.version}
+
+[files]
+lib/
+lib/alpn/
+
+[license]
+ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
+ALPN replaces/modifies OpenJDK classes in the sun.security.ssl package.
+http://github.com/jetty-project/jetty-alpn
+http://openjdk.java.net/legal/gplv2+ce.html
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-9.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-9.mod
new file mode 100644
index 00000000000..9a2708ba89a
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-9.mod
@@ -0,0 +1,6 @@
+[description]
+Provides support for ALPN based on JDK 9 APIs.
+
+[lib]
+lib/jetty-alpn-java-server-${jetty.version}.jar
+lib/alpn-api-*.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod
index 07280a8133a..e25948f4bc8 100644
--- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn.mod
@@ -1,23 +1,9 @@
[description]
-Enables the ALPN extension to TLS(SSL) by adding modified classes to
-the JVM bootpath.
-This modification has a tight dependency on specific recent updates of
-Java 1.7 and Java 1.8 (Java versions prior to 1.7u40 are not supported).
-The alpn module will use an appropriate alpn-boot jar for your
-specific version of Java.
-#
-# IMPORTANT: Versions of Java that exist after this module was created are
-# not guaranteed to work with existing alpn-boot jars, and might
-# need a new alpn-boot to be created / tested / deployed by the
-# Jetty project in order to provide support for these future
-# Java versions.
-#
-# All versions of alpn-boot can be found at
-# http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/
+Enables the ALPN (Application Layer Protocol Negotiation) TLS extension.
[depend]
ssl
-alpn-impl/alpn-${java.version}
+alpn-impl/alpn-${java.version.platform}
[lib]
lib/jetty-alpn-client-${jetty.version}.jar
@@ -26,15 +12,11 @@ lib/jetty-alpn-server-${jetty.version}.jar
[xml]
etc/jetty-alpn.xml
-[files]
-lib/
-lib/alpn/
-
[ini-template]
## Overrides the order protocols are chosen by the server.
## The default order is that specified by the order of the
## modules declared in start.ini.
-# jetty.alpn.protocols=h2-16,http/1.1
+# jetty.alpn.protocols=h2,http/1.1
## Specifies what protocol to use when negotiation fails.
# jetty.alpn.defaultProtocol=http/1.1
@@ -42,8 +24,3 @@ lib/alpn/
## ALPN debug logging on System.err
# jetty.alpn.debug=false
-[license]
-ALPN is a hosted at github under the GPL v2 with ClassPath Exception.
-ALPN replaces/modifies OpenJDK classes in the java.sun.security.ssl package.
-http://github.com/jetty-project/jetty-alpn
-http://openjdk.java.net/legal/gplv2+ce.html
diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java
index e6c15eb092e..c755ff5b459 100644
--- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java
+++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java
@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.io.EndPoint;
@@ -44,7 +45,7 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements
@Override
public void unsupported()
{
- select(Collections.emptyList());
+ select(Collections.emptyList());
}
@Override
@@ -52,8 +53,11 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements
{
SSLEngine sslEngine = getSSLEngine();
List serverProtocols = getProtocols();
- String tlsProtocol = sslEngine.getHandshakeSession().getProtocol();
- String tlsCipher = sslEngine.getHandshakeSession().getCipherSuite();
+ SSLSession sslSession = sslEngine.getHandshakeSession();
+ if (sslSession == null)
+ sslSession = sslEngine.getSession();
+ String tlsProtocol = sslSession.getProtocol();
+ String tlsCipher = sslSession.getCipherSuite();
String negotiated = null;
// RFC 7301 states that the server picks the protocol
@@ -83,12 +87,12 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements
else
{
if (LOG.isDebugEnabled())
- LOG.debug("{} could not negotiate protocol: C{} | S{}", this, clientProtocols, serverProtocols);
+ LOG.debug("{} could not negotiate protocol among client{} and server{}", this, clientProtocols, serverProtocols);
throw new IllegalStateException();
}
}
if (LOG.isDebugEnabled())
- LOG.debug("{} protocol selected {}", this, negotiated);
+ LOG.debug("{} protocol selected {} among client{} and server{}", this, negotiated, clientProtocols, serverProtocols);
setProtocol(negotiated);
ALPN.remove(sslEngine);
return negotiated;
diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java
index 3608b45727c..fca7fa0f27e 100644
--- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java
+++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java
@@ -18,22 +18,23 @@
package org.eclipse.jetty.alpn.server;
+import java.util.Iterator;
import java.util.List;
+import java.util.ServiceLoader;
import javax.net.ssl.SSLEngine;
-import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ssl.ALPNProcessor;
+import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.NegotiatingServerConnectionFactory;
import org.eclipse.jetty.util.annotation.Name;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory
+public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory implements SslHandshakeListener
{
- private static final Logger LOG = Log.getLogger(ALPNServerConnectionFactory.class);
+ private final ALPNProcessor.Server alpnProcessor;
public ALPNServerConnectionFactory(String protocols)
{
@@ -43,25 +44,34 @@ public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFact
public ALPNServerConnectionFactory(@Name("protocols") String... protocols)
{
super("alpn", protocols);
- try
- {
- ClassLoader alpnClassLoader = ALPN.class.getClassLoader();
- if (alpnClassLoader != null)
- {
- LOG.warn("ALPN must be in the boot classloader, not in: " + alpnClassLoader);
- throw new IllegalStateException("ALPN must be in the boot classloader");
- }
- }
- catch (Throwable x)
- {
- LOG.warn("ALPN not available", x);
- throw new IllegalStateException("ALPN not available", x);
- }
+ checkProtocolNegotiationAvailable();
+ Iterator processors = ServiceLoader.load(ALPNProcessor.Server.class).iterator();
+ alpnProcessor = processors.hasNext() ? processors.next() : ALPNProcessor.Server.NOOP;
+ }
+
+ public ALPNProcessor.Server getALPNProcessor()
+ {
+ return alpnProcessor;
}
@Override
protected AbstractConnection newServerConnection(Connector connector, EndPoint endPoint, SSLEngine engine, List protocols, String defaultProtocol)
{
+ getALPNProcessor().configure(engine);
return new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol);
}
+
+ @Override
+ public void handshakeSucceeded(Event event)
+ {
+ if (alpnProcessor instanceof SslHandshakeListener)
+ ((SslHandshakeListener)alpnProcessor).handshakeSucceeded(event);
+ }
+
+ @Override
+ public void handshakeFailed(Event event, Throwable failure)
+ {
+ if (alpnProcessor instanceof SslHandshakeListener)
+ ((SslHandshakeListener)alpnProcessor).handshakeFailed(event, failure);
+ }
}
diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml
index 9f8778c6a97..6fe74328604 100644
--- a/jetty-alpn/pom.xml
+++ b/jetty-alpn/pom.xml
@@ -1,4 +1,6 @@
-
+org.eclipse.jettyjetty-project
@@ -12,4 +14,16 @@
jetty-alpn-serverjetty-alpn-client
+
+
+ jdk9
+
+ [1.9,)
+
+
+ jetty-alpn-java-client
+ jetty-alpn-java-server
+
+
+
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
index 79889389e54..a6c48ee5e05 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HostnameVerificationTest.java
@@ -38,22 +38,28 @@ import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
/**
* This test class runs tests to make sure that hostname verification (http://www.ietf.org/rfc/rfc2818.txt
* section 3.1) is configurable in SslContextFactory and works as expected.
*/
+@Ignore
public class HostnameVerificationTest
{
private SslContextFactory clientSslContextFactory = new SslContextFactory();
- private Server server = new Server();
+ private Server server;
private HttpClient client;
private NetworkConnector connector;
@Before
public void setUp() throws Exception
{
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
+
SslContextFactory serverSslContextFactory = new SslContextFactory();
serverSslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
serverSslContextFactory.setKeyStorePassword("storepwd");
@@ -74,10 +80,10 @@ public class HostnameVerificationTest
clientSslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
clientSslContextFactory.setKeyStorePassword("storepwd");
- QueuedThreadPool executor = new QueuedThreadPool();
- executor.setName(executor.getName() + "-client");
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ clientThreads.setName("client");
client = new HttpClient(clientSslContextFactory);
- client.setExecutor(executor);
+ client.setExecutor(clientThreads);
client.start();
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
index 564a467abf6..ecfd2164fb4 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
@@ -259,65 +259,63 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Test
public void testAbortOnCommitWithContent() throws Exception
{
- try (StacklessLogging suppressor = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class))
+ final AtomicReference failure = new AtomicReference<>();
+ start(new AbstractHandler()
{
- final AtomicReference failure = new AtomicReference<>();
- start(new AbstractHandler()
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ try
+ {
+ baseRequest.setHandled(true);
+ if (request.getDispatcherType() != DispatcherType.ERROR)
+ IO.copy(request.getInputStream(), response.getOutputStream());
+ }
+ catch (IOException x)
+ {
+ failure.set(x);
+ throw x;
+ }
+ }
+ });
+
+ final Throwable cause = new Exception();
+ final AtomicBoolean aborted = new AtomicBoolean();
+ final CountDownLatch latch = new CountDownLatch(1);
+ try
+ {
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onRequestCommit(request ->
+ {
+ aborted.set(request.abort(cause));
+ latch.countDown();
+ })
+ .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
{
@Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public long getLength()
{
- try
- {
- baseRequest.setHandled(true);
- if (request.getDispatcherType() != DispatcherType.ERROR)
- IO.copy(request.getInputStream(), response.getOutputStream());
- }
- catch (IOException x)
- {
- failure.set(x);
- throw x;
- }
+ return -1;
}
- });
-
- final Throwable cause = new Exception();
- final AtomicBoolean aborted = new AtomicBoolean();
- final CountDownLatch latch = new CountDownLatch(1);
- try
- {
- client.newRequest("localhost", connector.getLocalPort())
- .scheme(scheme)
- .onRequestCommit(request ->
- {
- aborted.set(request.abort(cause));
- latch.countDown();
- })
- .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
- {
- @Override
- public long getLength()
- {
- return -1;
- }
- })
- .timeout(5, TimeUnit.SECONDS)
- .send();
- Assert.fail();
- }
- catch (ExecutionException x)
- {
- Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
- if (aborted.get())
- Assert.assertSame(cause, x.getCause());
- }
-
- HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- Assert.assertEquals(0, connectionPool.getConnectionCount());
- Assert.assertEquals(0, connectionPool.getActiveConnections().size());
- Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+ })
+ .timeout(5, TimeUnit.SECONDS)
+ .send();
+ Assert.fail();
}
+ catch (ExecutionException x)
+ {
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ if (aborted.get())
+ Assert.assertSame(cause, x.getCause());
+ }
+
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Assert.assertEquals(0, connectionPool.getConnectionCount());
+ Assert.assertEquals(0, connectionPool.getActiveConnections().size());
+ Assert.assertEquals(0, connectionPool.getIdleConnections().size());
+
}
@Test
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index 2596e1f3209..cd372cd97e9 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -1,18 +1,21 @@
-
- 4.0.0
+org.eclipse.jettyjetty-project9.4.1-SNAPSHOT
+ 4.0.0jetty-distributionJetty :: Distribution Assemblies
- http://www.eclipse.org/jettypom
+
${basedir}/target/distribution${basedir}/target/home
+
+
org.ops4j.pax.exampax-exam-container-forked${exam.version}test
-
org.ops4j.pax.exampax-exam-junit4
@@ -126,7 +124,6 @@
-
org.eclipse.jetty.osgijetty-httpservice
@@ -139,14 +136,12 @@
jetty-osgi-servlet-api3.1.0.M3
-
org.apache.geronimo.specsgeronimo-jta_1.1_spec1.1.1test
-
org.apache.geronimo.specsgeronimo-atinject_1.0_spec
@@ -159,7 +154,6 @@
1.0.1test
-
org.glassfish.webjavax.servlet.jsp.jstl
@@ -183,7 +177,6 @@
-
org.eclipse.jetty.orbitjavax.servlet.jsp.jstl
@@ -330,7 +323,6 @@
jetty-schemasruntime
-
org.eclipse.jettyjetty-plus
@@ -346,7 +338,6 @@
webbundletest
-
org.eclipse.jetty.teststest-spec-webapp
@@ -354,14 +345,12 @@
wartest
-
org.eclipse.jetty.teststest-container-initializer${project.version}test
-
org.eclipse.jetty.osgitest-jetty-osgi-fragment
@@ -379,7 +368,6 @@
test-mock-resources${project.version}
-
org.eclipse.jetty.osgitest-jetty-osgi-context
@@ -404,14 +392,13 @@
test
-
maven-surefire-plugin
-
+
-Dmortbay-alpn-boot=${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.version}/alpn-boot-${alpn.version}.jar
@@ -458,5 +445,4 @@
-
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java
index c539b94cf14..b6d0dd8af2b 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java
@@ -44,11 +44,11 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
@@ -93,7 +93,7 @@ public class ProxyServletFailureTest
private void prepareProxy() throws Exception
{
- prepareProxy(new HashMap());
+ prepareProxy(new HashMap<>());
}
private void prepareProxy(Map initParams) throws Exception
@@ -257,7 +257,7 @@ public class ProxyServletFailureTest
public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
- int expected = -1;
+ int expected;
if (proxyServlet instanceof AsyncProxyServlet)
{
// TODO should this be a 502 also???
@@ -308,7 +308,7 @@ public class ProxyServletFailureTest
long idleTimeout = 1000;
serverConnector.setIdleTimeout(idleTimeout);
- try(StacklessLogging stackless = new StacklessLogging(ServletHandler.class))
+ try(StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.content(new BytesContentProvider(content))
@@ -397,7 +397,7 @@ public class ProxyServletFailureTest
@Test
public void testServerException() throws Exception
{
- try (StacklessLogging stackless = new StacklessLogging(ServletHandler.class))
+ try (StacklessLogging stackless = new StacklessLogging(HttpChannel.class))
{
prepareProxy();
prepareServer(new HttpServlet()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java
index 94ced62825c..a312db24d12 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java
@@ -68,9 +68,9 @@ public class CachedContentFactory implements HttpContent.ContentFactory
private final CompressedContentFormat[] _precompressedFormats;
private final boolean _useFileMappedBuffer;
- private int _maxCachedFileSize =128*1024*1024;
- private int _maxCachedFiles=2048;
- private int _maxCacheSize =256*1024*1024;
+ private int _maxCachedFileSize = 128*1024*1024;
+ private int _maxCachedFiles= 2048;
+ private int _maxCacheSize = 256*1024*1024;
/* ------------------------------------------------------------ */
/** Constructor.
@@ -375,22 +375,34 @@ public class CachedContentFactory implements HttpContent.ContentFactory
}
/* ------------------------------------------------------------ */
- protected ByteBuffer getDirectBuffer(Resource resource)
+ protected ByteBuffer getMappedBuffer(Resource resource)
{
// Only use file mapped buffers for cached resources, otherwise too much virtual memory commitment for
// a non shared resource. Also ignore max buffer size
try
{
if (_useFileMappedBuffer && resource.getFile()!=null && resource.length()_maxCacheSize)
+ buffer=direct;
+ if (mapped==null && _cachedSize.addAndGet(BufferUtil.length(buffer))>_maxCacheSize)
shrinkCache();
}
else
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 48f02a0d8bb..0250200e895 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.server;
+import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
+import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -39,6 +42,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.HttpChannelState.Action;
import org.eclipse.jetty.server.handler.ContextHandler;
@@ -50,9 +54,6 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
-import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
-import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
-
/**
* HttpChannel represents a single endpoint for HTTP semantic processing.
@@ -484,7 +485,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (failure instanceof RuntimeIOException)
failure = failure.getCause();
- if (failure instanceof QuietServletException || !getServer().isRunning())
+ if (failure instanceof QuietException || !getServer().isRunning())
{
if (LOG.isDebugEnabled())
LOG.debug(_request.getRequestURI(), failure);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
index 33e63508176..76de6958b2a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
@@ -219,11 +219,15 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
@Override
public void earlyEOF()
{
+ _httpConnection.getGenerator().setPersistent(false);
// If we have no request yet, just close
if (_metadata.getMethod() == null)
_httpConnection.close();
- else if (onEarlyEOF())
+ else if (onEarlyEOF() || _delayedForContent)
+ {
+ _delayedForContent = false;
handle();
+ }
}
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
index 4ccc243de0f..a816607d7ef 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NegotiatingServerConnectionFactory.java
@@ -19,7 +19,6 @@
package org.eclipse.jetty.server;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
@@ -34,23 +33,25 @@ import org.eclipse.jetty.io.ssl.SslConnection;
public abstract class NegotiatingServerConnectionFactory extends AbstractConnectionFactory
{
public static void checkProtocolNegotiationAvailable()
- {
- if (!isAvailableInBootClassPath("org.eclipse.jetty.alpn.ALPN"))
- throw new IllegalStateException("No ALPN classes available");
- }
-
- private static boolean isAvailableInBootClassPath(String className)
{
try
{
- Class> klass = ClassLoader.getSystemClassLoader().loadClass(className);
- if (klass.getClassLoader() != null)
- throw new IllegalStateException(className + " must be on JVM boot classpath");
- return true;
+ String javaVersion = System.getProperty("java.version");
+ String alpnClassName = "org.eclipse.jetty.alpn.ALPN";
+ if (javaVersion.startsWith("1."))
+ {
+ Class> klass = ClassLoader.getSystemClassLoader().loadClass(alpnClassName);
+ if (klass.getClassLoader() != null)
+ throw new IllegalStateException(alpnClassName + " must be on JVM boot classpath");
+ }
+ else
+ {
+ NegotiatingServerConnectionFactory.class.getClassLoader().loadClass(alpnClassName);
+ }
}
catch (ClassNotFoundException x)
{
- return false;
+ throw new IllegalStateException("No ALPN classes available");
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java b/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java
index fe64bf675f8..7449f865e4b 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/QuietServletException.java
@@ -20,6 +20,8 @@ package org.eclipse.jetty.server;
import javax.servlet.ServletException;
+import org.eclipse.jetty.io.QuietException;
+
/* ------------------------------------------------------------ */
/** A ServletException that is logged less verbosely than
@@ -29,7 +31,7 @@ import javax.servlet.ServletException;
* than a stack trace.
*
*/
-public class QuietServletException extends ServletException
+public class QuietServletException extends ServletException implements QuietException
{
public QuietServletException()
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
index 57432ff6ec8..674c911fac8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java
@@ -399,7 +399,20 @@ public class ResourceService
{
if (LOG.isDebugEnabled())
LOG.debug("welcome={}",welcome);
- if (_redirectWelcome)
+
+ RequestDispatcher dispatcher=_redirectWelcome?null:request.getRequestDispatcher(welcome);
+ if (dispatcher!=null)
+ {
+ // Forward to the index
+ if (included)
+ dispatcher.include(request,response);
+ else
+ {
+ request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+ dispatcher.forward(request,response);
+ }
+ }
+ else
{
// Redirect to the index
response.setContentLength(0);
@@ -409,21 +422,6 @@ public class ResourceService
else
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),welcome)));
}
- else
- {
- // Forward to the index
- RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
- if (dispatcher!=null)
- {
- if (included)
- dispatcher.include(request,response);
- else
- {
- request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
- dispatcher.forward(request,response);
- }
- }
- }
return;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
index 6f437c476e8..74b5ecb3ab4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
@@ -797,7 +797,12 @@ public class Response implements HttpServletResponse
public String getCharacterEncoding()
{
if (_characterEncoding == null)
+ {
+ String encoding = MimeTypes.getCharsetAssumedFromContentType(_contentType);
+ if (encoding!=null)
+ return encoding;
_characterEncoding = StringUtil.__ISO_8859_1;
+ }
return _characterEncoding;
}
@@ -837,10 +842,14 @@ public class Response implements HttpServletResponse
encoding=_mimeType.getCharsetString();
else
{
- encoding = MimeTypes.inferCharsetFromContentType(_contentType);
+ encoding = MimeTypes.getCharsetAssumedFromContentType(_contentType);
if (encoding == null)
- encoding = StringUtil.__ISO_8859_1;
- setCharacterEncoding(encoding,EncodingFrom.INFERRED);
+ {
+ encoding = MimeTypes.getCharsetInferredFromContentType(_contentType);
+ if (encoding == null)
+ encoding = StringUtil.__ISO_8859_1;
+ setCharacterEncoding(encoding,EncodingFrom.INFERRED);
+ }
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
index 07e63fac350..d1feb67645f 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java
@@ -150,7 +150,7 @@ public class BufferedResponseHandler extends HandlerWrapper
}
// If the mime type is known from the path, then apply mime type filtering
- String mimeType = context==null?null:context.getMimeType(path);
+ String mimeType = context==null?MimeTypes.getDefaultMimeByExtension(path):context.getMimeType(path);
if (mimeType!=null)
{
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
index ead77e77cee..e4524f157d1 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java
@@ -394,6 +394,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,W
_resourceService.setCacheControl(new PreEncodedHttpField(HttpHeader.CACHE_CONTROL,cacheControl));
}
+ /* ------------------------------------------------------------ */
/**
* @param dirAllowed
* If true, directory listings are returned if no welcome file is found. Else 403 Forbidden.
@@ -486,6 +487,7 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,W
{
}
+ /* ------------------------------------------------------------ */
/**
* @param pathInfoOnly
* true, only the path info will be applied to the resourceBase
@@ -495,9 +497,12 @@ public class ResourceHandler extends HandlerWrapper implements ResourceFactory,W
_resourceService.setPathInfoOnly(pathInfoOnly);
}
+ /* ------------------------------------------------------------ */
/**
* @param redirectWelcome
* If true, welcome files are redirected rather than forwarded to.
+ * redirection is always used if the ResourceHandler is not scoped by
+ * a ContextHandler
*/
public void setRedirectWelcome(boolean redirectWelcome)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
index 2ec6e6e2a53..f4b9558e0fc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java
@@ -20,9 +20,12 @@ package org.eclipse.jetty.server.handler.gzip;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.EnumSet;
import java.util.Set;
import java.util.zip.Deflater;
+import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -66,6 +69,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
private boolean _checkGzExists = true;
private boolean _syncFlush = false;
private int _inflateBufferSize = -1;
+ private EnumSet _dispatchers = EnumSet.of(DispatcherType.REQUEST);
// non-static, as other GzipHandler instances may have different configurations
private final ThreadLocal _deflater = new ThreadLocal<>();
@@ -130,6 +134,25 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.exclude(m);
}
+
+ /* ------------------------------------------------------------ */
+ public EnumSet getDispatcherTypes()
+ {
+ return _dispatchers;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherTypes(EnumSet dispatchers)
+ {
+ _dispatchers = dispatchers;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setDispatcherTypes(DispatcherType... dispatchers)
+ {
+ _dispatchers = EnumSet.copyOf(Arrays.asList(dispatchers));
+ }
+
/* ------------------------------------------------------------ */
/**
* Set the mime types.
@@ -395,6 +418,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _minGzipSize;
}
+ /* ------------------------------------------------------------ */
protected HttpField getVaryField()
{
return _vary;
@@ -429,6 +453,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
LOG.debug("{} handle {} in {}",this,baseRequest,context);
+ if (!_dispatchers.contains(baseRequest.getDispatcherType()))
+ {
+ LOG.debug("{} excluded by dispatcherType {}",this,baseRequest.getDispatcherType());
+ _handler.handle(target,baseRequest, request, response);
+ return;
+ }
+
// Handle request inflation
if (_inflateBufferSize>0)
{
@@ -442,8 +473,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
}
- HttpOutput out = baseRequest.getResponse().getHttpOutput();
// Are we already being gzipped?
+ HttpOutput out = baseRequest.getResponse().getHttpOutput();
HttpOutput.Interceptor interceptor = out.getInterceptor();
while (interceptor!=null)
{
@@ -474,7 +505,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
// Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
- String mimeType = context==null?null:context.getMimeType(path);
+ String mimeType = context==null?MimeTypes.getDefaultMimeByExtension(path):context.getMimeType(path);
if (mimeType!=null)
{
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
index a38a0806aeb..4f2857b773c 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java
@@ -298,6 +298,7 @@ public class AsyncRequestReadTest
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
assertThat(in.readLine(),containsString("HTTP/1.1 200 OK"));
assertThat(in.readLine(),containsString("Content-Length:"));
+ assertThat(in.readLine(),containsString("Connection: close"));
assertThat(in.readLine(),containsString("Server:"));
in.readLine();
assertThat(in.readLine(),containsString("XXXXXXX"));
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
index ed61d574518..4e9c00703d6 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/DumpHandler.java
@@ -216,7 +216,10 @@ public class DumpHandler extends AbstractHandler.ErrorDispatchHandler
}
catch(IOException e)
{
- e.printStackTrace();
+ if (LOG.isDebugEnabled())
+ LOG.warn(e);
+ else
+ LOG.warn(e.toString());
writer.write(e.toString());
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
index b434a210414..bcd9974899a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
@@ -582,16 +582,18 @@ public class HttpConnectionTest
checkContains(response,offset,"12345");
offset=0;
+ Log.getLogger(DumpHandler.class).info("Expecting java.io.UnsupportedEncodingException");
response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
- "Host: localhost\r\n"+
- "Transfer-Encoding: chunked\r\n"+
- "Content-Type: text/plain; charset=unknown\r\n"+
- "Connection: close\r\n"+
- "\r\n"+
- "5;\r\n"+
- "12345\r\n"+
- "0;\r\n" +
- "\r\n");
+ "Host: localhost\r\n"+
+ "Transfer-Encoding: chunked\r\n"+
+ "Content-Type: text/plain; charset=unknown\r\n"+
+ "Connection: close\r\n"+
+ "\r\n"+
+ "5;\r\n"+
+ "12345\r\n"+
+ "0;\r\n" +
+ "\r\n");
+
offset = checkContains(response,offset,"HTTP/1.1 200");
offset = checkContains(response,offset,"encoding=unknown");
offset = checkContains(response,offset,"/R1");
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
index d36e9469af6..57bbba69d1d 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
@@ -49,6 +49,7 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -695,6 +696,54 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
}
+ @Test
+ public void testBlockingReadBadChunk() throws Exception
+ {
+ configureServer(new ReadHandler());
+
+ try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
+ {
+ client.setSoTimeout(600000);
+ OutputStream os = client.getOutputStream();
+ InputStream is = client.getInputStream();
+
+ os.write((
+ "GET /data HTTP/1.1\r\n" +
+ "host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
+ "content-type: unknown\r\n" +
+ "transfer-encoding: chunked\r\n" +
+ "\r\n"
+ ).getBytes());
+ os.flush();
+ Thread.sleep(50);
+ os.write((
+ "a\r\n" +
+ "123456890\r\n"
+ ).getBytes());
+ os.flush();
+
+ Thread.sleep(50);
+ os.write((
+ "4\r\n" +
+ "abcd\r\n"
+ ).getBytes());
+ os.flush();
+
+ Thread.sleep(50);
+ os.write((
+ "X\r\n" +
+ "abcd\r\n"
+ ).getBytes());
+ os.flush();
+
+ HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(is));
+
+ assertThat(response.getStatus(),is(200));
+ assertThat(response.getContent(),containsString("EofException"));
+ assertThat(response.getContent(),containsString("Early EOF"));
+ }
+ }
+
@Test
public void testBlockingWhileWritingResponseContent() throws Exception
{
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
index 2f8ae4e808d..b11960f7cd3 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java
@@ -186,7 +186,28 @@ public class HttpServerTestFixture
response.getOutputStream().print("Hello world\r\n");
}
}
+
+ protected static class ReadHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setStatus(200);
+ try
+ {
+ InputStream in = request.getInputStream();
+ String input= IO.toString(in);
+ response.getWriter().printf("read %d%n",input.length());
+ }
+ catch(Exception e)
+ {
+ response.getWriter().printf("caught %s%n",e);
+ }
+ }
+ }
+
protected static class DataHandler extends AbstractHandler
{
@Override
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
index 7fa3d1ee909..0dc58d4aacf 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
@@ -278,6 +278,45 @@ public class ResponseTest
response.getWriter();
assertEquals("application/json",response.getContentType());
}
+
+ @Test
+ public void testInferredCharset() throws Exception
+ {
+ // Inferred from encoding.properties
+ Response response = getResponse();
+
+ assertEquals(null, response.getContentType());
+
+ response.setHeader("Content-Type", "application/xhtml+xml");
+ assertEquals("application/xhtml+xml", response.getContentType());
+ response.getWriter();
+ assertEquals("application/xhtml+xml;charset=utf-8", response.getContentType());
+ assertEquals("utf-8", response.getCharacterEncoding());
+ }
+
+ @Test
+ public void testAssumedCharset() throws Exception
+ {
+ Response response = getResponse();
+
+ // Assumed from known types
+ assertEquals(null, response.getContentType());
+ response.setHeader("Content-Type", "text/json");
+ assertEquals("text/json", response.getContentType());
+ response.getWriter();
+ assertEquals("text/json", response.getContentType());
+ assertEquals("utf-8", response.getCharacterEncoding());
+
+ response.recycle();
+
+ // Assumed from encoding.properties
+ assertEquals(null, response.getContentType());
+ response.setHeader("Content-Type", "application/vnd.api+json");
+ assertEquals("application/vnd.api+json", response.getContentType());
+ response.getWriter();
+ assertEquals("application/vnd.api+json", response.getContentType());
+ assertEquals("utf-8", response.getCharacterEncoding());
+ }
@Test
public void testStrangeContentType() throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
index 02dc4f1c286..aeeb931ce00 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java
@@ -18,6 +18,14 @@
package org.eclipse.jetty.server.handler;
+import static org.eclipse.jetty.http.HttpHeader.CONTENT_LENGTH;
+import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE;
+import static org.eclipse.jetty.http.HttpHeader.LAST_MODIFIED;
+import static org.eclipse.jetty.http.HttpHeader.LOCATION;
+import static org.eclipse.jetty.http.HttpHeader.SERVER;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
@@ -28,11 +36,11 @@ import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -40,7 +48,6 @@ import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.SimpleRequest;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
@@ -123,6 +130,7 @@ public class ResourceHandlerTest
_resourceHandler = new ResourceHandler();
_resourceHandler.setResourceBase(MavenTestingUtils.getTargetFile("test-classes/simple").getAbsolutePath());
+ _resourceHandler.setWelcomeFiles(new String[]{"welcome.txt"});
_contextHandler = new ContextHandler("/resource");
_contextHandler.setHandler(_resourceHandler);
@@ -143,59 +151,75 @@ public class ResourceHandlerTest
}
@Test
- public void testJettyDirCss() throws Exception
+ public void testJettyDirRedirect() throws Exception
{
- SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
- Assert.assertNotNull(sr.getString("/resource/jetty-dir.css"));
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(302));
+ assertThat(response.get(LOCATION),containsString("/resource/"));
}
@Test
- public void testSimple() throws Exception
+ public void testJettyDirListing() throws Exception
{
- SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
- Assert.assertEquals("simple text",sr.getString("/resource/simple.txt"));
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/ HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.getContent(),containsString("jetty-dir.css"));
+ assertThat(response.getContent(),containsString("
Directory: /resource/"));
+ assertThat(response.getContent(),containsString("big.txt"));
+ assertThat(response.getContent(),containsString("bigger.txt"));
+ assertThat(response.getContent(),containsString("directory"));
+ assertThat(response.getContent(),containsString("simple.txt"));
}
@Test
public void testHeaders() throws Exception
{
- String response = _local.getResponses("GET /resource/simple.txt HTTP/1.0\r\n\r\n");
- assertThat(response,startsWith("HTTP/1.1 200 OK"));
- assertThat(response,Matchers.containsString("Content-Type: text/plain"));
- assertThat(response,Matchers.containsString("Last-Modified: "));
- assertThat(response,Matchers.containsString("Content-Length: 11"));
- assertThat(response,Matchers.containsString("Server: Jetty"));
- assertThat(response,Matchers.containsString("simple text"));
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/simple.txt HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.get(CONTENT_TYPE),equalTo("text/plain"));
+ assertThat(response.get(LAST_MODIFIED),Matchers.notNullValue());
+ assertThat(response.get(CONTENT_LENGTH),equalTo("11"));
+ assertThat(response.get(SERVER),containsString("Jetty"));
+ assertThat(response.getContent(),containsString("simple text"));
}
@Test
public void testBigFile() throws Exception
{
_config.setOutputBufferSize(2048);
- SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
- String response = sr.getString("/resource/big.txt");
- Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN));
+
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/big.txt HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.getContent(),startsWith(" 1\tThis is a big file"));
+ assertThat(response.getContent(),endsWith(" 400\tThis is a big file" + LN));
}
@Test
public void testBigFileBigBuffer() throws Exception
{
_config.setOutputBufferSize(16 * 1024);
- SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
- String response = sr.getString("/resource/big.txt");
- Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN));
+
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/big.txt HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.getContent(),startsWith(" 1\tThis is a big file"));
+ assertThat(response.getContent(),endsWith(" 400\tThis is a big file" + LN));
}
@Test
public void testBigFileLittleBuffer() throws Exception
{
_config.setOutputBufferSize(8);
- SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort()));
- String response = sr.getString("/resource/big.txt");
- Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file"));
- Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN));
+
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/big.txt HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.getContent(),startsWith(" 1\tThis is a big file"));
+ assertThat(response.getContent(),endsWith(" 400\tThis is a big file" + LN));
}
@Test
@@ -212,6 +236,32 @@ public class ResourceHandlerTest
}
}
+ @Test
+ public void testWelcome() throws Exception
+ {
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/directory/ HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(200));
+ assertThat(response.getContent(),containsString("Hello"));
+ }
+
+ @Test
+ public void testWelcomeRedirect() throws Exception
+ {
+ try
+ {
+ _resourceHandler.setRedirectWelcome(true);
+ HttpTester.Response response = HttpTester.parseResponse(
+ _local.getResponse("GET /resource/directory/ HTTP/1.0\r\n\r\n"));
+ assertThat(response.getStatus(),equalTo(302));
+ assertThat(response.get(LOCATION),containsString("/resource/welcome.txt"));
+ }
+ finally
+ {
+ _resourceHandler.setRedirectWelcome(false);
+ }
+ }
+
@Test
@Slow
public void testSlowBiggest() throws Exception
@@ -235,12 +285,10 @@ public class ResourceHandlerTest
try (Socket socket = new Socket("localhost",_connector.getLocalPort());OutputStream out=socket.getOutputStream();InputStream in=socket.getInputStream())
{
-
socket.getOutputStream().write("GET /resource/biggest.txt HTTP/1.0\n\n".getBytes());
byte[] array = new byte[102400];
ByteBuffer buffer=null;
- int i=0;
while(true)
{
Thread.sleep(100);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
index c0431dc1f23..451e1986d8e 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
@@ -258,8 +258,6 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
// Read the response.
String response = readResponse(client);
-
- System.err.println(response);
assertThat(response, containsString("HTTP/1.1 200 OK"));
assertThat(response, containsString("Hello world"));
diff --git a/jetty-server/src/test/resources/simple/directory/content.txt b/jetty-server/src/test/resources/simple/directory/content.txt
new file mode 100644
index 00000000000..6b584e8ece5
--- /dev/null
+++ b/jetty-server/src/test/resources/simple/directory/content.txt
@@ -0,0 +1 @@
+content
\ No newline at end of file
diff --git a/jetty-server/src/test/resources/simple/directory/welcome.txt b/jetty-server/src/test/resources/simple/directory/welcome.txt
new file mode 100644
index 00000000000..5ab2f8a4323
--- /dev/null
+++ b/jetty-server/src/test/resources/simple/directory/welcome.txt
@@ -0,0 +1 @@
+Hello
\ No newline at end of file
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
index 5763c506f37..42709eaa8fb 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -51,6 +51,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.log.Log;
@@ -315,19 +316,19 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
{
// Look for a precompiled JSP Servlet
String precompiled=getClassNameForJsp(_forcedPath);
- if (LOG.isDebugEnabled())
- LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
- ServletHolder jsp=getServletHandler().getServlet(precompiled);
- if (jsp!=null && jsp.getClassName() != null)
+ if (!StringUtil.isBlank(precompiled))
{
if (LOG.isDebugEnabled())
- LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
- // set the className for this servlet to the precompiled one
- setClassName(jsp.getClassName());
- }
- else
- {
- if (getClassName() == null)
+ LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath);
+ ServletHolder jsp = getServletHandler().getServlet(precompiled);
+ if (jsp!=null && jsp.getClassName() != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName());
+ // set the className for this servlet to the precompiled one
+ setClassName(jsp.getClassName());
+ }
+ else
{
// Look for normal JSP servlet
jsp=getServletHandler().getServlet("jsp");
@@ -905,13 +906,24 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
}
/* ------------------------------------------------------------ */
- private String getNameOfJspClass (String jsp)
+ /**
+ * @param jsp the jsp-file
+ * @return the simple classname of the jsp
+ */
+ protected String getNameOfJspClass (String jsp)
{
- if (jsp == null)
- return "";
+ if (StringUtil.isBlank(jsp))
+ return ""; //empty
- int i = jsp.lastIndexOf('/') + 1;
- jsp = jsp.substring(i);
+ jsp = jsp.trim();
+ if ("/".equals(jsp))
+ return ""; //only slash
+
+ int i = jsp.lastIndexOf('/');
+ if (i == jsp.length()-1)
+ return ""; //ends with slash
+
+ jsp = jsp.substring(i+1);
try
{
Class> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
@@ -930,7 +942,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
/* ------------------------------------------------------------ */
- private String getPackageOfJspClass (String jsp)
+ protected String getPackageOfJspClass (String jsp)
{
if (jsp == null)
return "";
@@ -942,11 +954,13 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
{
Class> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class);
- return (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+ String p = (String)makeJavaPackage.invoke(null, jsp.substring(0,i));
+ return p;
}
catch (Exception e)
{
- String tmp = jsp.substring(1).replace('/','.');
+ String tmp = jsp.substring(1,i).replace('/','.').trim();
+ tmp = (".".equals(tmp)? "": tmp);
LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead");
if (LOG.isDebugEnabled())
LOG.warn(e);
@@ -956,7 +970,10 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
/* ------------------------------------------------------------ */
- private String getJspPackagePrefix ()
+ /**
+ * @return the package for all jsps
+ */
+ protected String getJspPackagePrefix ()
{
String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
if (jspPackageName == null)
@@ -967,12 +984,40 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
/* ------------------------------------------------------------ */
- private String getClassNameForJsp (String jsp)
+ /**
+ * @param jsp the jsp-file from web.xml
+ * @return the fully qualified classname
+ */
+ protected String getClassNameForJsp (String jsp)
{
if (jsp == null)
return null;
- return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
+ String name = getNameOfJspClass(jsp);
+ if (StringUtil.isBlank(name))
+ return null;
+
+ StringBuffer fullName = new StringBuffer();
+ appendPath(fullName, getJspPackagePrefix());
+ appendPath(fullName, getPackageOfJspClass(jsp));
+ appendPath(fullName, name);
+ return fullName.toString();
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Concatenate an element on to fully qualified classname.
+ *
+ * @param path the path under construction
+ * @param element the element of the name to add
+ */
+ protected void appendPath (StringBuffer path, String element)
+ {
+ if (StringUtil.isBlank(element))
+ return;
+ if (path.length() > 0)
+ path.append(".");
+ path.append(element);
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/PostServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/PostServletTest.java
index e6dd0cc07be..055bfb14148 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/PostServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/PostServletTest.java
@@ -162,11 +162,8 @@ public class PostServletTest
req.append("\r\n");
req.append("\r\n");
- try (StacklessLogging scope = new StacklessLogging(ServletHandler.class))
- {
- String resp = connector.getResponses(req.toString());
- assertThat(resp,is("")); // Aborted before response committed
- }
+ String resp = connector.getResponse(req.toString());
+ assertThat(resp,startsWith("HTTP/1.1 200 OK")); // exception eaten by handler
assertTrue(complete.await(5,TimeUnit.SECONDS));
assertThat(ex0.get(),not(nullValue()));
assertThat(ex1.get(),not(nullValue()));
@@ -181,31 +178,26 @@ public class PostServletTest
req.append("Transfer-Encoding: chunked\r\n");
req.append("\r\n");
- try (StacklessLogging scope = new StacklessLogging(ServletHandler.class))
- {
- LocalConnector.LocalEndPoint endp=connector.executeRequest(req.toString());
- Thread.sleep(1000);
- assertFalse(posted.get());
+ LocalConnector.LocalEndPoint endp=connector.executeRequest(req.toString());
+ Thread.sleep(1000);
+ assertFalse(posted.get());
- req.setLength(0);
- // intentionally bad (not a valid chunked char here)
- for (int i=1024;i-->0;)
- req.append("xxxxxxxxxxxx");
- req.append("\r\n");
- req.append("\r\n");
+ req.setLength(0);
+ // intentionally bad (not a valid chunked char here)
+ for (int i=1024;i-->0;)
+ req.append("xxxxxxxxxxxx");
+ req.append("\r\n");
+ req.append("\r\n");
- endp.addInput(req.toString());
+ endp.addInput(req.toString());
- endp.waitUntilClosedOrIdleFor(1,TimeUnit.SECONDS);
- String resp = endp.takeOutputString();
+ endp.waitUntilClosedOrIdleFor(1,TimeUnit.SECONDS);
+ String resp = endp.takeOutputString();
- assertThat("resp", resp, containsString("HTTP/1.1 400 "));
-
- }
-
- // null because it was never dispatched!
- assertThat(ex0.get(),nullValue());
- assertThat(ex1.get(),nullValue());
+ assertThat(resp,startsWith("HTTP/1.1 200 OK")); // exception eaten by handler
+ assertTrue(complete.await(5,TimeUnit.SECONDS));
+ assertThat(ex0.get(),not(nullValue()));
+ assertThat(ex1.get(),not(nullValue()));
}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
index 94fc06d3ddd..8304901226b 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DataRateLimitedServletTest.java
@@ -66,7 +66,7 @@ public class DataRateLimitedServletTest
context.setContextPath("/context");
context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"});
- File baseResourceDir = testdir.getEmptyDir();
+ File baseResourceDir = testdir.getEmptyPathDir().toFile();
// Use resolved real path for Windows and OSX
Path baseResourcePath = baseResourceDir.toPath().toRealPath();
@@ -91,7 +91,7 @@ public class DataRateLimitedServletTest
@Test
public void testStream() throws Exception
{
- File content = testdir.getFile("content.txt");
+ File content = testdir.getPathFile("content.txt").toFile();
String[] results=new String[10];
try(OutputStream out = new FileOutputStream(content);)
{
@@ -109,7 +109,7 @@ public class DataRateLimitedServletTest
}
long start=System.currentTimeMillis();
- String response = connector.getResponses("GET /context/stream/content.txt HTTP/1.0\r\n\r\n");
+ String response = connector.getResponse("GET /context/stream/content.txt HTTP/1.0\r\n\r\n");
long duration=System.currentTimeMillis()-start;
assertThat("Response",response,containsString("200 OK"));
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java b/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java
new file mode 100644
index 00000000000..784b3a2cc5b
--- /dev/null
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/JavaVersion.java
@@ -0,0 +1,153 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.start;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JavaVersion
+{
+ private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?");
+ // Regexp from JEP 223 (http://openjdk.java.net/jeps/223).
+ private static final Pattern JDK9 = Pattern.compile("(\\d+)(\\.(\\d+))?(\\.(\\d+))?((-.+)?(\\+(\\d+)?(-.+)?)?)");
+
+ public static JavaVersion parse(String version)
+ {
+ if (version.startsWith("1."))
+ return parsePreJDK9(version);
+ return parseJDK9(version);
+ }
+
+ private static JavaVersion parsePreJDK9(String version)
+ {
+ Matcher matcher = PRE_JDK9.matcher(version);
+ if (!matcher.matches())
+ throw new IllegalArgumentException("Invalid Java version " + version);
+ int major = 1;
+ int minor = Integer.parseInt(matcher.group(1));
+ String microGroup = matcher.group(3);
+ int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
+ String updateGroup = matcher.group(5);
+ int update = updateGroup == null || updateGroup.isEmpty() ? 0 : Integer.parseInt(updateGroup);
+ String suffix = matcher.group(6);
+ return new JavaVersion(version, minor, major, minor, micro, update, suffix);
+ }
+
+ private static JavaVersion parseJDK9(String version)
+ {
+ Matcher matcher = JDK9.matcher(version);
+ if (!matcher.matches())
+ throw new IllegalArgumentException("Invalid Java version " + version);
+ int major = Integer.parseInt(matcher.group(1));
+ String minorGroup = matcher.group(3);
+ int minor = minorGroup == null || minorGroup.isEmpty() ? 0 : Integer.parseInt(minorGroup);
+ String microGroup = matcher.group(5);
+ int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
+ String suffix = matcher.group(6);
+ return new JavaVersion(version, major, major, minor, micro, 0, suffix);
+ }
+
+ private final String version;
+ private final int platform;
+ private final int major;
+ private final int minor;
+ private final int micro;
+ private final int update;
+ private final String suffix;
+
+ private JavaVersion(String version, int platform, int major, int minor, int micro, int update, String suffix)
+ {
+ this.version = version;
+ this.platform = platform;
+ this.major = major;
+ this.minor = minor;
+ this.micro = micro;
+ this.update = update;
+ this.suffix = suffix;
+ }
+
+ /**
+ * @return the string from which this JavaVersion was created
+ */
+ public String getVersion()
+ {
+ return version;
+ }
+
+ /**
+ *
Returns the Java Platform version, such as {@code 8} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.
+ *
+ * @return the Java Platform version
+ */
+ public int getPlatform()
+ {
+ return platform;
+ }
+
+ /**
+ *
Returns the major number version, such as {@code 1} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.
+ *
+ * @return the major number version
+ */
+ public int getMajor()
+ {
+ return major;
+ }
+
+ /**
+ *
Returns the minor number version, such as {@code 8} for JDK 1.8.0_92 and {@code 2} for JDK 9.2.4.
+ *
+ * @return the minor number version
+ */
+ public int getMinor()
+ {
+ return minor;
+ }
+
+ /**
+ *
Returns the micro number version, such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.
+ *
+ * @return the micro number version
+ */
+ public int getMicro()
+ {
+ return micro;
+ }
+
+ /**
+ *
Returns the update number version, such as {@code 92} for JDK 1.8.0_92 and {@code 0} for JDK 9.2.4.
+ *
+ * @return the update number version
+ */
+ public int getUpdate()
+ {
+ return update;
+ }
+
+ /**
+ *
Returns the remaining string after the version numbers, such as {@code -internal} for
+ * JDK 1.8.0_92-internal and {@code -ea} for JDK 9-ea, or {@code +13} for JDK 9.2.4+13.
+ *
+ * @return the remaining string after the version numbers
+ */
+ public String getSuffix()
+ {
+ return suffix;
+ }
+}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index a098b55a39a..fdb38dc8850 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -332,10 +332,10 @@ public class Main
// ------------------------------------------------------------
// 5) Lib & XML Expansion / Resolution
+ args.expandSystemProperties();
args.expandLibs();
args.expandModules(activeModules);
-
// ------------------------------------------------------------
// 6) Resolve Extra XMLs
args.resolveExtraXmls();
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
index fee0498a4f3..dda8589af9f 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java
@@ -179,7 +179,7 @@ public class Module implements Comparable
return _path.equals(other._path);
}
- public void expandProperties(Props props)
+ public void expandDependencies(Props props)
{
Function expander = d->{return props.expand(d);};
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
index 6633f5f65ae..16692ee698e 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java
@@ -322,7 +322,7 @@ public class Modules implements Iterable
newlyEnabled.add(module.getName());
// Expand module properties
- module.expandProperties(_args.getProperties());
+ module.expandDependencies(_args.getProperties());
// Apply default configuration
if (module.hasDefaultConfig())
@@ -330,7 +330,7 @@ public class Modules implements Iterable
for(String line:module.getDefaultConfig())
_args.parse(line,module.getName()+"[ini]");
for (Module m:_modules)
- m.expandProperties(_args.getProperties());
+ m.expandDependencies(_args.getProperties());
}
}
@@ -349,7 +349,7 @@ public class Modules implements Iterable
if (dependsOn.contains("/"))
{
Path file = _baseHome.getPath("modules/" + dependsOn + ".mod");
- registerModule(file).expandProperties(_args.getProperties());
+ registerModule(file).expandDependencies(_args.getProperties());
providers = _provided.get(dependsOn);
if (providers==null || providers.isEmpty())
throw new UsageException("Module %s does not provide %s",_baseHome.toShortForm(file),dependsOn);
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java
index f770374d569..53596374f8d 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java
@@ -190,7 +190,7 @@ public final class Props implements Iterable
return expand(str,new Stack());
}
- public String expand(String str, Stack seenStack)
+ private String expand(String str, Stack seenStack)
{
if (str == null)
{
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
index a4557ba4ceb..0c4833a4947 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java
@@ -18,8 +18,6 @@
package org.eclipse.jetty.start;
-import static org.eclipse.jetty.start.UsageException.ERR_BAD_ARG;
-
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -37,6 +35,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
+import java.util.function.Function;
import org.eclipse.jetty.start.Props.Prop;
import org.eclipse.jetty.start.config.ConfigSource;
@@ -180,7 +179,6 @@ public class StartArgs
private boolean createStartd = false;
private boolean updateIni = false;
-
private boolean exec = false;
private String exec_properties;
private boolean approveAllLicenses = false;
@@ -437,6 +435,28 @@ public class StartArgs
}
}
+ /**
+ * Expand any command line added {@code --lib} lib references.
+ *
+ * @throws IOException
+ * if unable to expand the libraries
+ */
+ public void expandSystemProperties() throws IOException
+ {
+ StartLog.debug("Expanding System Properties");
+
+ for (String key : systemPropertyKeys)
+ {
+ String value = properties.getString(key);
+ if (value!=null)
+ {
+ String expanded = properties.expand(value);
+ if (!value.equals(expanded))
+ System.setProperty(key,expanded);
+ }
+ }
+ }
+
/**
* Expand any command line added --lib lib references.
*
@@ -555,15 +575,30 @@ public class StartArgs
{
cmd.addRawArg(CommandLineBuilder.findJavaBin());
- for (String x : jvmArgs)
- {
- cmd.addRawArg(x);
- }
-
cmd.addRawArg("-Djava.io.tmpdir=" + System.getProperty("java.io.tmpdir"));
cmd.addRawArg("-Djetty.home=" + baseHome.getHome());
cmd.addRawArg("-Djetty.base=" + baseHome.getBase());
+ for (String x : jvmArgs)
+ {
+ if (x.startsWith("-D"))
+ {
+ String[] assign = x.substring(2).split("=",2);
+ String key = assign[0];
+ String value = assign.length==1?"":assign[1];
+
+ Property p = processProperty(key,value,"modules",k->{return System.getProperty(k);});
+ if (p!=null)
+ {
+ cmd.addRawArg("-D"+p.key+"="+getProperties().expand(p.value));
+ }
+ }
+ else
+ {
+ cmd.addRawArg(x);
+ }
+ }
+
// System Properties
for (String propKey : systemPropertyKeys)
{
@@ -861,7 +896,7 @@ public class StartArgs
Path commands = baseHome.getPath(Props.getValue(arg));
if (!Files.exists(commands) || !Files.isReadable(commands))
- throw new UsageException(ERR_BAD_ARG,"--commands file must be readable: %s",commands);
+ throw new UsageException(UsageException.ERR_BAD_ARG,"--commands file must be readable: %s",commands);
try
{
TextFile file = new TextFile(commands);
@@ -947,7 +982,7 @@ public class StartArgs
{
exec_properties = Props.getValue(arg);
if (!exec_properties.endsWith(".properties"))
- throw new UsageException(ERR_BAD_ARG,"--exec-properties filename must have .properties suffix: %s",exec_properties);
+ throw new UsageException(UsageException.ERR_BAD_ARG,"--exec-properties filename must have .properties suffix: %s",exec_properties);
return;
}
@@ -1057,23 +1092,19 @@ public class StartArgs
if (arg.startsWith("-D"))
{
String[] assign = arg.substring(2).split("=",2);
- systemPropertyKeys.add(assign[0]);
- switch (assign.length)
- {
- case 2:
- System.setProperty(assign[0],assign[1]);
- setProperty(assign[0],assign[1],source);
- break;
- case 1:
- System.setProperty(assign[0],"");
- setProperty(assign[0],"",source);
- break;
- default:
- break;
+ String key = assign[0];
+ String value = assign.length==1?"":assign[1];
+
+ Property p = processProperty(key,value,source,k->{return System.getProperty(k);});
+ if (p!=null)
+ {
+ systemPropertyKeys.add(p.key);
+ setProperty(p.key,p.value,p.source);
+ System.setProperty(p.key,p.value);
}
return;
}
-
+
// Anything else with a "-" is considered a JVM argument
if (arg.startsWith("-"))
{
@@ -1092,36 +1123,11 @@ public class StartArgs
String key = arg.substring(0,equals);
String value = arg.substring(equals + 1);
- if (key.endsWith("+"))
+ Property p = processProperty(key,value,source,k->{return getProperties().getString(k);});
+ if (p!=null)
{
- key = key.substring(0,key.length() - 1);
- String orig = getProperties().getString(key);
- if (orig == null || orig.isEmpty())
- {
- if (value.startsWith(","))
- value = value.substring(1);
- }
- else
- {
- value = orig + value;
- source = propertySource.get(key) + "," + source;
- }
+ setProperty(p.key,p.value,p.source);
}
- if (key.endsWith("?"))
- {
- key = key.substring(0,key.length() - 1);
- if (getProperties().containsKey(key))
- return;
-
- }
- else if (propertySource.containsKey(key))
- {
- if (!propertySource.get(key).endsWith("[ini]"))
- StartLog.warn("Property %s in %s already set in %s",key,source,propertySource.get(key));
- propertySource.put(key,source);
- }
-
- setProperty(key,value,source);
return;
}
@@ -1147,9 +1153,46 @@ public class StartArgs
}
// Anything else is unrecognized
- throw new UsageException(ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source);
+ throw new UsageException(UsageException.ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source);
}
-
+
+ protected Property processProperty(String key,String value,String source, Function getter)
+ {
+ if (key.endsWith("+"))
+ {
+ key = key.substring(0,key.length() - 1);
+ String orig = getter.apply(key);
+ if (orig == null || orig.isEmpty())
+ {
+ if (value.startsWith(","))
+ value = value.substring(1);
+ }
+ else
+ {
+ value = orig + value;
+ source = propertySource.get(key) + "," + source;
+ }
+ }
+ if (key.endsWith("?"))
+ {
+ key = key.substring(0,key.length() - 1);
+ String preset = getter.apply(key);
+ if (preset!=null)
+ {
+ source = source+"?=";
+ value = preset;
+ }
+ }
+ else if (propertySource.containsKey(key))
+ {
+ if (!propertySource.get(key).endsWith("[ini]"))
+ StartLog.warn("Property %s in %s already set in %s",key,source,propertySource.get(key));
+ propertySource.put(key,source);
+ }
+
+ return new Property(key,value,source);
+ }
+
private void enableModules(String source, List moduleNames)
{
for (String moduleName : moduleNames)
@@ -1158,7 +1201,7 @@ public class StartArgs
List list = sources.get(moduleName);
if (list == null)
{
- list = new ArrayList();
+ list = new ArrayList<>();
sources.put(moduleName,list);
}
list.add(source);
@@ -1219,13 +1262,20 @@ public class StartArgs
properties.setProperty(key,value,source);
if (key.equals("java.version"))
{
- Version ver = new Version(value);
-
- properties.setProperty("java.version",ver.toShortString(),source);
- properties.setProperty("java.version.major",Integer.toString(ver.getLegacyMajor()),source);
- properties.setProperty("java.version.minor",Integer.toString(ver.getMajor()),source);
- properties.setProperty("java.version.revision",Integer.toString(ver.getRevision()),source);
- properties.setProperty("java.version.update",Integer.toString(ver.getUpdate()),source);
+ try
+ {
+ JavaVersion ver = JavaVersion.parse(value);
+ properties.setProperty("java.version",ver.getVersion(),source);
+ properties.setProperty("java.version.platform",Integer.toString(ver.getPlatform()),source);
+ properties.setProperty("java.version.major",Integer.toString(ver.getMajor()),source);
+ properties.setProperty("java.version.minor",Integer.toString(ver.getMinor()),source);
+ properties.setProperty("java.version.micro",Integer.toString(ver.getMicro()),source);
+ properties.setProperty("java.version.update",Integer.toString(ver.getUpdate()),source);
+ }
+ catch (Throwable x)
+ {
+ throw new UsageException(UsageException.ERR_BAD_ARG, x.getMessage());
+ }
}
}
@@ -1249,5 +1299,23 @@ public class StartArgs
builder.append("]");
return builder.toString();
}
-
+
+ static class Property
+ {
+ String key;
+ String value;
+ String source;
+ public Property(String key, String value, String source)
+ {
+ this.key = key;
+ this.value = value;
+ this.source = source;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s=%s(%s)",key,value,source);
+ }
+ }
}
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
index 3099b8598d2..3e8d1da435e 100644
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
@@ -172,8 +172,8 @@ Properties:
+ XML files using the element
+ Module files using the ${pname} syntax
- Propertues may be set on the command line, in a ini file or in a [ini]
- section of a module using the following syntax:
+ Properties and System Properties may be set on the command line,
+ in a ini file or in a [ini] section of a module using the following syntax:
name=value
Set a property that can be expanded in XML files with the element.
@@ -187,7 +187,10 @@ Properties:
name?=value
Set a property only if it is not already set.
- Each module may definte it's own properties. Start properties defined include:
+ If any of the previous formats is preceded by -D, then a system property is set
+ as well as a start property.
+
+ Each module may define it's own properties. Start properties defined include:
jetty.home=[directory]
Set the home directory of the jetty distribution.
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
index ebf0bf48e9b..c44b3182fec 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jetty.start.Props.Prop;
@@ -127,7 +128,7 @@ public class ConfigurationAssert
Set expectedProperties = new HashSet<>();
for (String line : textFile)
{
- if (line.startsWith("PROP|"))
+ if (line.startsWith("PROP|") || line.startsWith("SYS|"))
{
expectedProperties.add(getValue(line));
}
@@ -147,6 +148,17 @@ public class ConfigurationAssert
}
assertContainsUnordered("Properties", expectedProperties, actualProperties);
+ // Validate PROPERTIES (order is not important)
+ for (String line : textFile)
+ {
+ if (line.startsWith("SYS|"))
+ {
+ String[] expected = getValue(line).split("=",2);
+ String actual = System.getProperty(expected[0]);
+ assertThat("System property "+expected[0],actual,Matchers.equalTo(expected[1]));
+ }
+ }
+
// Validate Downloads
List expectedDownloads = new ArrayList<>();
for (String line : textFile)
@@ -215,8 +227,8 @@ public class ConfigurationAssert
}
catch (AssertionError e)
{
- System.err.println("Expected: " + expectedSet);
- System.err.println("Actual : " + actualSet);
+ System.err.println("Expected: " + expectedSet.stream().sorted().collect(Collectors.toList()));
+ System.err.println("Actual : " + actualSet.stream().sorted().collect(Collectors.toList()));
throw e;
}
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
index bae8becba9f..f892cce9a15 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java
@@ -18,11 +18,6 @@
package org.eclipse.jetty.start;
-import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -34,10 +29,14 @@ import org.eclipse.jetty.start.config.JettyBaseConfigSource;
import org.eclipse.jetty.start.config.JettyHomeConfigSource;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestingDir;
-import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
public class ModulesTest
{
private final static String TEST_SOURCE = "";
@@ -71,8 +70,7 @@ public class ModulesTest
modules.registerAll();
// Check versions
- assertThat("java.version.major", args.getProperties().getString("java.version.major"),equalTo("1"));
- assertThat("java.version.minor", args.getProperties().getString("java.version.minor"),anyOf(equalTo("7"),Matchers.equalTo("8"),Matchers.equalTo("9")));
+ assertThat("java.version.platform", args.getProperties().getString("java.version.platform"),anyOf(equalTo("8"),equalTo("9")));
List moduleNames = new ArrayList<>();
for (Module mod : modules)
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java
index d83d1431414..4d285413545 100644
--- a/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestBadUseCases.java
@@ -47,7 +47,7 @@ public class TestBadUseCases
List