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 + 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 + 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.jetty jetty-project @@ -12,4 +14,16 @@ jetty-alpn-server jetty-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.jetty jetty-project 9.4.1-SNAPSHOT + 4.0.0 jetty-distribution Jetty :: Distribution Assemblies - http://www.eclipse.org/jetty pom + ${basedir}/target/distribution ${basedir}/target/home + + org.ops4j.pax.exam pax-exam-container-forked ${exam.version} test - org.ops4j.pax.exam pax-exam-junit4 @@ -126,7 +124,6 @@ - org.eclipse.jetty.osgi jetty-httpservice @@ -139,14 +136,12 @@ jetty-osgi-servlet-api 3.1.0.M3 - org.apache.geronimo.specs geronimo-jta_1.1_spec 1.1.1 test - org.apache.geronimo.specs geronimo-atinject_1.0_spec @@ -159,7 +154,6 @@ 1.0.1 test - org.glassfish.web javax.servlet.jsp.jstl @@ -183,7 +177,6 @@ - org.eclipse.jetty.orbit javax.servlet.jsp.jstl @@ -330,7 +323,6 @@ jetty-schemas runtime - org.eclipse.jetty jetty-plus @@ -346,7 +338,6 @@ webbundle test - org.eclipse.jetty.tests test-spec-webapp @@ -354,14 +345,12 @@ war test - org.eclipse.jetty.tests test-container-initializer ${project.version} test - org.eclipse.jetty.osgi test-jetty-osgi-fragment @@ -379,7 +368,6 @@ test-mock-resources ${project.version} - org.eclipse.jetty.osgi test-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 ret = new ArrayList<>(); ret.add(new Object[]{ "http2", - "Missing referenced dependency: alpn-impl/alpn-0.0.0_0", + "Invalid Java version", new String[]{"java.version=0.0.0_0"}}); ret.add(new Object[]{ "versioned-modules-too-new", diff --git a/jetty-start/src/test/resources/usecases/basic-properties.assert.txt b/jetty-start/src/test/resources/usecases/basic-properties.assert.txt index 52a09de9e1e..4029d09ffb7 100644 --- a/jetty-start/src/test/resources/usecases/basic-properties.assert.txt +++ b/jetty-start/src/test/resources/usecases/basic-properties.assert.txt @@ -15,3 +15,8 @@ PROP|jetty.http.port=9090 PROP|add=beginningmiddleend PROP|list=one,two,three PROP|name=value +PROP|name0=/ +PROP|name1=/foo +PROP|name2=/foo/bar +SYS|SYSTEM=value +SYS|PRESET=value \ No newline at end of file diff --git a/jetty-start/src/test/resources/usecases/basic-properties.cmdline.txt b/jetty-start/src/test/resources/usecases/basic-properties.cmdline.txt index 119dac10057..7dd3bdfacc0 100644 --- a/jetty-start/src/test/resources/usecases/basic-properties.cmdline.txt +++ b/jetty-start/src/test/resources/usecases/basic-properties.cmdline.txt @@ -8,3 +8,9 @@ list+=,two list+=,three name?=value name?=enoughAlready +name0=/ +name1=${name0}foo +name2=${name1}/bar +-DSYSTEM=${name} +-DSYSTEM?=IGNORED +-DPRESET?=${SYSTEM} \ No newline at end of file diff --git a/jetty-util/src/main/config/modules/jul-impl.mod b/jetty-util/src/main/config/modules/jul-impl.mod index 33beabdab4e..b691795b6e1 100644 --- a/jetty-util/src/main/config/modules/jul-impl.mod +++ b/jetty-util/src/main/config/modules/jul-impl.mod @@ -14,5 +14,5 @@ jul-impl basehome:modules/jul-impl [exec] --Djava.util.logging.config.file=etc/java-util-logging.properties +-Djava.util.logging.config.file?=${jetty.base}/etc/java-util-logging.properties diff --git a/jetty-util/src/main/config/modules/jul-slf4j.mod b/jetty-util/src/main/config/modules/jul-slf4j.mod index 81bb2330c3d..64397022f18 100644 --- a/jetty-util/src/main/config/modules/jul-slf4j.mod +++ b/jetty-util/src/main/config/modules/jul-slf4j.mod @@ -23,5 +23,4 @@ basehome:modules/jul-slf4j lib/slf4j/jul-to-slf4j-${slf4j.version}.jar [exec] --Djava.util.logging.config.file=etc/java-util-logging.properties - +-Djava.util.logging.config.file?=${jetty.base}/etc/java-util-logging.properties diff --git a/jetty-util/src/main/config/modules/logging-jul.mod b/jetty-util/src/main/config/modules/logging-jul.mod index 1b758125805..4a16acdeb41 100644 --- a/jetty-util/src/main/config/modules/logging-jul.mod +++ b/jetty-util/src/main/config/modules/logging-jul.mod @@ -13,7 +13,7 @@ jul-impl logging [exec] --Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog +-Dorg.eclipse.jetty.util.log.class?=org.eclipse.jetty.util.log.Slf4jLog [ini] jetty.webapp.addServerClasses+=,file:${jetty.base}/lib/slf4j/ diff --git a/jetty-util/src/main/config/modules/logging-log4j.mod b/jetty-util/src/main/config/modules/logging-log4j.mod index 98cb289a506..4d5ee4ef692 100644 --- a/jetty-util/src/main/config/modules/logging-log4j.mod +++ b/jetty-util/src/main/config/modules/logging-log4j.mod @@ -13,7 +13,7 @@ log4j-impl logging [exec] --Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog +-Dorg.eclipse.jetty.util.log.class?=org.eclipse.jetty.util.log.Slf4jLog [ini] jetty.webapp.addServerClasses+=,file:${jetty.base}/lib/slf4j/,file:${jetty.base}/lib/log4j/ diff --git a/jetty-util/src/main/config/modules/logging-log4j2.mod b/jetty-util/src/main/config/modules/logging-log4j2.mod index 1b50caf7b09..17cf3310462 100644 --- a/jetty-util/src/main/config/modules/logging-log4j2.mod +++ b/jetty-util/src/main/config/modules/logging-log4j2.mod @@ -13,7 +13,7 @@ log4j2-impl logging [exec] --Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog +-Dorg.eclipse.jetty.util.log.class?=org.eclipse.jetty.util.log.Slf4jLog [ini] jetty.webapp.addServerClasses+=,file:${jetty.base}/lib/slf4j/,file:${jetty.base}/lib/log4j2/ diff --git a/jetty-util/src/main/config/modules/logging-logback.mod b/jetty-util/src/main/config/modules/logging-logback.mod index 42bddcd9db1..ccc1aea998b 100644 --- a/jetty-util/src/main/config/modules/logging-logback.mod +++ b/jetty-util/src/main/config/modules/logging-logback.mod @@ -13,7 +13,7 @@ logback-impl logging [exec] --Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog +-Dorg.eclipse.jetty.util.log.class?=org.eclipse.jetty.util.log.Slf4jLog [ini] jetty.webapp.addServerClasses+=,file:${jetty.base}/lib/slf4j/,file:${jetty.base}/lib/logback/ diff --git a/jetty-util/src/main/config/modules/logging-slf4j.mod b/jetty-util/src/main/config/modules/logging-slf4j.mod index fa83733763c..fb26486e9da 100644 --- a/jetty-util/src/main/config/modules/logging-slf4j.mod +++ b/jetty-util/src/main/config/modules/logging-slf4j.mod @@ -13,7 +13,7 @@ slf4j-impl logging [exec] --Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog +-Dorg.eclipse.jetty.util.log.class?=org.eclipse.jetty.util.log.Slf4jLog [ini] jetty.webapp.addServerClasses+=,file:${jetty.base}/lib/slf4j/ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index a53480f5a46..e73919b0268 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -913,39 +913,22 @@ public class BufferUtil } } - static final Field fdMappedByteBuffer; - static - { - Field fd = null; - try - { - fd=MappedByteBuffer.class.getDeclaredField("fd"); - fd.setAccessible(true); - } - catch(Exception e) - { - } - fdMappedByteBuffer=fd; - } - public static boolean isMappedBuffer(ByteBuffer buffer) { if (!(buffer instanceof MappedByteBuffer)) return false; MappedByteBuffer mapped = (MappedByteBuffer) buffer; - if (fdMappedByteBuffer!=null) + try { - try - { - if (fdMappedByteBuffer.get(mapped) instanceof FileDescriptor) - return true; - } - catch(Exception e) - { - } - } - return false; + // Check if it really is a mapped buffer + mapped.isLoaded(); + return true; + } + catch(UnsupportedOperationException e) + { + return false; + } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java index 9ca3a1d7f4a..22aefa61bd8 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WSServer.java @@ -18,12 +18,15 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import org.eclipse.jetty.annotations.AnnotationConfiguration; @@ -35,6 +38,7 @@ import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.JAR; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; @@ -47,7 +51,6 @@ import org.eclipse.jetty.webapp.MetaInfConfiguration; import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebInfConfiguration; import org.eclipse.jetty.webapp.WebXmlConfiguration; -import org.junit.Assert; /** * Utility to build out exploded directory WebApps, in the /target/tests/ directory, for testing out servers that use javax.websocket endpoints. @@ -82,7 +85,7 @@ public class WSServer ClassLoader cl = Thread.currentThread().getContextClassLoader(); String endpointPath = clazz.getName().replace('.','/') + ".class"; URL classUrl = cl.getResource(endpointPath); - Assert.assertThat("Class URL for: " + clazz,classUrl,notNullValue()); + assertThat("Class URL for: " + clazz,classUrl,notNullValue()); File destFile = new File(classesDir,OS.separators(endpointPath)); FS.ensureDirExists(destFile.getParentFile()); File srcFile = new File(classUrl.toURI()); @@ -93,7 +96,31 @@ public class WSServer { copyClass(endpointClass); } - + + public void copyLib(Class clazz, String jarFileName) throws URISyntaxException, IOException + { + webinf = new File(contextDir,"WEB-INF"); + FS.ensureDirExists(webinf); + File libDir = new File(webinf,"lib"); + FS.ensureDirExists(libDir); + File jarFile = new File(libDir, jarFileName); + + URL codeSourceURL = clazz.getProtectionDomain().getCodeSource().getLocation(); + assertThat("Class CodeSource URL is file scheme", codeSourceURL.getProtocol(), is("file")); + + File sourceCodeSourceFile = new File(codeSourceURL.toURI()); + if (sourceCodeSourceFile.isDirectory()) + { + LOG.info("Creating " + jarFile + " from " + sourceCodeSourceFile); + JAR.create(sourceCodeSourceFile, jarFile); + } + else + { + LOG.info("Copying " + sourceCodeSourceFile + " to " + jarFile); + IO.copy(sourceCodeSourceFile, jarFile); + } + } + public void copyWebInf(String testResourceName) throws IOException { webinf = new File(contextDir,"WEB-INF"); @@ -173,7 +200,7 @@ public class WSServer contexts = new ContextHandlerCollection(); handlers.addHandler(contexts); server.setHandler(handlers); - + server.start(); String host = connector.getHost(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java index e48751469cd..a99b5fa7423 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilterTest.java @@ -204,6 +204,33 @@ public class WebSocketUpgradeFilterTest return server15.getServer(); }}); + // WSUF from web.xml, SCI active, apply app-ws configuration via ServletContextListener with WEB-INF/lib/jetty-http.jar + + cases.add(new Object[]{"wsuf/WebAppContext/web.xml/ServletContextListener/jetty-http.jar", new ServerProvider() + { + @Override + public Server newServer() throws Exception + { + File testDir = MavenTestingUtils.getTargetTestingDir("WSUF-webxml"); + + WSServer server = new WSServer(testDir, "/"); + + server.copyWebInf("wsuf-config-via-listener.xml"); + server.copyClass(InfoSocket.class); + server.copyClass(InfoContextAttributeListener.class); + // Add a jetty-http.jar to ensure that the classloader constraints + // and the WebAppClassloader setup is sane and correct + // The odd version string is present to capture bad regex behavior in Jetty + server.copyLib(org.eclipse.jetty.http.pathmap.PathSpec.class, "jetty-http-9.99.999.jar"); + server.start(); + + WebAppContext webapp = server.createWebAppContext(); + server.deployWebapp(webapp); + + return server.getServer(); + } + }}); + // WSUF from web.xml, SCI active, apply app-ws configuration via Servlet.init cases.add(new Object[]{"wsuf/WebAppContext/web.xml/Servlet.init", (ServerProvider) () -> diff --git a/pom.xml b/pom.xml index 062b1245a3d..88cee75d675 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 org.eclipse.jetty @@ -39,7 +41,6 @@ scm:git:https://github.com/eclipse/jetty.project.git scm:git:git@github.com:eclipse/jetty.project.git https://github.com/eclipse/jetty.project - jetty-9.3.13.M0 @@ -89,6 +90,7 @@ jetty-osgi jetty-alpn jetty-home + jetty-documentation @@ -150,7 +152,6 @@ org.jacoco jacoco-maven-plugin - 0.7.7.201606060606 @@ -366,15 +367,10 @@ maven-antrun-plugin 1.8 - - org.eclipse.jetty.toolchain - jetty-version-maven-plugin - 2.4 - org.apache.maven.plugins maven-assembly-plugin - 2.6 + 3.0.0 org.eclipse.jetty.toolchain @@ -386,7 +382,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.6.0 1.8 1.8 @@ -395,7 +391,7 @@ org.apache.maven.plugins maven-dependency-plugin - 2.10 + 3.0.0 org.apache.maven.plugins @@ -418,7 +414,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.0.0 + 3.0.2 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -433,7 +429,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.3 + 2.10.4 true true @@ -526,12 +522,12 @@ org.apache.maven.plugins maven-plugin-plugin - 3.4 + 3.5 org.apache.maven.plugins maven-pmd-plugin - 3.6 + 3.7 org.apache.maven.plugins @@ -561,12 +557,12 @@ org.apache.maven.plugins maven-site-plugin - 3.5.1 + 3.6 org.apache.maven.plugins maven-source-plugin - 3.0.0 + 3.0.1 org.apache.maven.plugins @@ -587,7 +583,17 @@ org.apache.maven.plugins maven-war-plugin - 2.6 + 3.0.0 + + + org.eclipse.jetty.toolchain + jetty-version-maven-plugin + 2.4 + + + org.jacoco + jacoco-maven-plugin + 0.7.8 com.agilejava.docbkx @@ -635,17 +641,17 @@ org.codehaus.mojo appassembler-maven-plugin - 1.10 + 2.0.0 org.codehaus.mojo build-helper-maven-plugin - 1.9.1 + 1.12 org.codehaus.mojo clirr-maven-plugin - 2.7 + 2.8 org.codehaus.mojo @@ -655,7 +661,7 @@ org.codehaus.mojo findbugs-maven-plugin - 3.0.3 + 3.0.4 true true @@ -988,9 +994,6 @@ [1.8,1.9) - - jetty-documentation - @@ -1014,37 +1017,6 @@ - - jdk9 - - [1.9,) - - - - apache-plugins-snapshots - https://repository.apache.org/content/groups/snapshots - - true - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.0.0-SNAPSHOT - - - org.apache.maven.plugins - maven-war-plugin - 3.0.0-SNAPSHOT - - - - - config diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index 282c4e966f6..bf7ad95d2ff 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -50,13 +50,32 @@ maven-surefire-plugin - -Xbootclasspath/p:${project.build.directory}/alpn/alpn-boot-${alpn.version}.jar - + -Xbootclasspath/p:${project.build.directory}/alpn/alpn-boot-${alpn.version}.jar + + jdk9 + + [1.9,) + + + + org.eclipse.jetty + jetty-alpn-java-client + ${project.version} + test + + + org.eclipse.jetty + jetty-alpn-java-server + ${project.version} + test + + + diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index 393f4197ac4..af1c90507ff 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -1137,6 +1137,10 @@ public class AsyncIOServletTest extends AbstractTest break; case H2C: case H2: + // In case of HTTP/2, we not only send the request, but also the preface and + // SETTINGS frames. SETTINGS frame need to be replied, so we want to wait to + // write the reply before shutting output down, so that the test does not fail. + Thread.sleep(1000); Session session = ((HttpConnectionOverHTTP2)connection).getSession(); ((HTTP2Session)session).getEndPoint().shutdownOutput(); break; diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java index 4ecbf52b813..f9aaa830073 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java @@ -43,6 +43,7 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -471,10 +472,10 @@ public class HttpClientStreamTest extends AbstractTest @Test(expected = ExecutionException.class) public void testInputStreamContentProviderThrowingWhileReading() throws Exception { - start(new AbstractHandler() + start(new AbstractHandler.ErrorDispatchHandler() { @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + public void doNonErrorHandle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); IO.copy(request.getInputStream(), response.getOutputStream()); diff --git a/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java index 22d2dbebc6a..528a6f39339 100644 --- a/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java +++ b/tests/test-webapps/test-proxy-webapp/src/test/java/org/eclipse/jetty/TestTransparentProxyServer.java @@ -28,7 +28,6 @@ import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.NegotiatingServerConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -106,7 +105,6 @@ public class TestTransparentProxyServer // HTTP2 factory HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(https_config); - NegotiatingServerConnectionFactory.checkProtocolNegotiationAvailable(); ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); alpn.setDefaultProtocol(h2.getProtocol());